skip to content

You edit a file under /etc/nginx/conf.d and run `nginx -s reload`, but nginx keeps behaving the old way. How do you determine which configuration nginx is actually running, and what are the usual reasons an edit does not take effect?

level: seniorimportance: must knowfreq 50%

answer

  1. prove what is loaded, not what you edited
  2. dump the resolved config first
  3. a failed reload keeps the old config
  4. include mask may skip your filename
  5. old workers drain their connections

basics

~20 s

Dump the running configuration with nginx -T, which prints the whole tree with every include resolved. Typical causes are a file the include mask never matched, a failed reload that left the old config live, an override in a deeper context, or old workers still finishing existing connections.

solid answer

~60 s

Start by proving what is loaded rather than what you edited: `nginx -T` validates and prints the full configuration with all `include` files expanded, so you can see whether your file is in the tree and which block your directive landed in. Then work through the usual causes. The include mask may not match the filename — `conf.d/*.conf` skips `site.conf.bak` or a dangling `sites-enabled` symlink. The reload may have failed: nginx validates the new config first, and on error it logs it and keeps serving the previous one, so a syntax error looks like "nothing happened". The directive may be legal but overridden by a deeper context, or sit in a `server`/`location` that this request never selects. Reload is graceful, so old workers keep serving their existing connections — including keep-alive ones — until they finish or `worker_shutdown_timeout` cuts them off. Finally, confirm you reloaded the right instance: check the master process's command line, since a different `-c` or prefix, or a container with its own copy of the file, means you edited a config nobody reads.

code

bash · 12 lines
bash
# 1. is the new configuration valid at all?
nginx -t

# 2. which files were actually parsed, and where did the directive land?
nginx -T | grep -n '# configuration file'
nginx -T | grep -n -A2 -B2 'proxy_read_timeout'

# 3. which binary and config path is the running master using?
ps -o args= -p "$(cat /var/run/nginx.pid)"

# 4. are old workers still draining after the reload?
ps ax | grep 'nginx: worker process is shutting down'

go deeper

for a junior

Know that nginx -t checks syntax, nginx -T prints the whole configuration with includes expanded, and that a reload with a broken config leaves the old one running.

for a middle

Explain the include mask and symlink traps, sorted wildcard order, and why a directive can be valid, loaded, and still not apply because a deeper context or a different server block wins.

for a senior

Walk a clean diagnosis: validate, dump the effective config, read the error log around the reload, check the master's command line and lingering workers, then reproduce against the origin with curl.

for a principal

Own the change process so this is not diagnosed repeatedly: config in version control, rendered and nginx -t-gated in CI, deployed by script, and verified after reload by an observable check rather than by the reload's exit code.

## First, stop guessing which config is live The file you edited is not necessarily the configuration nginx parsed. `nginx -t` only tells you the config is syntactically valid; `nginx -T` (available since nginx 1.9.2) does the same test and then prints the complete resolved configuration to stdout, every `include` expanded inline. That output is the ground truth, and almost every instance of this problem is visible in it within a minute: ```bash nginx -T | grep -n "proxy_read_timeout\|# configuration file" ``` The `# configuration file /path` markers show exactly which files were pulled in and in what order, so you can see immediately whether your file is one of them. One caveat: `nginx -t`/`-T` parse the configuration at the default prefix and path unless you pass `-c`/`-p`. If the running master was started with different options, you may be validating a different file than the one in memory. `ps -o args= -p $(cat /var/run/nginx.pid)` shows the master's actual command line. ## The usual causes, in the order worth checking **The file is not included.** Distribution configs include `/etc/nginx/conf.d/*.conf`, and on Debian-family packages also `/etc/nginx/sites-enabled/*`. A file saved as `site.conf.bak`, `site.conf~` or `site.txt` matches no mask. A `sites-enabled` entry is a symlink, and a broken or never-created symlink means the vhost in `sites-available` is decoration. Wildcard includes are expanded in sorted order, so a file named `00-defaults.conf` is parsed before `zz-site.conf` — order matters where duplicate `server` blocks or first-wins directives are involved. **The reload failed and nginx kept the old configuration.** On SIGHUP the master re-reads and validates the config before applying it. If validation fails, it logs the error and continues running the previous configuration — deliberately, so a bad edit cannot take a site down. From the client side that is indistinguishable from "my change did nothing". Always read the error log after a reload, or reload only after `nginx -t` passes. **The directive is in a context that does not apply.** It may be overridden by a declaration in a deeper block, or it may sit in a `server` or `location` that this request never selects. It may also be in the wrong file entirely — two vhosts listening on the same port where the other one is chosen. `nginx -T` plus the request's own access-log entry narrows this quickly. **Old workers are still serving old configuration.** A reload is graceful by design: the master starts workers with the new configuration and asks the old ones to shut down, but the old workers keep handling requests already in flight and connections already established, including idle keep-alive connections. Until those close, some clients legitimately get the old behaviour. `worker_shutdown_timeout` (since nginx 1.11.11) puts a ceiling on that grace period; without it, a long-lived connection can hold an old worker for a long time. Seeing several `nginx: worker process is shutting down` entries in `ps` long after a reload is the symptom. **You reloaded a different nginx.** Two installations (a package and a hand-built binary), a container that baked the config into its image rather than mounting it, or a config edited on the host while nginx runs in a pod — all produce a perfectly successful reload of a configuration you did not change. **Something in front is answering.** A CDN, a cache, or nginx's own response cache can serve the previous behaviour after the config is correct. Test the origin directly before concluding the config is at fault. ## A dependable procedure 1. `nginx -t` — is the new config even valid? 2. `nginx -T | less` — is my file present, and where did my directive land? 3. Read the error log around the reload timestamp. 4. `ps` for the master's command line and for lingering shutting-down workers. 5. Reproduce with `curl` against the origin, on the exact host and path that selects the block you edited. The pattern to internalise: nginx fails safe on reload and fails silent on misplacement. Neither will tell you it happened, so make reading the effective config the first step rather than the last.

  • What exactly happens between `nginx -s reload` and the new configuration serving traffic?
    The master receives SIGHUP, re-reads and validates the configuration, and on success starts new worker processes with it and begins accepting new connections there. It then signals the old workers to stop accepting and shut down once their current connections finish. On validation failure it logs the error and leaves the running configuration untouched, so the site keeps serving.
  • Why is `nginx -t` on its own not enough to prove a change will apply?
    `-t` answers only "does this parse". It says nothing about whether your file is inside the include tree, whether a deeper context overrides the directive, or whether the request you are testing even selects that block. It can also validate a different file than the running master loaded if the master was started with a different `-c` or prefix.
  • How would you make this class of problem rare rather than recurring?
    Keep the config in version control, render it in CI, and gate merges on `nginx -t` inside an image matching production. Deploy by writing files and reloading in one scripted step so nothing is hand-edited on a host, and have the deploy assert an observable behaviour afterwards — a curl against a canary path — rather than trusting that a successful reload means the intended change is live.

saying these in an interview costs you the question

  • Assumes a successful `nginx -s reload` proves the change applied
  • Uses `nginx -t` and never inspects the resolved config
  • Thinks a bad config makes nginx exit rather than keep the old one
  • Restarts nginx blindly instead of finding which file was loaded
  • Forgets old workers keep serving established keep-alive connections

context