Nginx SPA Refresh 404 on Static Routes and How to Fix It
When deploying single-page applications (SPA) built with Vue, React, or Angular, you usually run into this problem: the home page loads fine and in-app navigation works too. But if you refresh the browser on a sub-route, or paste that link straight into the address bar, Nginx returns 404 Not Found.

1. The Problem and Its Background
When deploying single-page applications (SPA) built with Vue, React, or Angular, you usually hit this:
- The home page
https://example.com/loads fine. - In-app navigation (for example
https://example.com/about) works too, because the front-end router intercepts it. - But if you refresh the browser on
https://example.com/about, or type that link directly into the address bar, Nginx returns a 404 Not Found error.
1.1 The Original Configuration (the broken one)
Nginx
server {
listen 443 ssl;
server_name example.com;
# SSL configuration omitted...
location / {
root /usr/share/nginx/html; # assumed static file path
index index.html index.htm;
}
}
1.2 Why It Happens
By default, Nginx handles requests based on the file system. When /about is requested, Nginx looks in the root directory for a file named about. If it cannot find one, it tries about/index.html. For an SPA, /about is only a front-end virtual route — there is no such physical file or directory on disk — so Nginx ends up throwing a 404.
2. The Fix: Use try_files
To solve this, we need to tell Nginx: “If the matching file or directory does not exist, don’t return 404. Instead, internally redirect every request to index.html and let the front-end code handle routing.”
2.1 The Optimized Configuration
Nginx
server {
listen 443 ssl;
server_name example.com;
# It is a good idea to declare the document root explicitly
root /usr/share/nginx/html;
# The SSL section stays unchanged...
location / {
# The key change: add the try_files directive
# Try the file ($uri), then the directory ($uri/); if both fail, serve index.html
try_files $uri $uri/ /index.html;
}
}
3. How the try_files Directive Works
try_files is one of the most powerful directives in Nginx. Its syntax looks like this:
try_files $uri $uri/ /index.html;
The directive checks its arguments from left to right:
$uri:- Check whether the requested path maps to a real file on disk (an image, JS, CSS, and so on).
- If it does, return that file directly.
$uri/:- If no file matches, check whether a directory with the same name exists.
- If it does, Nginx automatically looks for the
indexfile inside it (for example/about/index.html).
/index.html:- If neither of the first two exists, Nginx performs an internal redirect and forwards the request to
/index.html. - At that point the front-end
index.htmlloads, the front-end router (Vue Router / React Router) takes over the URL, and renders the correct page from the path.
- If neither of the first two exists, Nginx performs an internal redirect and forwards the request to
4. A Note (Optional)
If your configuration needs strict error handling, you can append =404 as a fallback. In SPA setups this is usually unnecessary unless you are sure index.html might go missing.
Nginx
# Variant: only return 404 if index.html is missing too
try_files $uri $uri/ /index.html =404;
Comments