[status_page_exposed] stub_status exposed without IP restrictions
What this check looks for
This plugin flags stub_status endpoints that do not implement an IP whitelist using allow plus a final deny all.
It only reports findings when the enclosing server is reachable over TCP (i.e., it is not a server that listens exclusively on unix: sockets).
Why this is a problem
stub_status reveals operational details such as active connections and request handling state. While it does not expose application data directly, it is valuable for reconnaissance:
- confirms the server is NGINX and that a status endpoint exists,
- provides traffic and connection signals that help time attacks,
- can reveal load patterns and availability.
If the endpoint is publicly reachable, anyone can query it.
Bad configuration
stub_status enabled with no IP restrictions:
server {
listen 80;
server_name example.com;
location /nginx_status {
stub_status;
}
}
Partial restrictions are also flagged. For example, a whitelist without deny all:
location /nginx_status {
stub_status;
allow 10.0.0.0/8;
# missing: deny all;
}
Or deny all without an explicit whitelist:
location /nginx_status {
stub_status;
# missing: allow <trusted ranges>;
deny all;
}
Better configuration
Restrict access to trusted IP ranges and end with a catch-all deny:
server {
listen 80;
server_name example.com;
location /nginx_status {
stub_status;
allow 10.0.0.0/8;
allow 192.168.0.0/16;
allow 203.0.113.10; # monitoring host
deny all;
}
}
Additional notes
- This plugin treats an
allowcovering every address the enclosingservercan accept as not a whitelist, and does not count it as a restriction.allow allalways qualifies.0.0.0.0/0and::/0are per-family:ngx_http_access_rulefiles the first under the IPv4 rule list and the second under the IPv6 one, andngx_http_access_handlerconsults only the list matching the client's address family. Soallow ::/0covers every client of an IPv6-capable server but nobody on an IPv4-only one, andallow 0.0.0.0/0the other way around. The same applies todeny. allow/denyare read in order, as NGINX evaluates them: the first matching rule decides the request, so anything written after anallow allis dead.allow 10.0.0.0/8; allow all; deny all;is reported, because everyone the/8did not cover is let in by theallow alland thedeny allnever applies.allow/denyare resolved with NGINX inheritance: rules set on an enclosingserver/httpscope protect astub_statuslocation that declares none of its own. (A location that sets its ownallow/denydoes not inherit the parent's, matchingngx_http_access_module, so in that case it must repeat them.)- An endpoint protected by an auth module (
auth_basic,auth_request,auth_jwt, orauth_oidc) is not reported, with one exception: undersatisfy any, anallowcovering every address makes the access module succeed on its own and NGINX then skips the rest of the access phase, so the authentication never runs.satisfy any; allow all;alongsideauth_basicis therefore reported as exposed. The intendedtrusted network OR passwordshape —satisfy any; allow 10.0.0.0/8; deny all;withauth_basic— is not. -
The address families are read off the
server'slistendirectives:listenspellingServer accepts none (implicit *:80)IPv4 listen 80;,listen *:80;IPv4 listen 127.0.0.1:80;IPv4 listen [::]:80;IPv6 only, because ipv6onlydefaults toonlisten [::]:80 ipv6only=off;IPv4 and IPv6, as IPv4-mapped addresses listen example.com:80;assumed both; resolution at startup decides -
Servers that listen only on
unix:sockets are ignored by this check, since they are not reachable over the network. - Prefer keeping the endpoint unadvertised (non-obvious path) in addition to access control, but do not rely on obscurity alone.