Kingsfordweg 151, 1043GR Amsterdam, The Netherlands ● 24/7 support — support@novogara.com
Knowledge base

nginx pitfalls

nginx does exactly what you wrote. Most of the time the surprise is that what you wrote is not what you meant.

Location matching is not top to bottom

nginx picks a location by precedence, not by order in the file:

  1. location = /path — exact match, wins immediately
  2. location ^~ /path — prefix match that stops regex checking
  3. location ~ /regex and ~* — regexes, in file order, first match wins
  4. location /path — longest matching prefix, used only if no regex matched

This is why a regex block for \.(css|js)$ silently steals requests from a prefix block you thought was handling a directory. If a prefix block must win, write it as ^~.

root versus alias

root appends the whole request path to the value. alias replaces the matched part of it.

location /img/ { root /var/www; } serves /var/www/img/x.png

location /img/ { alias /var/data/; } serves /var/data/x.png

With alias, keep the trailing slash on both sides or you get path traversal surprises. If you can express it with root, do.

add_header does not inherit

The moment a block contains one add_header, it stops inheriting all headers from its parent. Your security headers set at server level vanish inside that location, which nobody notices until a scan reports it.

Put the headers in a snippet and include it in every block that sets any header of its own.

if is evil, mostly

if inside a location is only reliably safe with return and rewrite ... last. Anything else — especially setting variables or adding headers inside it — behaves in ways that look like bugs. Use map, try_files or a separate location instead.

Proxying: four headers and a trap

Behind a proxy, your application sees the proxy unless you tell it otherwise:

proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

And only trust those headers from your own proxy. An application that believes X-Forwarded-For from anyone has no working rate limiting and no reliable logs. See load balancing.

The trap: a trailing slash on proxy_pass changes the meaning. proxy_pass http://app; passes the full URI; proxy_pass http://app/; strips the matched location prefix.

Your change did not take effect

  • Did you reload? nginx -t && systemctl reload nginx
  • Old workers finish their connections after a reload. Wait a few seconds before concluding anything.
  • Is the file actually included? nginx -T prints the complete configuration as nginx sees it. That one command settles most arguments.
  • Is something caching? Your browser, a CDN, or proxy_cache.

Still stuck? Mail support@novogara.com — an engineer answers, at any hour. Back to the knowledge base