# 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:
tail -f /var/log/nginx/error.logYou'll see entries like:
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.Location matching order:
- 2.Exact match:
location = /path - 3.Priority prefix:
location ^~ /path - 4.Regex (in order):
location ~ /pathorlocation ~* /path - 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;
}
Common Cause 6: Symbolic Link Issues
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.Identify where Nginx is looking:
- 2.```bash
- 3.# Watch error log while making request
- 4.tail -f /var/log/nginx/error.log &
- 5.curl http://localhost/css/styles.css
- 6.
` - 7.Verify file exists at that path:
- 8.```bash
- 9.ls -la /var/www/html/css/styles.css
- 10.
` - 11.Test configuration:
- 12.```bash
- 13.sudo nginx -t
- 14.
` - 15.Debug with full configuration dump:
- 16.```bash
- 17.sudo nginx -T | less
- 18.
` - 19.Test location matching:
- 20.```bash
- 21.# Install nginx debug tools or use curl with verbose
- 22.curl -v http://localhost/css/styles.css
- 23.
`
Quick Reference
| Symptom | Cause | Fix |
|---|---|---|
| Path wrong in error log | root directive issue | Adjust root path |
| Works with alias, not root | root vs alias difference | Use correct directive |
| SPA routes return 404 | try_files missing fallback | Add /index.html fallback |
| Regex location ignored | Wrong precedence | Use ^~ or reorder |
| File exists but 404 | Case sensitivity | Match case or use ~* |
| Symlinks return 404 | disable_symlinks on | Change 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?