Skip to content

The nginx alias path traversal

Severity: criticalApplies to: nginx (all versions)Applies to: freenginx

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.

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.

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.

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.