I'm using nginx with the deployment of my website on my local server.
The website is a single page create react app running on the server. I have a domain, www.test.com for example, and i want the single page app to be found on www.test.com/first-website. From reading online I'm supposed to use the rewrite directive.
This is my current config:
http {
upstream sensory-showcase {
server 127.0.0.1:5000;
}
server {
listen 80;
location /sensory-solution-for-firefighters {
rewrite ^/sensory-solution-for-firefighters $1 break;
proxy_pass http://sensory-showcase/;
}
}
}
events { }
From this the url resolves without an nginx 500 error however it just shows a blank white page.
And i have to have a trailing /, eg www.test.com/first-website/ . Without the trailing / it errors.
I just note, when I didnt have the rewrite directive in, and left the location at just / the site loaded fine.
Try this, it will fallback to the index.html of your single page app, which will do the routing:
location /first-website {
try_files $uri $uri.html $uri/ /first-website/index.html;
}
Related
I'm very new to Nginx and am using it to configure my website in the following way:
Request for http://xx.xx.xx.xx/ -> Nginx should serve static html files from the /var/www/html directory
Request for http://xx.xx.xx.xx/project-name/ -> Nginx should call Python Flask application through WSGI (using UNIX socket)
Here is my /etc/nginx/sites-available/project-name file:
server {
listen 80:
server_name xx.xx.xx.xx;
root /var/www/html;
location / {
try_files $uri $uri/ =404;
}
location /project-name/ {
include wsgi_params;
uwsgi_pass unix:/home/user-name/project-name/project-name.sock
}
}
When I type http://xx.xx.xx.xx in the browser, I'm served the default Nginx page. But if I type http://xx.xx.xx.xx/project-name/ in the browser, I get a 404 (Requested URL not found) error.
Now the thing is, if I point "location /" to the Flask application as below, then the Flask application works fine and returns the proper page (but http://xx.xx.xx.xx calls the Flask application, and I'm unable to serve static html files directly from Nginx):
server {
listen 80:
server_name xx.xx.xx.xx;
location / {
include wsgi_params;
uwsgi_pass unix:/home/user-name/project-name/project-name.sock
}
}
Am I missing something simple?
It was a simple oversight.
In my Flask App, I was handling route '/' instead of '/project-name/'.
I have set up my vue-cli version 3 SPA so that any requests not found in my routes.js file will default to my 404 view as shown in the official documentation:
Inserted near bottom of routes.js file:
{ // catches 404 errors
path: '*',
name: '404',
component: () => import(/* webpackChunkName: "NotFoundComponent" */ './views/NotFoundComponent.vue'),
},
Inserted into nginx configuration file:
location / {
try_files $uri $uri/ /index.html;
}
This successfully alerts the user that the page they requested doesn't exist.
My Question:
I would like for the error 404 component to return a 404 response header (it current returns the 200 status code) and also log this error to the nginx error.log file. I imagine this is only possible through using nginx configuration. Has anyone achieved this goal?
I noticed that this issue is addressed in the following page in the vue-cli official docs, but it only is concerned with node express servers and not nginx:
https://router.vuejs.org/guide/essentials/history-mode.html#caveat
I think it is similar to Node solution - you should repeat all your routes in nginx config to return 404 status code correctly, the main idea is that you should use "equals" modifier in locations and define error_page to return same index.html file but with 404 status code, example:
server {
listen 80;
server_name localhost;
root /my/dir/with/app
error_page 404 /index.html;
location = / {
try_files $uri $uri/ /index.html;
}
location = /books {
try_files $uri $uri/ /index.html;
}
# example nested page
location = /books/authors {
try_files $uri $uri/ /index.html;
}
# example dynamic route to access book by id
location ~ /books/\d+$ {
try_files $uri $uri/ /index.html;
}
}
Probably this config can be simplified or improved because I am not very good at nginx configuration but it works.
I solved this the easy way by breaking out of the Vue ecosystem else it wont work or would take a lot of effort.
Make the following route in your Vue router:
{
path: '*',
name: 'PageNotFound',
component: PageNotFound
}
The PageNotFound component should have the following code:
<script>
export default {
name: 'PageNotFound',
created() {
window.location.href = "/404/"
}
}
</script>
The nginx config should return a 404 on getting /404/ route:
server {
...
location ~ ^/404/$ {
return 404;
}
...
}
I don't think it would work in a server side rendering environment. In such a case you might need to put the statement containing window.location.href in the mounted method.
For whoever lands into this issue and to save you hours of headaches.
Caveats with above answers
Simply reloading the page with a new URL (e.g. /notfound) does not solve it since that means that a potential spider already received 200.
Simply copying the routes, is a half-solution. That works with URLs that are never going to change and by checking the validity of the URL structure. So for example it can check if the book id in books/123 has the right format, but it can't check if books/123 actually exists in the backend.
Here's two approaches that can tackle the above issues
Have Nginx to make a mirrored subrequest to the backend to check if the resource actually exists. Then always return index.html but with the status from the subrequest's response. This is very tricky with Nginx, since by design it makes it very hard to combine answers.
Have the backend API to return index.html for Accept: text/html. Then Nginx simply needs to forward responses.
The first solution is a pain in the ass for someone not familiar with Nginx. It requires to get Lua with OpenResty and then again you will run into all kinds of quirks with how Nginx works. You end up with a lot of hard-to-read code and that makes it extra hard if you further-more want to introduce caching.
The second solution is easier. The only possible negative is that it means that you can't view the API from the browser if that's something currently you have in place.
nginx.config (when API responds with index.html on Accept: text/html)
location / {
try_files $uri $uri/ #fallback;
}
location #fallback {
rewrite ^(.*) /api$1 break;
proxy_set_header "Accept" "text/html";
proxy_pass http://localhost:8000;
}
In this case, Nginx will try first to serve the file and in case it doesn't find it locally, it will go through the fallback.
In the fallback we rewrite the URI to match what the backend server expects. In this example I prepend api/ to each request. Then I add the header Accept: text/html so that the backend API will respond with the index.html instead of JSON. And lastly we directly give back the response to the client.
This has the below benefits:
It does not rely on Nginx so it can work with any reverse proxy. Most importantly it does not rely on the proxy server to have certain features.
Works even during development without having Nginx running.
Easy to write tests for. You simply have to test your backend API for spitting out index.html when given Accept: text/html for any endpoint.
Does not require you to manually update Nginx configuration with each new endpoint.
Furthermore you can change the config to make Nginx follow redirects internally, and possibly not even having to look at the backend API for URLs that never change.
I have a website that has laravel setup to run under http://www.example.com/lara/
So, most Laravel pages have URLs of type http://www.example.com/lara/page/23 OR http://www.example.com/lara/category/23 etc.
Nginx is the underlying server and it has the following configuration to handle these requests:
location /lara/ {
try_files $uri $uri/ /lara/index.php?$query_string;
}
Everything works ok.
Now, I need to setup a special page with the URL http://www.example.com/mystuff/ which actually is handled by
http://www.example.com/lara/category/29
To get this working I added the following rewrite right below location /lara/, that is:
rewrite ^/mystuff/(.*)$ /lara/category/29/$1 last;
Unfortunately, I get a page not found error. Any insights?
Further investigation & research:
1)
location /mystuff/ {
return 301 /lara/index.php/category/29;
}
worked although that's not (browser address bar changes to) what I actually want.
2) Looks like Laravel is not seeing the updated REQUEST_URI.
Try this
server {
...
rewrite ^/lara/(.*)\..*$ $1/mystuff last;
return 403;
...
}
I have django and flask applications running on the same machine through different ports:
Django runs on server:8088
Flask runs on server:666
In NGINX.conf I have the following code:
location / {
proxy_pass http://127.0.0.1:8088;
}
location ^/server2 {
proxy_pass http://127.0.0.1:666;
}
Django has been running for over a year successfully with this set up, where as flask is a new addition. Any time I try to access one of the Flask urls I either get a "this url does not exist on this server" error, or on occasion a 500 error (when i've been fiddling).
If I write location information for a specific flask url like this:
location /server2/splash {
proxy_pass http://127.0.0.1:666/splash;
}
It works, but I obviously don't want to write individual location information for each and every URL in the flask application.
I've gone through many of the existing Nginx location posts on stackoverflow but I've not been able to get it working. Any ideas?
Thanks!
EDIT
this is an example of what I'm trying to achieve, but rather than an individual mapping for each URL, I want a single mapping that covers all URLs:
location /server2{
proxy_pass http://127.0.0.1:666/splash;
}
location /server2/split {
proxy_pass http://127.0.0.1:666/split;
}
location /server2/export {
proxy_pass http://127.0.0.1:666/export;
}
location /server2/import {
proxy_pass http://127.0.0.1:666/import;
}
Why do you use the ^ sign? Just remove it I think it will work:
location /server2 {
proxy_pass http://127.0.0.1:666;
}
Note that when you use location /server2 the server2 is still being passed to your flask application.
In this case Nginx is doing the following:
server.com/server2 => http://127.0.0.1:666/server2
server.com/server2/splash => http://127.0.0.1:666/server2/splash
In this case location is not doing a rewrite. Always check /var/log/nginx (or wherever your logs are located) to check the requests done by the browser and what Nginx looks for after the rules for your site are processed.
What you probably want is to set an upstream directive:
upstream flask_server {
server 127.0.0.1:666;
}
server {
...
location /server2 {
proxy_pass http://flask_server;
}
}
I've got a an nginx setup that's proxying all requests to a rails app. I would like to have a some kind of "maintenance-mode", so that if I use a different nginx-config, all incoming requests should return a local maintenance.html page.
Here's what I've come up with:
server {
listen 3000;
server_name localhost;
root html/; #maintenance.html resides here
location / {
# redirect everything not already going to maintenance.html
if ($request_uri !~ ^.*/maintenance.html$ ) {
rewrite ^(.*)$ /maintenance.html break;
}
}
}
With this setup, I am able to directly access http://localhost:3000/maintenance.html. However, if I trigger the rewrite rule, the new URL results in localhost/maintenance.html instead of http://localhost:3000/maintenance.html.
This results in a completely empty page being rendered.
nginx version: nginx/1.0.6 on Windows 7
thx for any help
UPDATE:
I just noticed, it works fine with Internet Explorer and also with Chrome. Only Firefox seems to have that problem.
location / {
try_files /maintenance.html $uri =404;
}