Most nginx misconfigurations announce themselves.
You typo a directive, fails, you fix it.
That feedback loop is fast and it works.
The dangerous ones are different.
The config is valid. passes.
The server starts, serves traffic, logs nothing unusual.
And the thing you configured is quietly not happening.
I maintain gixy-ng, a static analyzer for nginx configs.
A growing share of its checks exist for exactly this category, because it turns out static analysis is the only practical way to catch a failure that produces no signal at runtime.
Here are four worth knowing about.
1.
OCSP stapling that staples nothing Looks right.
It does nothing.
OCSP stapling means nginx fetches the certificate's revocation status from the CA itself and attaches it to the handshake, so the client does not have to.
To do that, nginx has to make an outbound request to a hostname. nginx does not use the system resolver for runtime lookups.
It has its own, and it only exists if you configure it.
No in scope means the hostname never resolves, the fetch never happens, and stapling is silently skipped.
Your config test passes.
Your clients go do their own OCSP lookups, which is the exact thing you turned stapling on to avoid.
Use a local resolver or your cloud provider's internal DNS.
Pointing this at sends every internal lookup off your network in cleartext, which is its own problem.
Check it with: Working stapling prints .
Broken stapling prints .
Run it twice, since the first handshake after a reload usually goes out unstapled while the fetch happens in the background.
One caveat that catches people right now: Let's Encrypt stopped serving OCSP in August
2025.
If your cert is from them, the fix is to remove , not to add a resolver.
2.
An allow list that allows everyone checks rules in order and stops at the first match.
If nothing matches, access is granted.
So a request from an arbitrary internet address matches neither rule, falls off the end of the list, and gets served.
The intent is obvious to a human reading it and completely invisible to nginx.
Add after the allows and it works.
The subtler version bites harder.
Access rules are inherited from an outer context only when the inner context defines none of its own.
So a single inside a location discards the entire server-level rule set for that location, including its : You added a rule to tighten access and made the endpoint public.
- return answering before your access rules run Everyone on the internet gets .
The access list is correct, complete, and never consulted. nginx processes requests in ordered phases. lives in the rewrite phase. and live in the access phase.
Rewrite runs first, terminates the request immediately, and the access phase never happens.
Position in the file is irrelevant.
Moving below changes nothing, because nginx is not reading your block top to bottom at request time.
This is the same root cause as "if is evil": directives from different modules run in different phases, in an order that has nothing to do with how you wrote the file.
The fix is to reach the canned response through an internal redirect, so the access phase gets a chance to run: runs in the content phase, after access has been evaluated, so a refused client gets its 403 before the internal redirect is considered.
The lines are there because a bare pays a filesystem lookup per candidate on every request, which gixy also flags.
Fixing one finding by creating another is not a fix.
4.
QUIC connections dying on every reload This one is my favourite, in the way that a really good bug is a favourite.
Three ingredients: , on a QUIC listener, and more than one worker.
Any one alone is fine.
All three together, and after every roughly half your QUIC connections are silently dropped. exists for a good reason.
QUIC connections survive an IP or port change by connection ID rather than by 4-tuple, so a migrated packet can land on the wrong worker. nginx attaches an eBPF program to the socket group that reads the connectio