HTTP 404 Not Found — routing and deployment causes
Diagnose unexpected HTTP 404 in production from SPA fallback, trailing slashes, case sensitivity, and reverse-proxy path rewriting.
Symptoms
- HTTP 404 on a path that exists in the application
- It works locally but not on the deployed host
- Refreshing a client-side route returns 404
Possible causes
- The SPA fallback does not rewrite unknown paths to index.html
- A trailing slash or case difference mismatches the route
- A reverse proxy strips or prefixes the path unexpectedly
- The asset was not included in the build or deploy artifact
Diagnosis steps
Compare the requested path with the route table
Interpretation
A missing leading slash, an extra prefix, or a case difference reveals a routing mismatch.
Test the same path against the origin server directly
curl -I http://origin/internal-pathInterpretation
Success at the origin but 404 through the proxy localises the fault to proxy rewriting.
Check the deploy artifact for the file
Interpretation
A 404 on a static asset often means the build output was not uploaded or was ignored by deploy rules.
Root cause
A 404 means no resource matched the requested path. In SPAs the server must return index.html for client-side routes, or direct navigation and refreshes 404. Proxies can also rewrite paths so the upstream sees a different one than the browser requested, and case-sensitive filesystems on Linux differ from case-insensitive ones on macOS or Windows.
Fix
Configure the SPA fallback so unmatched paths serve index.html while API and asset routes remain exact. Normalise trailing slashes and case, and make route matching explicit. Align proxy path rewriting with the application expectations, and verify the deploy artifact includes every referenced asset.
Verify the fix
Request the previously 404 path directly and via refresh; both return the expected content, and origin logs show the request reaching the intended route.
Symptoms
A path that exists in the app returns 404, often only in production. Client-side routes frequently 404 on refresh because the server has no matching file.
Diagnosis
Compare and test at the origin:
curl -I https://app.example.com/settings # through proxy
curl -I http://127.0.0.1:3000/settings # origin
If the origin succeeds and the proxy 404s, the proxy path handling is at fault.
Fix
Add an SPA fallback that preserves API routes:
location / {
try_files $uri /index.html;
}
location /api/ {
proxy_pass http://backend;
}
Normalise trailing slashes and match case exactly.
Verification
Request the failing path directly and after a refresh; both return the page, and origin access logs show the intended route handling the request.
SPA Fallback Caveats
A blanket fallback that serves index.html for every unmatched path works for client routes but quietly breaks two other cases. First, it can mask genuine API 404s, returning HTML where a client expects JSON, which turns a clear error into a confusing parse failure. Second, missing static assets start returning the HTML page with a 200, so a stale script reference looks successful while delivering the wrong content type. Scope the fallback so HTML routes are served but API prefixes and asset extensions are excluded, and keep returning a true 404 for anything the app does not own.