nginx: fallback to try_files when proxy_pass fails requires unusual config
Asked Answered
N

1

7

I am using Nginx to server a single page app. Basically we just need to serve the index.html page whenever no matching file is found. The location looks like this and has been working just fine:

location / {
   try_files $uri $uri/ /index.html
}

Now I would like to query an upstream server, and only if that fails, use the try_files directive as above

If the try_files is just moved to a fallback location like

location @fallback {
  try_files $uri $uri/ /index.html;
}

location / {
  proxy_pass http://127.0.0.1:8080;
  proxy_intercept_errors on;
  error_page 400 403 502 503 504 @fallback;
}

then - when the upstream server is unavailable - the client sees the Nginx 502 error page instead of the files served from the file system.

I finally found a solution that works by using a double slash in front of the /index.html fallback. This is the whole config file which can be used with the official nginx docker image for testing

events {
} 

http {

  error_log /var/log/nginx/error.log;
  access_log /var/log/nginx/access.log;

  server {

    listen 80;

    root /usr/share/nginx/html/;

    location / { 
      proxy_pass http://127.0.0.1:9990;
      proxy_intercept_errors on; 
      error_page 400 403 502 503 504 = @fallback;
    }

    location @fallback {
      try_files $uri?$args /index.html //index.html;
    }
  }
}

which can be run with a command like

docker run -v /path/to/www/folder:/usr/share/nginx/html:ro -v /path/to/config/nginx.conf:/etc/nginx/nginx.conf -d -p 8080:80 nginx

In case no double slash is present before the last index.html fallback like

location @fallback {
  try_files $uri?$args /index.html;
}

Then nginx constructs a path on the filesystem like <root>index.html, which has a missing delimiter instead of the correct <root>/index.html, whenever a url which is not the root url is requested.

Final question: why does this setup require a double slash within the try_files directive? Why can't one just use the try_files section from a regular config and move it to a fallback location used when intercepting errors?

Neville answered 19/4, 2019 at 15:49 Comment(3)
Could it be the other way around? fallback to proxy_pass on 404? I'm having a similar problem, and would love to have a somewhat elegant solution to it :)Adamis
No, there is a good reason for that order: The proxy_pass renders a web page server side and rehydrates to a SPA in the browser, whereas the fallback just serves the SPA, which then needs to load the data for the current page from an api, which causes longer loading times, etc.Neville
I see. That would be a suitable detail to add to the question, as it does change the scope!Adamis
J
4

I was presented with a similar situation, and I solved this going the other way around, using the suggestion from this page of common pitfalls. That is, first serve the static files, and then fallback to the proxy:

location / {
    try_files $uri $uri/ @proxy;
}
location @proxy {
    proxy_pass http://127.0.0.1:9990;
}

In this case, this would first look for the presence of the files as static files in the root, then proxy the request to http://127.0.0.1:9000. This is functionnally equivalent unless you want the files from the proxy to shadow the static files.

Jez answered 28/11, 2019 at 20:18 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.