NGINX SSL certificate expired: detection and emergency renewal
An expired SSL certificate on NGINX is an immediate outage, not gradual degradation. Browsers and API clients reject the connection at the TLS handshake, often before NGINX logs anything useful. The fix is rarely as simple as running a renewal script again. Verify what is on disk, confirm the running configuration is using it, and force a reload so workers load the new certificate.
NGINX loads certificates at configuration parse time. Workers present whatever the master loaded at startup or during the last reload. Replacing the PEM file on disk does not change what active workers present. Only a reload or restart pulls the new material into memory. If a reload fails, NGINX continues serving with the previous configuration, which still holds the expired certificate. Old workers continue until their connections drain, so even after a successful reload, long-lived connections may briefly present the old certificate.
This guide covers detection through resolution. It assumes server access and the ability to read NGINX configuration and error logs. ACME client configuration is out of scope; during an incident the question is usually why the new certificate did not take effect, not how to generate a CSR.
What this means
After the notAfter date passes, workers still present the certificate they loaded at startup or during the last reload. Clients validate it against their trust store and clock. If it is expired, the TLS handshake fails before an HTTP request is sent. Access logs may remain empty while users experience complete service denial.
OCSP stapling can fail silently when the responder is unreachable; NGINX continues the handshake without the staple. An expired leaf certificate breaks the handshake entirely, as does an expired intermediate in the chain. NGINX does not validate expiry in real time; it serves what was loaded at configuration parse time.
flowchart TD
A[Clients report certificate error] --> B{Cert file on disk expired?}
B -->|Yes| C[Renew or replace certificate]
B -->|No| D{Config references correct file?}
D -->|No| E[Fix ssl_certificate path]
D -->|Yes| F{Did reload occur?}
F -->|No| G[Run nginx -t && nginx -s reload]
F -->|Yes| H{Reload failed?}
H -->|Yes| I[Fix permissions or config syntax]
C --> J[Reload NGINX]
E --> J
G --> K[Verify remote handshake]
I --> K
J --> KCommon causes
| Cause | What it looks like | First thing to check |
|---|---|---|
| Certificate file expired on disk | TLS handshake failures; clients report expired certificate; error log shows SSL handshake errors | openssl x509 -enddate -noout -in against the file referenced in ssl_certificate |
| Renewed certificate not reloaded | File on disk is fresh, but clients still see the old expiry; error log shows no recent reload | Master process start time versus file modification time; whether a reload occurred after renewal |
| Intermediate certificate missing or expired | Chain validation fails on some clients but not others; mobile apps or older browsers reject while desktop accepts | Chain completeness and intermediate expiry dates |
| File permissions or SELinux blocking read | Renewal process writes new files, but NGINX reload fails with permission denied in error.log | Worker process ownership and access rights |
| Wrong certificate file referenced | ssl_certificate points to an old backup or staging file; new cert exists but is unused | nginx -T output to confirm the absolute path |
Quick checks
These commands are read-only and safe to run during an incident.
# Check certificate expiry on disk directly
openssl x509 -enddate -noout -in /path/to/cert.pem
# Display the active ssl_certificate paths from the running config
nginx -T 2>/dev/null | grep -E 'ssl_certificate|ssl_certificate_key'
# Show when the master process started to compare against cert renewal time
ps -o pid,lstart,command -p $(cat /var/run/nginx.pid)
# Search error log for SSL or permission failures
tail -n 200 /var/log/nginx/error.log | grep -E '\[(crit|alert|emerg|error)\].*SSL'
# Validate configuration syntax without applying changes
nginx -t
# Inspect permissions on the certificate file referenced by the active config
ls -l $(nginx -T 2>/dev/null | awk '/ssl_certificate / {print $2; exit}' | tr -d ';')
# Count current worker processes to confirm normal state
pgrep -c -P $(cat /var/run/nginx.pid)
# Check for recent reload or reconfiguration events
grep -E 'reconfiguring|signal 1 \(SIGHUP\)' /var/log/nginx/error.log | tail -5
How to diagnose it
- Confirm the certificate on disk is expired. Use
openssl x509 -enddate -noout -in <path>where<path>is the file referenced byssl_certificate. Do not rely on browser screenshots; verify the file directly. - Confirm the active configuration references that file. Run
nginx -Tand search forssl_certificate. Ensure there is no mismatch between the path you checked and the path NGINX parsed. - Check whether a reload happened after the most recent renewal. Compare the NGINX master process start time against the certificate file modification time. If the master is older than the file but no reload occurred, the new certificate was never loaded. Look for
reconfiguringmessages in the error log. - Read the error log for SSL or permission messages. Look for
[crit]or[error]lines containingSSL_do_handshake() failedor permission denied. These reveal whether NGINX can read the file or if the certificate itself is rejected during handshake. - Validate the certificate chain. If the leaf is valid but clients still reject the connection, inspect intermediate certificates. Their expiry dates must be in the future, and the chain must reach a trusted root. An incomplete chain is a distinct failure mode that mimics an expired certificate on some clients.
- Check for reload failures. A failed reload leaves the previous configuration active. Run
nginx -tto validate syntax. Look inerror.logfor[emerg]lines after the last reload attempt. If the configuration is invalid, NGINX will not apply the new certificate. - Test a remote handshake. From another host, use
openssl s_client -connect host:443 -servername hostto inspect the certificate presented by the running server. If the expiry date shown remotely differs from the file on disk, the active workers are still holding the old certificate in memory.
Metrics and signals to monitor
| Signal | Why it matters | Warning sign |
|---|---|---|
| SSL certificate expiration countdown | An expired certificate is an immediate outage; lead time prevents emergency renewals | Less than 30 days remaining on the leaf certificate |
| Error log SSL handshake failure rate | Reveals active TLS rejection before access logs show HTTP errors | Any sustained [crit] or [error] containing SSL |
| NGINX reload success/failure | A failed reload keeps the old certificate in memory indefinitely | [emerg] or configuration file .* test failed after reload |
| Intermediate certificate expiry | Chain validation fails even when the leaf certificate is fresh | Intermediate notAfter within the monitoring window |
Fixes
The certificate file is expired and no valid replacement exists
Generate or procure a certificate and private key. Place them in the paths referenced by ssl_certificate and ssl_certificate_key. Run nginx -t to validate syntax and readability, then nginx -s reload to spawn new workers. Avoid a full restart unless the reload fails; a restart drops active connections, while a reload performs a graceful handover.
The renewed certificate is on disk but NGINX still serves the old one
This is the most common post-renewal failure. NGINX loads certificates at parse time, not per request. After confirming the file is updated, run nginx -t && nginx -s reload. The test validates syntax; the reload spawns new workers that load the new certificate. Old workers continue until their connections drain. Watch the error log for [notice] lines indicating successful reconfiguration. If you see [emerg], the reload failed and the previous certificate remains active.
Reload fails due to configuration or permission errors
If nginx -t reports errors, fix them before reloading. If the error log shows Permission denied on the certificate path after a reload attempt, verify the worker process owner can read the file. SELinux or AppArmor can block reads even when standard Unix permissions appear correct. The failure appears as a permission error in the log.
Clients still reject after a successful reload
Check the full chain. If ssl_certificate contains only the leaf, some clients cannot build a trust path. Ensure intermediates are present and valid. Verify the private key matches the certificate. If OCSP stapling is configured, remember that stapling failure alone does not break the handshake; do not attribute an expiry outage to OCSP.
Emergency bypass if renewal is delayed
If the certificate cannot be renewed immediately, two last-resort options exist. Redirect HTTPS traffic to HTTP at the load balancer or DNS level, accepting plaintext exposure. Alternatively, load a temporary self-signed certificate and instruct internal clients to trust it. Both are operational risks. Use them only while the real renewal proceeds.
Prevention
- Monitor expiration independently of the renewal pipeline. Alert at 30, 14, 7, 3, and 1 day before expiry. The renewal script may fail silently; monitoring the file directly is the only way to catch that.
- Require a reload as part of every renewal workflow. Updating the PEM file without reloading NGINX leaves the old certificate in memory. Automate the reload and verify it succeeds by checking the error log for reconfiguration lines rather than
[emerg]. - Validate the full chain after every renewal. Include intermediates in the
ssl_certificatefile and confirm their expiry dates are healthy. An expired intermediate breaks the chain just as effectively as an expired leaf. - Test reloads in a staging environment before production. A syntax error in an updated configuration can cause a reload to fail, leaving the expired certificate active indefinitely.
- Keep certificate files in a path the worker can read after renewal. If the renewal process changes ownership or security contexts, ensure the NGINX worker retains read access. A common failure is a renewal script writing files owned by root with mode
0600. - Log
$ssl_protocoland$ssl_cipherin access logs. While not directly an expiry signal, TLS anomalies often correlate with certificate changes and client compatibility issues.
How Netdata helps
- Error log rate spikes and worker process counts reveal when a reload has failed or workers are stuck with old certificates.
- Correlating error log severity with configuration change events pinpoints whether a certificate update triggered a reload failure.
- Process count and connection metrics reveal if the worker pool failed to rotate after a reload.
- Request latency and 5xx rate monitoring detects the client-facing impact of TLS handshake failures.
- Access log integration captures TLS version distribution shifts that accompany certificate or chain changes.
Related guides
- How NGINX actually works in production: a mental model for operators
- nginx 413 Request Entity Too Large: client_max_body_size explained
- nginx 499 status code: why clients close connections before the response
- nginx 500 Internal Server Error: how to diagnose it
- nginx 502 Bad Gateway: causes and how to fix it
- nginx 503 Service Temporarily Unavailable: causes and fixes
- nginx 504 Gateway Time-out: causes and fixes
- NGINX active connections climbing: reading, writing, waiting explained
- NGINX backend cascade failure: when slow upstreams take down everything
- nginx: a client request body is buffered to a temporary file - what it means
- nginx connect() failed (111: Connection refused) while connecting to upstream
- NGINX connection exhaustion: detection, diagnosis, and prevention







