# 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.

bash
$ curl -I https://example.com/dashboard/settings
HTTP/1.1 404 Not Found
Server: nginx/1.24.0
Content-Type: text/html

The issue is almost always a misconfigured try_files directive in Nginx.

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:

nginx
try_files $uri $uri/ /index.html =404;
CheckWhat it does
$uriExact 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.htmlFallback file (served if previous checks fail)
=404Last 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:

nginx
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:

nginx
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:

nginx
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:

nginx
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:

nginx
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:

nginx
try_files $uri "=404";  # Looks for a file named "=404"

Solution:

nginx
try_files $uri =404;  # No quotes - returns 404 status

Production 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

nginx
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:

bash
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.html

Test 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

nginx
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. 1.Check if root is set correctly:
  2. 2.```bash
  3. 3.ls -la /var/www/app/
  4. 4.`
  5. 5.Verify file permissions:
  6. 6.```bash
  7. 7.ls -la /var/www/app/index.html
  8. 8.# Should be readable by nginx user (usually www-data or nginx)
  9. 9.`
  10. 10.Test the exact file path:
  11. 11.```bash
  12. 12.curl -I https://example.com/index.html
  13. 13.# Should return 200
  14. 14.`
  15. 15.Check for conflicting location blocks:
  16. 16.```bash
  17. 17.sudo nginx -T | grep -A 10 "location"
  18. 18.`
  19. 19.Verify the fallback file exists:
  20. 20.```bash
  21. 21.ls -la /var/www/app/index.html
  22. 22.`
  23. 23.Test with verbose output:
  24. 24.```bash
  25. 25.curl -v https://example.com/some/path 2>&1
  26. 26.`