NGINX Error Guide: 'open() failed (2: No such file or directory)' — fix root, alias, and try_files
Fix NGINX 'open() failed (2: No such file or directory)' 404s: diagnose wrong root, alias trailing-slash mangling, broken try_files, SPA fallbacks, and index paths, then reload safely.
- #nginx
- #web-server
- #troubleshooting
- #errors
Stuck on this NGINX error? Get the free incident triage checklist
A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.
Overview
NGINX logs this whenever a worker tries to open a static file at a computed path that does not exist on disk. It is the classic source of a 404 for a file you are certain is there — the file exists, but NGINX is looking somewhere else because root, alias, index, or try_files built the wrong path.
2026/07/08 14:22:07 [error] 8420#8420: *91233 open() "/var/www/app/staticassets/app.css" failed (2: No such file or directory), client: 203.0.113.44, server: app.example.com, request: "GET /static/assets/app.css HTTP/1.1", host: "app.example.com"
The key is the quoted path in the log: that is the exact filesystem path NGINX built from your config. Compare it to where the file actually lives, and the misconfiguration is usually obvious — a doubled segment, a missing slash, or a wrong root.
Symptoms
- Browser gets a
404 Not Foundfor a file that clearly exists on disk. error.logshowsopen() "..." failed (2: No such file or directory).- The logged path has a doubled or mangled segment (e.g.
/static/assetsbecomesstaticassets). - A single-page app deep link (e.g.
/dashboard/settings) 404s on refresh, but works when navigating in-app. - Some paths serve fine while one
locationconsistently 404s.
curl -sI https://app.example.com/static/assets/app.css | head -1
HTTP/1.1 404 Not Found
sudo tail -5 /var/log/nginx/error.log
[error] 8420#8420: *91233 open() "/var/www/app/staticassets/app.css" failed (2: No such file or directory)
Common Root Causes
1. alias missing a trailing slash (path mangling)
With location /static/ and alias /var/www/app/assets; (no trailing slash), NGINX concatenates the leftover URI onto the alias without a separator, producing a mangled path.
location /static/ {
alias /var/www/app/assets; # BUG: no trailing slash
}
Request GET /static/app.css becomes open() "/var/www/app/assetsapp.css". The rule: when location ends in /, the alias must end in / too.
2. Wrong or doubled root
root is appended with the entire request URI, so a root that already includes the URI prefix doubles it.
location /static/ {
root /var/www/app/static; # BUG: root gets the FULL uri appended
}
GET /static/app.css becomes open() "/var/www/app/static/static/app.css". For a prefix you want stripped, use alias; root is for serving the URI as-is under a base directory.
3. Actual wrong document root
The root points at a directory that does not contain the deployed files at all — a stale path after a deploy moved the release, or a typo.
root /var/www/html; # but the build deployed to /var/www/app/current
Every static request 404s because the tree simply is not there.
4. Broken try_files / missing SPA fallback
A single-page app needs unmatched paths to fall back to index.html. Without it, a deep-link refresh looks for a real file that does not exist.
location / {
try_files $uri $uri/ =404; # deep links 404 on refresh
}
GET /dashboard/settings finds no /var/www/app/dashboard/settings file, so it returns 404 instead of serving the SPA shell.
5. Missing index file
A directory request resolves via index, but the named index file is not present, so NGINX tries to open a file that does not exist.
index index.html;
# but the build outputs index.htm or main.html
Diagnostic Workflow
Step 1: Read the exact path NGINX tried
sudo tail -20 /var/log/nginx/error.log
Copy the quoted path from open() "..." failed. This is ground truth — NGINX built this path from your config. Everything else is comparing it to reality.
Step 2: Check whether that exact path exists
ls -l /var/www/app/staticassets/app.css # the path from the log
ls -l /var/www/app/static/assets/app.css # where you think it is
If the logged path is mangled or doubled, you have a root/alias bug (causes 1 and 2). If the logged path is correct but missing, the file was not deployed there (cause 3).
Step 3: Find the matching location and its root/alias
grep -RnE 'root|alias|index|try_files' /etc/nginx/conf.d/ /etc/nginx/sites-enabled/
Identify the location serving the failing URI and confirm root vs alias is used correctly:
# Correct alias: trailing slashes on BOTH, leftover URI appended cleanly
location /static/ {
alias /var/www/app/assets/;
}
# Correct root: full URI appended under the base
location / {
root /var/www/app/public;
try_files $uri $uri/ /index.html; # SPA fallback
}
Step 4: Validate and reload
sudo nginx -t && sudo systemctl reload nginx
curl -sI https://app.example.com/static/app.css | head -1
Example Root Cause Analysis
After a front-end deploy, every asset under /static/ returns 404 while index.html serves fine.
The error log:
[error] 8420#8420: *91233 open() "/var/www/app/assetsapp.css" failed (2: No such file or directory), request: "GET /static/app.css HTTP/1.1"
The logged path is /var/www/app/assetsapp.css — assets and app.css are fused with no slash. That fingerprint points straight at an alias trailing-slash bug.
Checking the config:
grep -RnE 'location /static|alias' /etc/nginx/conf.d/app.conf
location /static/ {
alias /var/www/app/assets;
}
The location ends in / but the alias does not, so NGINX appends app.css directly to .../assets. Fix by matching the trailing slash:
location /static/ {
alias /var/www/app/assets/;
}
sudo nginx -t && sudo systemctl reload nginx
curl -sI https://app.example.com/static/app.css | head -1 # now 200
The assets serve correctly, and the logged open() path is now /var/www/app/assets/app.css.
Prevention Best Practices
- When a
locationprefix ends in/, make thealiasend in/too — mismatched trailing slashes are the number-one cause of mangled static paths. - Use
aliaswhen you want a prefix stripped androotwhen you want the full URI served under a base directory; do not reach forrootto strip a prefix. - For single-page apps, always add a
try_files $uri $uri/ /index.html;fallback so deep-link refreshes serve the shell instead of 404ing. - After every deploy,
curl -sIone asset and the index as a smoke test so a moved release directory fails the deploy, not the users. - Trust the quoted path in
open() ... failed— it is exactly what NGINX built; diff it against the real filesystem path before touching anything else.
Quick Command Reference
# The exact path NGINX tried to open
sudo tail -20 /var/log/nginx/error.log
# Compare logged path vs reality
ls -l <path-from-log>
ls -l <path-you-expect>
# Find the location's root/alias/try_files
grep -RnE 'root|alias|index|try_files' /etc/nginx/conf.d/ /etc/nginx/sites-enabled/
# Validate, reload, smoke test
sudo nginx -t && sudo systemctl reload nginx
curl -sI https://app.example.com/static/app.css | head -1
Conclusion
open() failed (2: No such file or directory) is NGINX telling you the precise filesystem path it looked for and did not find. The usual root causes:
- An
aliasmissing its trailing slash, mangling the path. - A
rootthat doubles the URI prefix (usealiasto strip it). - A genuinely wrong document
rootafter a deploy or typo. - A missing
try_filesSPA fallback, 404ing deep-link refreshes. - A missing or misnamed
indexfile.
Read the quoted path from the log first, compare it to where the file actually lives, then fix root/alias/try_files accordingly and nginx -t before reloading. Nearly every case is a trailing-slash mismatch or a root vs alias mix-up.
Fixed it? Get 500 NGINX & DevOps AI prompts — free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.
Did this fix your issue?
Get 500 Battle-Tested DevOps AI Prompts — Free
500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.
- 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
- Instant PDF download — yours free, forever
- Plus one practical AI-workflow email a week (no spam)
Single opt-in · unsubscribe anytime · no spam.