DevFlow logoDevFlow
CERT_HAS_EXPIREDVerified Production Fix

Error: certificate has expired (CERT_HAS_EXPIRED)

Diagnose and fix CERT_HAS_EXPIRED errors in HTTPS API requests and webhooks. Check cert validity dates, renew Let’s Encrypt certs, and avoid insecure rejection disabling.

Root Cause Mechanical Summary

The TLS certificate presented by the remote server has passed its `notAfter` validity expiration date. Modern HTTP clients and web browsers immediately terminate TLS handshakes to protect users from stale keys.

During the TLS handshake, the client verifies `cert.valid_to >= current_time`. If the timestamp has elapsed, OpenSSL triggers X509_V_ERR_CERT_HAS_EXPIRED.

Vulnerable / Problematic Syntax
Before
// Insecure anti-pattern: disables all TLS certificate verification
const agent = new https.Agent({ rejectUnauthorized: false });
Production-Safe Remediation
After
// Best practice: maintain verified certificates and monitor expiry
// Renew certificate on the host machine:
// sudo certbot renew --dry-run

// Node.js client with proper CA trust
import https from 'https';
const req = https.request('https://api.wtool.dev', (res) => {
  console.log('TLS handshake succeeded, status:', res.status);
});

Resolution Note: Never disable `rejectUnauthorized: false` in production; renew the server certificate.

Step-by-Step Triage Checklist

  • Inspect certificate expiration date: `echo | openssl s_client -servername domain.com -connect domain.com:443 2>/dev/null | openssl x509 -noout -dates`.

  • Check if intermediate certificate bundle (fullchain.pem) is being served.

  • Verify if automated cron / systemd certbot timers are actively running.

Automated TLS Expiry Monitoring Script

scripts/monitor-tls.sh

Prevent recurrence by enforcing this verification check in staging or pre-commit hooks:

DOMAIN="wtool.dev"
EXPIRY_DAYS=$(echo | openssl s_client -servername "$DOMAIN" -connect "$DOMAIN:443" 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
echo "Certificate for $DOMAIN expires on $EXPIRY_DAYS"

Quick CLI Fix / Diagnosis

sudo certbot renew --force-renewal && sudo systemctl reload nginx

Frequently Asked Questions

Why does my updated certificate still appear expired in Node.js?
Node.js connection pools reuse keep-alive TLS sessions. You must restart the Node.js application process or reload the reverse proxy (Nginx/Caddy) to pick up newly issued certs.
Was this guide / tool helpful to you?