nginx -s reload said OK, but my change didn't apply

Updated 4 November 2026 · By Brent Alexis

Short answer: when an Nginx reload isn't applying your changes, either Nginx rejected the new config and kept running the old one, or it isn't reading the file you edited. nginx -s reload catches syntax errors, but some problems only show up while Nginx loads the new config. Then the command still reports success, Nginx keeps the old config, and the reason goes to the error log. Check the error log right after every reload.

Every cause on this page was reproduced on the official nginx Docker image (Nginx 1.30.6), Debian 12's package (Nginx 1.22.1) and AlmaLinux 9's package (Nginx 1.20.1), with the exit code, the live page, the worker processes and the error log checked each time: 95 checks. How it's tested

What a reload actually does

nginx -s reload first checks the config's syntax. If that passes, it sends a signal to the running Nginx master process and exits with 0. Only then does the master load the new config: it sets up shared memory zones, opens any new ports, starts new worker processes and tells the old ones to finish their requests. If any of those steps fails, it logs an [emerg] line and keeps the old workers running with the old config, but the command that sent the signal has already reported success.

What systemctl reload shows depends on how your distro's package wrote it:

Packagesystemctl reload runs
Debian 12nginx -g 'daemon on; master_process on;' -s reload
AlmaLinux 9 (RHEL family)nginx -s reload
nginx.org packages (also in the official Docker image)/bin/kill -s HUP $(/bin/cat /run/nginx.pid)

Where the unit runs nginx -s reload, a syntax error stops the reload with an error, because that command checks the syntax first and exits with 1. With the nginx.org package, systemctl reload only sends the signal, so it reports success even when the file is broken. On every package, problems Nginx only finds while loading the new config, like causes 2 and 3 below, are never reported by the reload command. (These ExecReload lines come from each package's systemd unit; the lab ran the same commands directly, since containers don't run systemd.)

Check that a reload worked

sudo nginx -t && sudo systemctl reload nginx
sudo tail -n 5 /var/log/nginx/error.log
ps -o pid,lstart,cmd -C nginx

Look for a new [emerg] or [alert] line with the current time. Then look at the worker processes: after a reload that worked, the nginx: worker process lines have new PIDs and a start time of a moment ago. If they still show the old start time, Nginx kept the old config. In the lab, the worker PIDs changed after every reload that worked and stayed the same after every one that didn't, on all three platforms.

In the official Docker image, /var/log/nginx/error.log points to the container's output, so read it with docker logs instead.

1. A syntax error you didn't test

A typo, a missing ; or a directive your version doesn't know. nginx -t catches all of these, which is why every ProxyForge config ends with sudo nginx -t && sudo systemctl reload nginx: the reload only runs if the test passes.

nginx -s reload does catch these: on all three platforms it checked the syntax first, printed the error below and exited with 1, and the old config stayed live. The trap is the other way of reloading. A plain kill -HUP, which is what systemctl reload runs with the nginx.org package, exited 0, the old config stayed live, and the reason only went to the error log.

nginx: [emerg] unknown directive "bogus_directive" in /etc/nginx/conf.d/example.com.conf:4

Fix: run sudo nginx -t, fix the file and line it names, then reload.

2. A changed limit_req_zone key

This one passes nginx -t. If you change the key of an existing rate-limit zone, for example from $binary_remote_addr to $server_name, and keep the zone's name, the reload exits 0 and nothing changes. The error log says:

[emerg] 46#46: limit_req "one" uses the "$server_name" key while previously it used the "$binary_remote_addr" key

Fix: restart instead (sudo nginx -t && sudo systemctl restart nginx), or give the zone a new name and update every limit_req zone= line that uses it. A restart briefly drops connections; a rename doesn't. Checked on all three platforms: nginx -t passed, the reload exited 0, the old config stayed live and the worker PIDs didn't change. A restart applied it, and so did a reload after renaming the zone.

3. A new port that's already in use

Adding a listen line for a port another program already holds (Apache, another Nginx, a Docker container) also passes nginx -t, because the test doesn't try to open ports. The reload exits 0, and the error log says:

[emerg] 452#452: bind() to 0.0.0.0:8081 failed (98: Address already in use)

Fix: find what owns the port with sudo ss -tlnp | grep :8081 (use your port), move one of them, then reload. See Address already in use for the common culprits. Checked on all three platforms with a second Nginx holding port 8081: nginx -t passed, the reload exited 0 and the old config stayed live. The same reload applied as soon as the port was free.

4. You edited a file Nginx doesn't load

The reload works perfectly, just not with your file. Common reasons:

Check: sudo nginx -T prints every config file Nginx would load, each one headed by its path. If your change isn't in that output, Nginx isn't reading that file:

sudo nginx -T 2>/dev/null | grep -n "# configuration file"

Note that nginx -T reads the files on disk now, not what the running Nginx loaded, so use it to see which files count and the error log to see whether the reload worked. Checked with a .conf.bak file in conf.d and an unlinked file in sites-available: the reload exited 0, the change never went live, and nginx -T didn't include it.

5. Nginx isn't running

A reload needs a running Nginx to signal. If Nginx stopped, crashed or never started, nginx -s reload fails with:

nginx: [error] open() "/run/nginx.pid" failed (2: No such file or directory)

Fix: sudo nginx -t && sudo systemctl start nginx, then sudo systemctl status nginx to see why it had stopped. If it won't start, the error it prints names the reason, for example a port in use or a missing certificate file.

Still seeing the old page?

If the error log is clean and the workers restarted, the new config is live, and the old page is coming from somewhere else:

Generate a config with the test-and-reload steps built in →

How to use ProxyForge · FAQ: common errors · Guides · What's new · About