· via dev.to (home feed)
nginx 1.31.5 control API exposes worker PIDs and config swaps without auth
A dev.to hands-on test found nginx's new control API serves live worker PIDs and full config swaps to any bare curl reaching its TCP port, because the feature has no built-in authentication.

A hands-on write-up on dev.to has documented exactly how far a bare curl gets against the control API nginx added to its open-source build in version 1.31.5. The feature ships with no authentication of its own, and with the listener bound to a plain TCP port, an uncredentialed request sent from outside the container returned the live process IDs of both workers. A second request, still carrying no credentials, replaced the running configuration outright.
The post dates the feature to 2 September 2026 and tests it on nginx 1.31.8, the mainline release at the time of writing, which carries the capability forward unchanged. It is turned on with a command-line flag rather than a config directive, and the official nginx Docker image already compiles the support in, so no custom build is needed.
An HTTP door into the master process
The API replaces the traditional kill -HUP or nginx -s reload routine with HTTP calls. Probing the endpoint tree with successive GET requests, the author mapped a small surface: /1/control/config returns the live configuration file as JSON with its name and full content, /1/control/processes reports worker process state, and a PATCH with no request body makes nginx re-read the config file from disk and reload.
The main draw is error reporting. After breaking the config on purpose with a typo, the author reloaded both ways: the signal route produced an exit code plus a line that exists only in the error log, while the API answered with HTTP 422 and the precise parser error inside a JSON array in the response body. Both paths rejected the bad config and kept the old one serving, which the author verified. The difference is that a deploy script receives a structured answer rather than scraping logs or hoping an exit code tells the whole story.
Not faster, and no harder on live traffic
Benchmarks undercut the assumption that skipping the signal would at least be quicker. Across five reloads each, alternating between two valid configs so every run was a genuine reload, the signal route measured 78–100 ms against 90–102 ms for the API route. The author caveats that these figures include docker exec overhead, so they compare the two methods rather than measure true reload latency — and by that comparison, the older path was if anything slightly faster.
Under concurrency, neither method disturbed in-flight requests. A proxied request to a backend that sleeps three seconds was reloaded one second in, three trials per method, and every attempt finished with HTTP 200 in about 3.004 s, indistinguishable from a reload-free baseline of 3.012 s. The author checked that the reloads genuinely overlapped the requests — old workers showed as exiting while new PIDs appeared — rather than landing before or after them. Graceful worker shutdown applies the same way regardless of which door the reload comes through.
What an open port actually exposes
According to the post, nginx's own release notes advise strongly against binding the socket to a network port and state that the API carries no authentication of its own. The author wanted the exact shape of that risk, not just the warning. Running the official image with the listener on 0.0.0.0:9000 and the port published, two requests from the host — no Authorization header, no API key, nothing but a URL — read live process state and executed a config swap that immediately changed the content being served. There is no login, no token and no IP allowlist built into the feature; anyone able to reach the port can inspect worker state and force a full configuration replacement.
The documented mitigation is to bind to a unix socket and lean on filesystem permissions, and the author verified it holds: the default socket came up as srw------- owned by root, and a connection attempt as an unprivileged user failed with a connection error before nginx saw anything. Over a network interface, by contrast, the feature blocks nothing.
There is also a standing cost to leaving the listener on. With the socket open and idle, the master process's resident memory measured 9,468 KB against 9,164 KB without it — roughly 304 KB, or 3.3 percent, simply to hold the listener open. Too small to show up in monitoring, but a real, non-zero cost for a flag an operator might flip once and forget.
Why it matters
This is a textbook operational footgun: a convenience API whose blast radius is the entire server configuration, gated by nothing but network reachability. nginx fronts traffic across a huge share of deployments, and an unauthenticated config swap on a reverse proxy can redirect requests, repoint upstreams or expose internal topology through the config dump endpoint. Container setups amplify the risk — the feature ships compiled into the official image, and the distance between a safe unix socket and a published TCP port is a single flag. The encouraging half is that the safe configuration is documented, verified and cheap: bind to the filesystem and let permissions do the work. The API itself is worth keeping, because a parseable 422 beats log scraping for deployment automation. The takeaway is to hold both halves at once: keep the feature, and never point it at a network interface.
- #nginx
- #web-servers
- #devops
- #security
- #docker