Replace an SSL Certificate on NGINX
Replace the SSL certificate on NGINX: build and check the full-chain PEM, install it with a rollback path, test with nginx -t, reload without dropping connections, also in Docker.
NGINX reads a PEM certificate chain and a private key from the paths in
ssl_certificate and ssl_certificate_key. Replacing the files is not enough: the
configuration must pass nginx -t, and the master process must reload it.
Before you begin
-
You need three PEM files from your CA (names vary): the server certificate (here
leaf.pem), the intermediate certificate(s) (intermediates.pem) and the private key (private.key, unencrypted). Do not include the root CA. -
Find every certificate NGINX actually uses -
nginx -Tprints the complete loaded configuration, not justsites-enabled:sudo nginx -T | grep -E 'server_name|ssl_certificate' -
Keep the current files: the procedure below installs the new ones into a new directory, so the old ones stay in place for a rollback.
-
With several servers behind a load balancer, take one out, update it, put it back.
Step-by-step certificate replacement
Run the commands as root (or with sudo).
-
Build the full chain - server certificate first - and check it:
cat leaf.pem intermediates.pem > fullchain.pem grep -c 'BEGIN CERTIFICATE' fullchain.pem openssl x509 -in fullchain.pem -noout -subject -issuer -dates -ext subjectAltName openssl verify -untrusted intermediates.pem leaf.pem openssl pkey -in private.key -pubout | sha256sum openssl x509 -in leaf.pem -noout -pubkey | sha256sumThe count must equal the number of certificates you concatenated - if a file had no final newline, two certificates merge into one broken block.
openssl verifymust printleaf.pem: OK(it uses the system's trusted roots). The twosha256sumvalues must be identical; otherwise key and certificate do not belong together. -
Install into a new, versioned directory and switch a symlink to it:
install -d -m 0700 /etc/nginx/tls/example-2026 install -m 0644 fullchain.pem /etc/nginx/tls/example-2026/fullchain.pem install -m 0600 private.key /etc/nginx/tls/example-2026/private.key ln -sfn /etc/nginx/tls/example-2026 /etc/nginx/tls/example-currentThe NGINX master process runs as root and reads the key before starting the workers, so a root-only key (
0600) is correct. If you run NGINX entirely as a non-root user, that user must be able to read the key instead. -
Make sure the server block points at the symlink:
ssl_certificate /etc/nginx/tls/example-current/fullchain.pem; ssl_certificate_key /etc/nginx/tls/example-current/private.key; -
Test, then reload:
nginx -t && systemctl reload nginx journalctl -u nginx --since "5 minutes ago"If
nginx -tfails, nothing has changed for the running server - point the symlink back and investigate. A reload drops no connections: NGINX starts new workers with the new certificate, and the old workers finish their open connections (long-lived ones such as WebSockets can keep an old worker alive for a while). Check the journal or/var/log/nginx/error.log-systemctl is-activealone does not show whether the reload worked.
NGINX in a container
-
Mount the certificate directory into the container, not single files. Replacing a file on the host usually creates a new file (new inode), and a single-file bind mount keeps showing the old one.
-
Test and reload inside the container:
docker exec <container> nginx -t docker exec <container> nginx -s reload -
See also the Docker container guide.
Product-specific considerations
Key mismatch: nginx -t reporting SSL_CTX_use_PrivateKey ... key values mismatch
means the first certificate in the chain file does not belong to the key. Either the
wrong key was installed, or the chain file does not start with the server certificate.
Compare the two hashes from step 1 and check the order.
SNI makes the tested hostname matter: several server blocks can use different
certificates on the same address. SELinux or AppArmor can block access to a new path
even when the file permissions look correct - on SELinux systems run restorecon -Rv /etc/nginx/tls after creating new files.
Verify the new certificate
openssl s_client -connect www.example.com:443 -servername www.example.com -showcerts </dev/null
openssl x509 -in leaf.pem -noout -fingerprint -sha256Check subject and SAN, issuer, expiry date and that the intermediate is sent, and
compare the served certificate's fingerprint with the one of leaf.pem. Test through
every load balancer, too.
Rollback
Point the symlink back to the previous directory, test and reload:
ln -sfn /etc/nginx/tls/example-2025 /etc/nginx/tls/example-current
nginx -t && systemctl reload nginxThen check the served certificate again. This requires the old directory to still exist - delete it only after the new certificate has been verified.
Common problems
- Key and certificate do not belong together.
- Intermediates are missing, or come before the server certificate.
- Two certificates merged because a file had no final newline.
- NGINX was reloaded without
nginx -t. - The test request was sent without SNI and hit the default server.
- In a container, a single-file bind mount still shows the old file.
Automating certificate replacement with aethercert
The aethercert NGINX target writes the certificate chain and private key, applies private-key ownership/mode handling based on the NGINX configuration, and runs the configured reload command.
See Deploy targets for the fields this preset takes, what it needs on the host, and how far it has been verified.