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.
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.
// Insecure anti-pattern: disables all TLS certificate verification
const agent = new https.Agent({ rejectUnauthorized: false });// 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.
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.
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"
sudo certbot renew --force-renewal && sudo systemctl reload nginx