Short answer: NET::ERR_CERT_DATE_INVALID means the certificate the site sent is outside its validity dates, judged by your device’s clock: it has expired, a certificate in its chain has expired, or it is not valid yet. If only your device shows the error, your clock is wrong; turn on automatic date and time and reload. If everyone sees it, the site’s certificate has expired, and the owner has to renew it and reload the web server.
Chrome and Edge show it under “Your connection is not private”. Firefox says SEC_ERROR_EXPIRED_CERTIFICATE, or SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE when an intermediate expired, and MOZILLA_PKIX_ERROR_NOT_YET_VALID_CERTIFICATE for the opposite case. curl reports SSL certificate problem: certificate has expired, and Python certificate verify failed: certificate has expired. The other certificate codes are in our guide to “Your connection is not private”; this one is about dates only.
What does Chrome’s message say?
Click Advanced on the warning. Chrome writes one of two sentences, and the number in it tells you a lot. The expired version ends with Your computer’s clock is currently set to a date, followed by Does that look right? Read that date first.
| Chrome says | And | What it means | Who fixes it |
|---|---|---|---|
| its security certificate expired 12 days ago | Your clock shows the right date | The site's certificate, or one in its chain, has expired | The site owner |
| its security certificate expired 400 days ago | Your clock is a year or more ahead | The certificate is fine; your clock is wrong | You |
| its security certificate is supposedly from 3 days in the future | Your clock is behind | The certificate is not valid yet by your clock | You |
| Your clock is behind | or Your clock is ahead, as the heading | Chrome compared your clock with a trusted time source and found it off | You |
net::ERR_CERT_DATE_INVALID | Only in the DevTools console, the page itself loads | A script, image or API call on another hostname has an expired certificate | Whoever runs that hostname |
A quick rule: if many HTTPS sites fail at once on one device, it is that device’s clock. If one site fails on every device, it is the site.
Visitor fixes: set the clock
Certificates are checked against your device’s clock, in UTC. Turn on automatic time and let the device sync, then reload the page:
- Windows 10 and 11: Settings, Time & language, Date & time: turn on Set time automatically and Set time zone automatically, then click Sync now. From an administrator terminal,
w32tm /resyncdoes the same. - macOS: System Settings, General, Date & Time: turn on Set time and date automatically.
- iPhone and iPad: Settings, General, Date & Time: Set Automatically.
- Android: Settings, System, Date & time: turn on automatic time (the menu path varies by maker).
- Linux:
timedatectl statusshould showSystem clock synchronized: yes;sudo timedatectl set-ntp trueturns sync on.
Three clock problems survive that step:
- The clock resets after every shutdown. On a desktop that is the battery on the mainboard; replace it, or the date jumps back to a default after every power cut.
- The right time in the wrong zone. If the time zone is wrong and someone set the displayed time by hand to look right, the clock underneath is hours off. Fix the zone, then let it sync.
- A dual-boot computer. Windows keeps the hardware clock in local time and Linux in UTC, so the clock jumps by your UTC offset after switching. Hours rarely matter, except on a certificate issued minutes ago or expiring today.
An old device that no longer gets updates is the other visitor-side cause. It checks the chain against its own, outdated list of root certificates, and if the path it builds ends at a root that has since expired, it shows a date error on sites that work everywhere else. The best-known case: on 30 September 2021 the DST Root CA X3 root that many Let’s Encrypt chains led to expired, and devices that did not trust the newer ISRG Root X1, such as old iPhones, started failing; software on OpenSSL 1.0.x failed too, even with the new root installed, because it followed the old path. Update the system and the browser; on a system that gets no more updates, Firefox brings its own list of roots.
Site owner: which certificate expired?
Check what the server sends, from outside, and check every certificate in the chain, not just your own. The first command prints the dates of the certificate your server presents; the second lists every certificate the server sends, numbered from 0 (yours) upwards:
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -startdate -enddate
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null \
| openssl storeutl -noout -text -certs /dev/stdin 2>/dev/null \
| grep -E '^[0-9]+: |Subject:|Not After'We ran the second command against a test server in a scratch directory on Debian 12 with OpenSSL 3.0.22, set up with a leaf certificate valid for 60 days and an intermediate valid for only 10. The intermediate runs out first, and nothing in your own certificate’s dates would show it:
0: Certificate
Not After : Dec 20 12:19:29 2026 GMT
Subject: CN=example.test
1: Certificate
Not After : Oct 31 12:19:29 2026 GMT
Subject: CN=Test IntermediateThe root is normally not in this list: a correctly configured server leaves it out, and the visitor’s trust store supplies it. s_client can also check the chain as if the clock were already later. That is how to see what a visitor sees after a date, or what a visitor whose clock runs ahead sees today. -attime takes a Unix timestamp, and GNU date makes one:
openssl s_client -connect example.com:443 -servername example.com \
-attime "$(date -d '+15 days' +%s)" </dev/null 2>&1 | grep -E '^depth|verify error|Verify return code'depth=2 CN = Test Root
depth=1 CN = Test Intermediate
verify error:num=10:certificate has expired
depth=1 CN = Test Intermediate
depth=0 CN = example.test
Verify return code: 10 (certificate has expired)The depth line above the error names the culprit: 0 is your certificate, 1 the intermediate, 2 the root. A certificate that is not valid yet gives verify error:num=9:certificate is not yet valid instead. The openssl commands guide explains the other options of s_client and x509.
Without a terminal, our SSL checker shows the expiry date, the chain exactly as your server sends it and whether it is trusted, checked from Amsterdam.
Cause 1: the renewal failed
Let’s Encrypt certificates last 90 days by default, and certbot renews them with about 30 days left, so an expired one means renewal has failed for weeks. Nobody told you, because Let’s Encrypt stopped sending expiry reminder emails on 4 June 2025. Run sudo certbot renew --dry-run for the reason, and read the last attempts in /var/log/letsencrypt/letsencrypt.log or with journalctl -u certbot. The usual reasons are port 80 closed for the HTTP challenge, a DNS record that no longer points at this server, or a missing renewal timer; the commands for each are in the expired certificate section of our certificate errors guide.
Cause 2: renewed, but the old certificate is still served
Certbot replaced the file, but a process still holds the old certificate in memory. Compare the serial number on disk with the one the server sends:
sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -serial -enddate
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -serial -enddateDifferent serials mean something has not reloaded. nginx is the usual one (sudo nginx -t && sudo systemctl reload nginx), but check everything else that uses the same certificate:
- Mail servers. Postfix and Dovecot read the certificate at start and need a reload too; check them on ports 465 or 993 with
s_client, and on 587 with-starttls smtpadded. - Apps that terminate TLS themselves. A Node.js or Go service that loads the files at start keeps the old certificate until it restarts.
- HAProxy and other proxies that want one combined file. The combined file has to be rebuilt from the new certificate and key after every renewal.
- Docker containers. A certificate copied into an image at build time never changes. Mount the whole
/etc/letsencryptdirectory instead; mounting onlylive/breaks, because the files there are symlinks into../../archive/.
Put the reloads into a certbot deploy hook so the next renewal does them on its own; the hook is shown in our SSL handshake guide.
Cause 3: a different machine answers
If you renewed on the new server but the name still points at the old one for some visitors, they see the old certificate. Check every address the name has, IPv6 included, and ask each one directly:
dig +short example.com A
dig +short example.com AAAA
openssl s_client -connect 203.0.113.10:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -serial -enddate
openssl s_client -connect '[2001:db8::10]:443' -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -serial -enddateA stale AAAA record pointing at a previous host is the classic: visitors on IPv6 get the old machine and its expired certificate, everyone else gets the new one. The same happens with one node of a load-balanced pair that was missed, or with a certificate you uploaded to a load balancer or CDN by hand and forgot.
Cause 4: an expired intermediate in your chain file
Your certificate is valid, but the chain file next to it is years old and contains an intermediate or cross-signed root that has since expired. That happened to many sites when Sectigo’s AddTrust External CA Root expired on 30 May 2020 and again with DST Root CA X3 in 2021: servers that still sent the old bundle broke on clients that followed it. Use the chain your CA issues today. With certbot that is fullchain.pem, which is rebuilt on every renewal; with a paid certificate, download the current bundle from the CA rather than reusing the file from the last renewal.
Cause 5: a paid certificate renewed by hand, once a year
Calendar reminders for yearly renewals no longer fit. Under the CA/Browser Forum’s Ballot SC-081v3, public TLS certificates issued since 15 March 2026 can be valid for at most 200 days, from 15 March 2027 at most 100 days, and from 15 March 2029 at most 47. A certificate bought for “a year” now arrives as at least two certificates over that year, and from 2027 as four. Automate it: many commercial CAs offer ACME, the protocol certbot speaks, so the same tooling renews a paid certificate.
Behind Cloudflare
On a proxied hostname, visitors see Cloudflare’s edge certificate, which renews on its own. An expired certificate on your server then shows up as Cloudflare error 526 with the Full (strict) SSL mode, not as this error. If every visitor sees ERR_CERT_DATE_INVALID on a Cloudflare site, the record is set to DNS only, or a custom certificate you uploaded to Cloudflare has expired; if only one does, it is that visitor’s clock.
Catch it before visitors do
A short script checks every hostname and port you serve and prints a line for each certificate that expires within 14 days, or that did not come back at all. We ran this loop against two test servers and a closed port: it stayed silent for the certificate with 60 days left and named the 10-day one and the closed port.
#!/bin/sh
# Warn when a certificate expires within 14 days (1209600 seconds).
for target in example.com:443 www.example.com:443 mail.example.com:993; do
host=${target%:*}
if ! openssl s_client -connect "$target" -servername "$host" </dev/null 2>/dev/null \
| openssl x509 -noout -checkend 1209600 >/dev/null 2>&1; then
echo "$target: expires within 14 days, or no certificate came back"
fi
donesudo chmod 755 /usr/local/bin/cert-expiry-check
echo '0 7 * * * root /usr/local/bin/cert-expiry-check' | sudo tee /etc/cron.d/cert-expiry-checkCron mails any output to the job’s owner when the machine can send mail. For alerts by chat or from outside your own server, an HTTP monitor in Uptime Kuma can also warn about certificate expiry.
On a VMHeaven Linux VPS you run certbot and the reload hooks as root yourself. On Plesk hosting the free SSL certificate renews automatically, with no hook to maintain.
Frequently asked
What does NET::ERR_CERT_DATE_INVALID mean?
The site's certificate is outside its validity dates by your device's clock: it has expired, a certificate in its chain has expired, or it is not valid yet. If only your device shows it, your clock is wrong; if everyone sees it, the site's certificate has expired and the owner has to renew it.
How do I fix ERR_CERT_DATE_INVALID as a visitor?
Turn on automatic date, time and time zone in your system settings and reload the page. If the clock resets after every shutdown, replace the mainboard battery. On an old device that gets no updates, update the system or use a browser that brings its own root certificates. Do not click through on a site where you log in or pay.
How do I check when my SSL certificate expires?
Run openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -enddate from any machine; it prints the notAfter date of the certificate your server actually sends. With -checkend 1209600 instead of -enddate, openssl exits with 1 if the certificate expires within 14 days.
I renewed the certificate, so why do browsers still say it expired?
Something still serves the old one: nginx or Apache was not reloaded, a mail server, app or Docker container loaded the old file at start, or the name points at a second server through an old A or AAAA record. Compare the serial of the certificate on disk with the one the server sends, then reload or fix the record.