# Nginx 404 Not Found for Static Files

You deploy a website, but static assets return 404 Not Found. The HTML loads, but CSS, JavaScript, and images are missing. The files exist on disk, yet Nginx insists they don't. This mismatch between reality and Nginx's behavior is almost always a path resolution issue.

Understanding the Error

A 404 Not Found means Nginx couldn't locate the requested resource at the path it computed. The file exists somewhere - just not where Nginx is looking.

Check the error log:

bash
tail -f /var/log/nginx/error.log

You'll see entries like:

bash
2026/04/04 11:20:30 [error] 5678#5678: *10 open() "/var/www/html/css/styles.css" failed (2: No such file or directory), client: 192.168.1.50, server: example.com, request: "GET /css/styles.css HTTP/1.1"

The log tells you exactly where Nginx looked. Compare that path to where your file actually exists.

Common Cause 1: Wrong Root Directive

The root directive appends the request URI to the specified path. This is the most common source of path confusion.

Problematic config: ```nginx server { listen 80; server_name example.com;

# Wrong: Nginx looks for /var/www/html/css/styles.css root /var/www/html;

location /css/ { # This inherits root from server block # Final path: /var/www/html/css/styles.css } } ```

But your files are actually at /var/www/myapp/static/css/styles.css.

Solution: ```nginx server { listen 80; server_name example.com;

root /var/www/myapp/static;

location /css/ { # Now looks at /var/www/myapp/static/css/styles.css } } ```

Or use different roots for different locations: ```nginx server { listen 80; server_name example.com;

location /static/ { root /var/www/myapp; # Looks at /var/www/myapp/static/ }

location /assets/ { root /var/www/uploads; # Looks at /var/www/uploads/assets/ } } ```

Common Cause 2: Root vs Alias Confusion

The alias directive works differently from root. This trips up many configurations.

With root: ``nginx location /images/ { root /var/www/data; # Request: GET /images/logo.png # Looks for: /var/www/data/images/logo.png }

With alias: ``nginx location /images/ { alias /var/www/data/; # Request: GET /images/logo.png # Looks for: /var/www/data/logo.png (NOT /var/www/data/images/) }

The trailing slash matters with alias:

```nginx # These are equivalent: location /images { alias /var/www/data; # No trailing slash on either }

location /images/ { alias /var/www/data/; # Trailing slash on both }

# This causes problems: location /images/ { alias /var/www/data; # Trailing slash mismatch # Request: GET /images/logo.png # May look for: /var/www/datalogo.png (concatenated!) } ```

Best practice - be consistent: ``nginx location /images/ { alias /var/www/data/images/; }

Common Cause 3: Try Files Misconfiguration

The try_files directive checks files in order and serves the first match. If none match, it returns 404.

Problematic config: ``nginx location / { try_files $uri $uri/ =404; # This returns 404 if file doesn't exist and it's not a directory }

For single-page applications, you typically want: ``nginx location / { root /var/www/myapp; try_files $uri $uri/ /index.html; # Falls back to index.html for client-side routing }

Diagnosis: ```bash # Test if the file exists at the path Nginx computed ls -la /var/www/html/css/styles.css

# Check Nginx's internal path resolution sudo nginx -T 2>/dev/null | grep -A 10 "location /css" ```

Common Cause 4: Incorrect Location Matching

Location block precedence can cause unexpected behavior.

  1. 1.Location matching order:
  2. 2.Exact match: location = /path
  3. 3.Priority prefix: location ^~ /path
  4. 4.Regex (in order): location ~ /path or location ~* /path
  5. 5.Prefix (longest match): location /path

Problematic config: ```nginx # This regex matches first for /css/styles.css location ~* \.css$ { root /var/www/old; }

# This never gets matched location /css/ { root /var/www/new; } ```

Solution: ```nginx # Use priority prefix for static files location ^~ /css/ { root /var/www/new; }

# Or use exact regex ordering location ~* ^/css/.*\.css$ { root /var/www/new; } ```

Common Cause 5: Case Sensitivity Issues

On Linux, filenames are case-sensitive. Styles.css and styles.css are different files.

Diagnosis: ```bash # Check actual filename case ls -la /var/www/html/css/

# Compare with what's requested grep "GET /css/" /var/log/nginx/access.log ```

Solution: Either rename files to match requests, or handle case-insensitivity: ``nginx # Case-insensitive matching for file extensions location ~* \.(css|js|png|jpg|gif)$ { root /var/www/html; }

Nginx may not follow symbolic links depending on configuration.

Diagnosis: ```bash # Check if path contains symlinks ls -la /var/www/html/

# Check symlink target readlink -f /var/www/html/css ```

Solution: ```nginx # Enable following symlinks (default) location /css/ { root /var/www/html; disable_symlinks off; # Follows symlinks }

# Disable for security in some cases location /uploads/ { root /var/www/html; disable_symlinks on; # Won't follow symlinks } ```

Common Cause 7: Hidden Files

Files starting with . are hidden and may not be served.

Diagnosis: ``bash ls -la /var/www/html/

Solution: Nginx serves hidden files by default, but check for explicit blocks: ``nginx # This would block hidden files location ~ /\. { deny all; }

Verification Steps

  1. 1.Identify where Nginx is looking:
  2. 2.```bash
  3. 3.# Watch error log while making request
  4. 4.tail -f /var/log/nginx/error.log &
  5. 5.curl http://localhost/css/styles.css
  6. 6.`
  7. 7.Verify file exists at that path:
  8. 8.```bash
  9. 9.ls -la /var/www/html/css/styles.css
  10. 10.`
  11. 11.Test configuration:
  12. 12.```bash
  13. 13.sudo nginx -t
  14. 14.`
  15. 15.Debug with full configuration dump:
  16. 16.```bash
  17. 17.sudo nginx -T | less
  18. 18.`
  19. 19.Test location matching:
  20. 20.```bash
  21. 21.# Install nginx debug tools or use curl with verbose
  22. 22.curl -v http://localhost/css/styles.css
  23. 23.`

Quick Reference

SymptomCauseFix
Path wrong in error logroot directive issueAdjust root path
Works with alias, not rootroot vs alias differenceUse correct directive
SPA routes return 404try_files missing fallbackAdd /index.html fallback
Regex location ignoredWrong precedenceUse ^~ or reorder
File exists but 404Case sensitivityMatch case or use ~*
Symlinks return 404disable_symlinks onChange to off

The key to solving 404 errors is reading the error log. It tells you exactly where Nginx looked. Then verify: does the file exist at that path?