# ERR_CERT_DATE_INVALID: Expired Certificates and Fixes

> Fix NET::ERR_CERT_DATE_INVALID by checking certificate dates, client clocks, failed renewals, stale nginx deployments and expired intermediate certificates.

Source: https://howhttpworks.com/debug/err-cert-date-invalid
Last reviewed: 2026-10-05

Error messages this page covers:
- `NET::ERR_CERT_DATE_INVALID`
- `Your connection is not private`
- `SEC_ERROR_EXPIRED_CERTIFICATE`
- `SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE`
- `SSL certificate problem: certificate has expired`
- `CERT_HAS_EXPIRED`

> **TL;DR:** The client thinks a certificate is outside its validity dates. Check the certificate the hostname actually serves, then compare it with the renewed file. If the file is new but the endpoint is old, fix deployment or reload nginx. If the leaf dates are fine, inspect the intermediates and the client clock. A successful renewal job does not prove users received the renewed certificate.

## What it means

Chrome can show **Your connection is not private** with `NET::ERR_CERT_DATE_INVALID`. Its [error definition](https://github.com/chromium/chromium/blob/main/net/base/net_error_list.h) covers both expiry and a certificate whose validity has not started, as judged by the client clock.

Certificates carry `notBefore` and `notAfter`. [RFC 5280 Section 4.1.2.5](https://www.rfc-editor.org/rfc/rfc5280#section-4.1.2.5) defines that interval; Section 6.1.3 checks validity during path validation. The error is a TLS certificate validation failure, before the client can exchange an HTTP request with that endpoint. It is not an HTTP status code.

Other clients expose these exact strings:

- Firefox: `SEC_ERROR_EXPIRED_CERTIFICATE` for an expired peer certificate, and `SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE` for an expired issuer certificate, as defined in [Mozilla NSS](https://github.com/nss-dev/nss/blob/master/lib/util/SECerrs.h).
- curl with OpenSSL: `SSL certificate problem: certificate has expired`. The [curl backend](https://github.com/curl/curl/blob/master/lib/vtls/openssl.c) combines its prefix with OpenSSL's verification error. Other TLS backends can phrase the message differently.
- Node.js: [`CERT_HAS_EXPIRED`](https://nodejs.org/api/errors.html#cert_has_expired). A future `notBefore` instead maps to `CERT_NOT_YET_VALID`.

## Who sent it?

The client reports the failure; the certificate comes from the TLS terminator it reached. That might be nginx, an ingress controller, a load balancer or a CDN. Start with the public hostname, not a certificate file on your origin:

```bash
openssl s_client -connect example.com:443 -servername example.com </dev/null \
  | openssl x509 -noout -dates -issuer -subject
date -u
```

Replace `example.com` with the failing hostname. `-servername` sends SNI so you inspect that hostname's certificate. The pipeline prints the leaf certificate's dates, not every certificate in the chain. It also does not prove validation succeeded: `s_client` normally continues after verification errors. Read its diagnostics, and use curl without `-k` for a client check.

Interpret the result against the correct current UTC time:

- Past `notAfter`: the certificate has expired.
- Future `notBefore`: it is not yet valid.
- Dates contain the correct time, but the failing device's time falls outside them: fix that device's clock and time synchronization.

## Fix it, in order of evidence

### 1. The leaf expired and renewal never completed

With Certbot, inspect the local inventory and scheduler:

```bash
sudo certbot certificates
systemctl list-timers --all
sudo certbot renew --dry-run
```

Check when the renewal task last ran and read its logs. Timer names depend on how Certbot was installed; its [documentation](https://eff-certbot.readthedocs.io/en/latest/using.html#automated-renewals) tells you to check systemd timers or cron for `certbot renew`. A dry run exercises renewal against a test server. It does not replace the production certificate.

For cert-manager, replace the namespace and Certificate name:

```bash
kubectl -n web describe certificate example-com
kubectl -n web get certificaterequests,orders,challenges
# Describe the pending Challenge using the name from the preceding command
kubectl -n web describe challenge CHALLENGE_NAME
```

Read the Certificate's `Ready` condition, `Reason`, `Message`, expiry and renewal time, plus its events. `Ready: False` is a reason to follow the CertificateRequest → Order → Challenge trail. Check the failed challenge rather than repeatedly recreating the Certificate: cert-manager documents [HTTP-01 reachability and DNS-01 propagation checks](https://cert-manager.io/docs/troubleshooting/acme/). `Ready: True` concerns the certificate in the target Secret; still inspect what the ingress serves.

### 2. Renewal succeeded, but the endpoint serves the old certificate

Compare the served leaf with the file nginx is configured to use:

```bash
openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem \
  -noout -dates -serial -fingerprint -sha256
openssl s_client -connect example.com:443 -servername example.com </dev/null \
  | openssl x509 -noout -dates -serial -fingerprint -sha256
sudo nginx -t && sudo nginx -s reload
```

For ordinary file-based nginx TLS configuration, replacing the file alone does not reload the running configuration. [nginx reloads gracefully](https://nginx.org/en/docs/control.html); if applying the new configuration fails, it keeps the old configuration. Inspect its error log and repeat the live probe after reloading.

If Certbot only obtains certificates and your deployment requires an nginx reload, wire that step into a successful-renewal deploy hook, for example:

```bash
sudo certbot renew --deploy-hook 'nginx -t && nginx -s reload'
```

An installer plugin may already handle the reload. Check your renewal configuration first. Deploy hooks run after successful renewal; ordinary dry runs skip them unless `--run-deploy-hooks` is supplied.

For a load balancer or CDN, compare the certificate attached to the public TLS listener with the newly issued one. Renewing an origin file cannot update a separate edge certificate. For an ingress, confirm the TLS Secret name and namespace, then check controller logs for reload failures.

Check each address DNS returns; one stale endpoint can make the failure intermittent:

```bash
dig +short example.com A
dig +short example.com AAAA
# Repeat for each returned IP, keeping the hostname in the URL
curl --noproxy '*' -v --resolve example.com:443:192.0.2.10 https://example.com/
curl --noproxy '*' -v --resolve 'example.com:443:[2001:db8::10]' https://example.com/
```

The addresses above are placeholders. [`--resolve`](https://curl.se/docs/manpage.html#--resolve) pins the connection address while preserving the hostname for SNI and certificate verification. Skip CNAME lines in `dig` output. This checks the addresses visible to your resolver, not every backend behind a load balancer or every CDN location. Probe known TLS-serving nodes separately and repeat from an affected network.

### 3. An intermediate expired

Do not stop at a valid leaf. Inspect the served chain:

```bash
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
```

Copy each PEM certificate block into a separate file, then inspect it:

```bash
openssl x509 -in intermediate.pem -noout -dates -issuer -subject
```

`-showcerts` lists what the server sent, not a verified chain. OpenSSL diagnostics identify the failing depth; depth 0 is the leaf. An expiry at a higher depth points beyond the leaf. Install the current intermediate chain supplied by your CA and reload the terminator. For Certbot-managed nginx, use `fullchain.pem` for `ssl_certificate`; it contains the leaf followed by intermediates.

### 4. The client clock is wrong, or validity has not started

Compare the failing device's clock with a correctly synchronized machine. Mozilla's [time-related error guide](https://support.mozilla.org/en-US/kb/troubleshoot-time-errors-secure-websites) covers both expired and not-yet-valid certificates. Enable automatic date/time synchronization on the affected device, then retry.

If the client time is correct and `notBefore` is in the future, deploy a currently valid certificate or ask the issuer to correct issuance. Changing the client's clock to accommodate a bad certificate hides the fault. Keep certificate verification enabled while checking the repair.

## Shorter lifetimes change the renewal budget

The adopted CA/Browser Forum ballot is **SC-081v3**. Its [Section 6.3.2 schedule](https://cabforum.org/2025/04/11/ballot-sc081v3-introduce-schedule-of-reducing-validity-and-data-reuse-periods/BR-SC081v3.pdf) sets these maximum validity periods for newly issued publicly trusted TLS subscriber certificates:

- Issued March 15, 2026 through March 14, 2027: **200 days**.
- Issued March 15, 2027 through March 14, 2029: **100 days**.
- Issued on or after March 15, 2029: **47 days**.

These are ceilings, not promised lifetimes; a CA can issue shorter certificates. The schedule does not retroactively change an existing certificate's `notAfter` and does not govern your private CA.

Treat automated renewal **and deployment** as an operational requirement. That is the engineering consequence of the shorter schedule, not a claim that the ballot mandates a particular automation tool. Monitor the dates served by the public endpoint so a broken deployment is caught even when issuance succeeds.

## Reproduce and verify

After the repair, repeat the leaf and chain inspection and test each known serving address without bypassing verification:

```bash
curl --noproxy '*' -v https://example.com/ -o /dev/null
openssl s_client -connect example.com:443 -servername example.com \
  -verify_hostname example.com -verify_return_error </dev/null
```

Confirm the new dates and fingerprint at the endpoint, a successful verification result, and a working request from the previously failing client. One successful request does not clear a stale node you have not tested.

## Related

- [Certificate name errors](https://howhttpworks.com/debug/err-cert-common-name-invalid): the certificate is for a different hostname.
- [ERR_SSL_PROTOCOL_ERROR](https://howhttpworks.com/debug/err-ssl-protocol-error): the TLS exchange fails before certificate validation can finish.
- [HTTPS and TLS](https://howhttpworks.com/guides/https-and-tls): certificates, chains and the handshake.
- [ERR_CERT_AUTHORITY_INVALID](https://howhttpworks.com/debug/err-cert-authority-invalid): the certificate is in date but its issuer is untrusted or an intermediate is missing.
