The nginx alias path traversal
This configuration serves the directory above your static files to anyone who asks:
location /static { alias /var/www/site/static/;}The bug is the missing slash after /static. That’s it. It is valid config, it
passes nginx -t, it serves your assets correctly, and it has been the single
most common serious nginx misconfiguration for over a decade.
Why it happens
Section titled “Why it happens”alias replaces the matched location prefix with a filesystem path. nginx
strips the prefix as a string operation — it is not path-aware and does not
care about directory boundaries.
Walk a request through it:
Request: GET /static../Location: /static matches (prefix match on the string)Strip: "/static" leaves the remainder "../"alias: /var/www/site/static/ + ../Result: /var/www/site/static/../ → /var/www/site/The remainder ../ is concatenated onto the alias path and resolved by the
filesystem, landing one directory above the static root.
The trick that makes it work is that /static../ is a single path segment
called static.. — not a .. traversal segment. So it survives nginx’s URI
normalisation untouched, and only becomes a ../ after the prefix is stripped,
at which point it’s too late.
One level up doesn’t sound like much. Consider what’s usually there:
/var/www/site/ holds your .env, your config.php, your .git directory, your
source, and your database credentials. GET /static../.env reads it.
The fix is one character
Section titled “The fix is one character”Make the slashes agree:
location /static/ { alias /var/www/site/static/;}Now /static../ does not match location /static/ at all — the segment
static.. isn’t static/ — so it falls through to another location or a 404.
Better: use root instead.
location /static/ { root /var/www/site; # serves /var/www/site/static/...}root appends the full URI to the path rather than replacing a prefix. There
is no strip step, so there is no remainder to escape with. The class of bug does
not exist.
The rule of thumb: reach for alias only when the URL path and the filesystem
path genuinely differ. When they’re the same — which is most of the time — root
is both simpler and immune.
Where to look for it
Section titled “Where to look for it”The pattern to hunt is any location without a trailing slash that contains an
alias. It is disproportionately common in configs for /static, /assets,
/media, /uploads, /files and /downloads — because those are exactly the
paths people alias to a directory outside the web root.
Related
Section titled “Related”- Hardening nginx — the full checklist.
- Run workers as an unprivileged user — bounds what a path bug can read.