# Fix Nginx try_files Configuration for Static File Serving
You deployed a React application behind Nginx, but when users navigate to a page directly (like /dashboard/settings), they get a 404 error. The homepage works fine, but any deep link fails.
$ curl -I https://example.com/dashboard/settings
HTTP/1.1 404 Not Found
Server: nginx/1.24.0
Content-Type: text/htmlThe issue is almost always a misconfigured try_files directive in Nginx.
Real Scenario: React SPA Returns 404 on Deep Links
A startup deployed their React dashboard to production. The homepage loaded correctly, users could navigate between pages using the app's navigation menu, but bookmarked URLs and direct links to internal pages returned 404 errors.
The problematic Nginx configuration:
```nginx server { listen 80; server_name example.com; root /var/www/app; index index.html;
location / { # This only serves exact file matches try_files $uri =404; } } ```
When a user requests /dashboard/settings, Nginx looks for a file at /var/www/app/dashboard/settings. Since React is a Single Page Application (SPA), that file doesn't exist - all routes are handled client-side by index.html.
The fix:
```nginx server { listen 80; server_name example.com; root /var/www/app; index index.html;
location / { # Try the exact file, then directory, then fall back to index.html try_files $uri $uri/ /index.html; } } ```
Understanding try_files Syntax
The try_files directive checks for file existence in order and serves the first match:
try_files $uri $uri/ /index.html =404;| Check | What it does |
|---|---|
$uri | Exact file match (e.g., /style.css → /var/www/app/style.css) |
$uri/ | Directory with index file (e.g., /admin/ → /var/www/app/admin/index.html) |
/index.html | Fallback file (served if previous checks fail) |
=404 | Last resort - return 404 status code |
Important: The last parameter can be:
- A URI (starts with /) - internal redirect to that URI
- A named location (starts with @) - internal redirect to that location
- A status code (starts with =) - return that status code
Common Configuration Errors
Error 1: Wrong Variable Usage
Problem:
location / {
root /var/www/html;
try_files $request_filename =404;
}$request_filename is the full filesystem path including the root. This can cause double-path issues.
Solution:
location / {
root /var/www/html;
try_files $uri $uri/ =404;
}Use $uri which is the request URI path, not the filesystem path.
Error 2: Missing Fallback for SPAs
Problem:
location / {
try_files $uri $uri/ =404;
}This returns 404 for any route not matching a physical file. SPAs need all routes to serve index.html.
Solution:
location / {
try_files $uri $uri/ /index.html;
}Error 3: Root Inside Location Block
Problem:
```nginx server { listen 80;
location / { root /var/www/html; try_files $uri /index.html; }
location /api/ { root /var/www/html; # Repeated root directive proxy_pass http://backend; } } ```
This works but is inefficient and error-prone.
Solution:
```nginx server { listen 80; root /var/www/html; # Define once at server level
location / { try_files $uri /index.html; }
location /api/ { proxy_pass http://backend; } } ```
Error 4: Alias vs Root Confusion
Problem:
location /static/ {
alias /var/www/assets/;
try_files $uri =404; # Wrong with alias
}With alias, $uri still contains /static/... but the filesystem path is already /var/www/assets/....
Solution:
```nginx location /static/ { alias /var/www/assets/; try_files $uri =404; # This actually works with alias }
# Or use root instead location /static/ { root /var/www; # Files at /var/www/static/ try_files $uri =404; } ```
Error 5: Quoted Status Code
Problem:
try_files $uri "=404"; # Looks for a file named "=404"Solution:
try_files $uri =404; # No quotes - returns 404 statusProduction Configuration Examples
Single Page Application (React, Vue, Angular)
```nginx server { listen 80; server_name example.com; root /var/www/spa; index index.html;
# Main location - serve SPA location / { try_files $uri $uri/ /index.html; }
# Static assets with long cache location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { expires 1y; add_header Cache-Control "public, immutable"; try_files $uri =404; }
# Disable caching for index.html location = /index.html { add_header Cache-Control "no-cache, no-store, must-revalidate"; add_header Pragma "no-cache"; add_header Expires "0"; } } ```
WordPress with PHP-FPM
```nginx server { listen 80; server_name blog.example.com; root /var/www/wordpress; index index.php;
location / { try_files $uri $uri/ /index.php?$args; }
# Handle PHP files location ~ \.php$ { try_files $uri =404; fastcgi_pass unix:/run/php/php8.2-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; }
# Deny access to sensitive files location ~ /\. { deny all; }
location ~ /wp-config.php { deny all; } } ```
The $args preserves query parameters when falling back to index.php.
Static Site with API Backend
```nginx server { listen 80; server_name app.example.com; root /var/www/app;
# API requests go to backend location /api/ { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }
# Static files with multiple fallbacks location / { # 1. Try exact file # 2. Try directory # 3. Try appending .html # 4. Fall back to backend for SSR try_files $uri $uri/ $uri.html @ssr; }
location @ssr { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; } } ```
Multiple Named Locations
```nginx server { listen 80; root /var/www/app;
location / { try_files $uri @cache @backend; }
location @cache { # Check cache server first proxy_pass http://cache-server; proxy_intercept_errors on; error_page 404 = @backend; }
location @backend { proxy_pass http://app-server; } } ```
Debugging try_files Issues
Enable Debug Logging
error_log /var/log/nginx/error.log debug;Test Specific URLs
```bash # Test a URL and see the response curl -I https://example.com/dashboard/settings
# Check which file Nginx is looking for curl -v https://example.com/dashboard/settings 2>&1 | grep -E "(< HTTP|< Location)" ```
Check Nginx Error Log
With debug logging enabled, you'll see:
2026/04/23 10:30:15 [debug] 1234#1234: *5678 http filename: /var/www/app/dashboard/settings
2026/04/23 10:30:15 [debug] 1234#1234: *5678 http open fd: -1
2026/04/23 10:30:15 [debug] 1234#1234: *5678 try files: /dashboard/settings
2026/04/23 10:30:15 [debug] 1234#1234: *5678 try files: /dashboard/settings/
2026/04/23 10:30:15 [debug] 1234#1234: *5678 try files: /index.html
2026/04/23 10:30:15 [debug] 1234#1234: *5678 http filename: /var/www/app/index.htmlTest Configuration Syntax
```bash # Check for syntax errors sudo nginx -t
# Output should be: # nginx: the configuration file /etc/nginx/nginx.conf syntax is ok # nginx: configuration file /etc/nginx/nginx.conf test is successful ```
Performance Optimization
Enable Open File Cache
For high-traffic sites, cache file existence checks:
```nginx http { # Cache open file descriptors open_file_cache max=1000 inactive=20s; open_file_cache_valid 30s; open_file_cache_min_uses 2; open_file_cache_errors on;
server { location / { try_files $uri $uri/ /index.html; } } } ```
This caches: - File existence - File size - File modification time - File descriptors
Disable Logging for Static Assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
access_log off;
log_not_found off;
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri =404;
}Checklist for Fixing try_files Issues
- 1.Check if root is set correctly:
- 2.```bash
- 3.ls -la /var/www/app/
- 4.
` - 5.Verify file permissions:
- 6.```bash
- 7.ls -la /var/www/app/index.html
- 8.# Should be readable by nginx user (usually www-data or nginx)
- 9.
` - 10.Test the exact file path:
- 11.```bash
- 12.curl -I https://example.com/index.html
- 13.# Should return 200
- 14.
` - 15.Check for conflicting location blocks:
- 16.```bash
- 17.sudo nginx -T | grep -A 10 "location"
- 18.
` - 19.Verify the fallback file exists:
- 20.```bash
- 21.ls -la /var/www/app/index.html
- 22.
` - 23.Test with verbose output:
- 24.```bash
- 25.curl -v https://example.com/some/path 2>&1
- 26.
`