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 --> K

Common causes

CauseWhat it looks likeFirst thing to check
Certificate file expired on diskTLS handshake failures; clients report expired certificate; error log shows SSL handshake errorsopenssl x509 -enddate -noout -in against the file referenced in ssl_certificate
Renewed certificate not reloadedFile on disk is fresh, but clients still see the old expiry; error log shows no recent reloadMaster process start time versus file modification time; whether a reload occurred after renewal
Intermediate certificate missing or expiredChain validation fails on some clients but not others; mobile apps or older browsers reject while desktop acceptsChain completeness and intermediate expiry dates
File permissions or SELinux blocking readRenewal process writes new files, but NGINX reload fails with permission denied in error.logWorker process ownership and access rights
Wrong certificate file referencedssl_certificate points to an old backup or staging file; new cert exists but is unusednginx -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

  1. Confirm the certificate on disk is expired. Use openssl x509 -enddate -noout -in <path> where <path> is the file referenced by ssl_certificate. Do not rely on browser screenshots; verify the file directly.
  2. Confirm the active configuration references that file. Run nginx -T and search for ssl_certificate. Ensure there is no mismatch between the path you checked and the path NGINX parsed.
  3. 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 reconfiguring messages in the error log.
  4. Read the error log for SSL or permission messages. Look for [crit] or [error] lines containing SSL_do_handshake() failed or permission denied. These reveal whether NGINX can read the file or if the certificate itself is rejected during handshake.
  5. 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.
  6. Check for reload failures. A failed reload leaves the previous configuration active. Run nginx -t to validate syntax. Look in error.log for [emerg] lines after the last reload attempt. If the configuration is invalid, NGINX will not apply the new certificate.
  7. Test a remote handshake. From another host, use openssl s_client -connect host:443 -servername host to 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

SignalWhy it mattersWarning sign
SSL certificate expiration countdownAn expired certificate is an immediate outage; lead time prevents emergency renewalsLess than 30 days remaining on the leaf certificate
Error log SSL handshake failure rateReveals active TLS rejection before access logs show HTTP errorsAny sustained [crit] or [error] containing SSL
NGINX reload success/failureA failed reload keeps the old certificate in memory indefinitely[emerg] or configuration file .* test failed after reload
Intermediate certificate expiryChain validation fails even when the leaf certificate is freshIntermediate 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_certificate file 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_protocol and $ssl_cipher in 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.