July 14, 2026

The Site Loaded Everywhere Except the Office: One Expired SSL Certificate

Some users hit NET::ERR_CERT_DATE_INVALID while the site worked fine for everyone else. The real cause wasn't the certificate — it was the route traffic took.

  • SSL
  • Cloudflare
  • Nginx
  • DevOps
  • Debugging
Illustration of a certificate splitting into a valid path and an expired one

The symptom: “it works for me, not for my colleague”

One morning a message came in: some people couldn’t open our site. Instead of the page, they got the scary one:

NET::ERR_CERT_DATE_INVALID
Your connection is not private

The strangest part: for me personally, the site loaded perfectly. From home, from my phone on mobile data — all fine. But in the office, several people reliably hit an expired-certificate error.

Classic trap: “well, it works for me,” which makes the problem easy to under-rate. It’s real — just selective. And “selective” is the whole clue.

First lead: two different certificates

We compared which certificate different people were actually being served:

  • The ones it worked for got a cert from Google Trust Services.
  • The ones getting the error got an expired Let’s Encrypt cert — one that had expired yesterday.

One site, but a different certificate depending on who’s visiting. That’s not random; that’s a routing difference.

Why: Cloudflare + Split DNS

The answer was in which path the traffic takes to reach the server.

  • The site sits behind Cloudflare. External visitors go through it, and Cloudflare serves its own valid edge certificate — so for the whole outside world, everything looks fine.
  • The office network runs Split DNS: internally, the domain resolves straight to the origin server, bypassing Cloudflare entirely. Office users were talking directly to our backend.

And there’s the truth: the origin server’s own certificate was expired. Cloudflare had been masking it from the entire internet, while the office — via Split DNS — was hitting it head-on.

Lesson #1: if you have a CDN in front, the origin still needs a valid certificate. The CDN hides the problem, it doesn’t fix it — and sooner or later something (internal network, health checks, direct-to-origin traffic) lands on the backend directly.

Why auto-renewal didn’t save us

Let’s Encrypt certificates last 90 days and normally renew automatically. So why did this one expire?

The server runs the Plesk control panel, and Plesk was what originally issued this certificate for the domain. But the Plesk license was inactive and the panel was locked — and when the panel froze, so did its certificate auto-renewal. The cert died quietly, nobody got a warning, and on the fateful day the office stopped being able to open the site.

Meanwhile, the same server also had a second, separate toolcertbot — holding a perfectly valid certificate for the very same domain. Nginx just wasn’t using it.

Lesson #2: two certificate-issuing mechanisms on one server (Plesk + certbot) is future confusion waiting to happen. One thing issues, another renews, and nginx serves a third.

Diagnosis, step by step

Rather than guessing, I looked at facts.

What valid certs certbot is holding:

certbot certificates

It showed a valid certificate for the domain (~2 months left) — so a “healthy” version of the cert already existed on the box.

Which certificate file nginx actually serves for this domain:

grep -rn "ssl_certificate" /var/www/vhosts/system/example.com/conf/
ssl_certificate      /opt/psa/var/certificates/scfXXXXXX;
ssl_certificate_key  /opt/psa/var/certificates/scfXXXXXX;

Nginx was serving a file out of Plesk’s certificate store, not certbot’s.

The date on that file:

openssl x509 -in /opt/psa/var/certificates/scfXXXXXX -noout -subject -enddate
subject=CN = example.com
notAfter=Jul 19 ... 2026 GMT   ← expired

The full picture snapped into place: nginx serves an expired Plesk file, while a fresh certbot certificate sits right next to it, wired into nothing.

The fix: replace the file nginx serves

Because Plesk was frozen (with no license it won’t rewrite its own configs), I could safely drop the valid certbot certificate’s contents into the file Plesk points at, and reload nginx.

# 1. Back up first
cp -a /opt/psa/var/certificates/scfXXXXXX /root/scfXXXXXX.backup-$(date +%F)

# 2. Write the private key + full chain from certbot into the file
cat /etc/letsencrypt/live/example.com/privkey.pem \
    /etc/letsencrypt/live/example.com/fullchain.pem \
    > /opt/psa/var/certificates/scfXXXXXX

# 3. Lock down permissions
chmod 600 /opt/psa/var/certificates/scfXXXXXX

# 4. Validate config, then reload gracefully
nginx -t && systemctl reload nginx

Verify:

openssl x509 -in /opt/psa/var/certificates/scfXXXXXX -noout -enddate
# notAfter=Sep 18 ... 2026 GMT   ← valid

Done. The office immediately started getting a valid certificate. Zero downtime, and zero money paid to the host’s support desk to “renew a certificate.”

Detail: both ssl_certificate and ssl_certificate_key pointed at the same file. That’s why we cat the key, the cert, and the chain all into it — nginx picks the right block for each directive out of the combined file.

So it never happens again: a certbot deploy hook

This would come back in ~2 months: certbot renews its cert, and the Plesk file goes stale again. So the real fix is a hook that syncs the file automatically on every renewal:

# /etc/letsencrypt/renewal-hooks/deploy/plesk-example.com.sh
#!/bin/bash
cat /etc/letsencrypt/live/example.com/privkey.pem \
    /etc/letsencrypt/live/example.com/fullchain.pem \
    > /opt/psa/var/certificates/scfXXXXXX
chmod 600 /opt/psa/var/certificates/scfXXXXXX
systemctl reload nginx
chmod +x /etc/letsencrypt/renewal-hooks/deploy/plesk-example.com.sh
certbot renew --dry-run   # make sure it all runs

Now every certbot auto-renewal also updates the exact file nginx serves.

What I took from it

  1. A CDN hides origin problems, it doesn’t solve them. The origin needs a valid certificate too — otherwise internal traffic (Split DNS, health checks) walks straight into the expired one.
  2. Two SSL mechanisms on one server is a confusion source. Plesk issued, certbot renews, nginx serves a third thing. Pick one source of truth.
  3. A frozen control panel is not a frozen server. No Plesk license or panel access isn’t a reason to pay for every little thing — with root and SSH, almost everything is doable by hand.
  4. Automate the fix immediately. A one-time file swap is a patch. The deploy hook is the solution.

Time spent: about an hour of diagnosis. Cost: nothing. The most expensive thing here wasn’t the hardware or the support plan — it was understanding which path the traffic takes to reach the server.