You renewed the certificate — so why is it still showing the old one?

The renewal ran. The log says success, the certificate authority's dashboard says issued, and the file on disk carries a brand-new expiry date. Your browser still shows the old certificate — still expired, still throwing a full-page warning at every visitor. Nothing you did was wrong. You completed one half of an operation that almost nobody tells you has two halves, and the half that everyone automates is not the half that fixes the site.

This is worth understanding properly rather than fixing by trial and error, because the same confusion is about to become much more common. Certificate lifetimes are being cut in stages, and every reduction multiplies the number of times this exact step gets a chance to fail quietly.

Renewal is two operations, and only one of them is automated

Getting a certificate and using a certificate are separate acts performed by separate parties, and the tooling most people run only performs the first.

Issuance is a conversation with a certificate authority. Your ACME client — certbot, acme.sh, lego, or whatever your control panel wraps around one of them — proves to the CA that you control the domain, receives a freshly signed certificate, and writes it to disk. That is the entirety of its remit. The protocol exists to prove domain control and deliver a file, and it deliberately stops there.

Deployment is making the software that answers on port 443 actually present that file. And here is the fact that explains the whole problem: the certificate authority cannot do this for you, and neither can the ACME protocol. The CA has no account on your server, no credentials, and no route in. It has no idea whether you run nginx, IIS, or a load balancer in front of both. A protocol with no access to your machine has no mechanism to restart your web server — and it would be alarming if it did.

So when certbot prints a success message it is making a precise and truthful claim: a valid certificate now exists at this path. It is not claiming that anything is serving it. Read that way, the log stops being misleading. It was answering a narrower question than the one you were asking.

Why your web server ignores the new file

The next question is the interesting one. The new certificate is sitting right there, in the same directory, under the same filename. Why does a running server keep serving the old one?

Because nginx and Apache read the certificate and private key exactly once, when the process starts, and hold the parsed result in memory for the lifetime of that process. They never look at the file again. This is not laziness or a missing feature; it falls out of two deliberate design choices you would not want reversed.

The first is ordinary performance. Reading and parsing a private key on every incoming connection would be pointless work repeated thousands of times a second for a value that changes a few times a year.

The second matters more. A web server binds to port 443, which requires privilege, so the master process starts as root. It reads the private key while it still can, then forks worker processes that drop that privilege and run as an unprivileged user for the rest of their lives. The workers handling live traffic typically cannot read the key file at all any more — and that is precisely the point, because it means compromising a worker does not hand an attacker your private key. The security property that protects you is the same property that guarantees your workers will never notice a new file. They are not permitted to.

Which is why the fix is not to copy the file again, or to copy it somewhere else, but to make the server redo that privileged startup read. On nginx that is systemctl reload nginx (or nginx -s reload); on Apache, systemctl reload apache2 or apachectl graceful.

Use reload rather than restart, and the reason is worth knowing. A reload is graceful: the master process re-reads its configuration and certificates, starts new workers with the new material, and lets the existing workers finish the requests they are already handling before retiring them. Nobody mid-upload gets cut off. A restart tears down the listening socket and kills connections in flight. Both pick up the new certificate; only one does it without dropping traffic.

Where else the old certificate hides

If a reload didn't fix it, then the old certificate is being served by something other than the process you just reloaded. There are four common candidates, and each has a distinct fingerprint.

A proxy or CDN is terminating TLS. This is the one that wastes the most time, because it means you have been examining the wrong certificate all along. When Cloudflare, Fastly, a cloud load balancer, or any reverse proxy sits in front of your site, two entirely independent certificates are in play: the one the edge presents to your visitors, and the one your origin presents to the edge. They are issued separately, they expire separately, and renewing one has precisely zero effect on the other. An expired origin certificate usually produces no browser certificate warning at all — the edge refuses to trust the origin and returns an error page instead, which on Cloudflare is a 526. If you are staring at a 526 while your local openssl output looks perfect, this is why.

More than one machine is serving the site. The fingerprint here is intermittency: refresh and it's fixed, refresh again and it's broken. That alternation isn't caching, it's arithmetic — the load balancer hands each request to a different node, renewal ran on one of them, and the others are still holding the old certificate in memory from their own last startup. A stale instance in an autoscaling group, a canary that never got redeployed, and a failover node nobody remembers all produce the same symptom.

The request is landing on the wrong virtual host. A single server hosting many sites on one IP address decides which certificate to send based on the hostname the client asks for during the handshake. If no configured site matches — a typo in a server_name, a site that was never enabled, a missing www variant — the server falls back to its default site and serves that certificate. The giveaway is that the certificate you are looking at is valid and correctly renewed, but issued for a completely different domain.

You are on IIS. Windows deserves its own note because its failure mode is structural rather than accidental. IIS does not bind a site to a certificate file; it binds to a specific certificate's thumbprint, which is a hash of that exact certificate. A renewed certificate is a different certificate, so it has a different thumbprint, so the existing binding still points at the old one. Importing the new certificate into the store is genuinely not enough, and no amount of reinstalling changes that — the binding itself has to be updated to reference the new thumbprint. This is the most common reason a renewal appears to do nothing on Windows.

Why every place you're looking is the wrong place to look

Step back and notice what the sources of truth you have been consulting have in common. The file on disk tells you what was written. The renewal log tells you what your client did. The CA's dashboard tells you what was issued. All three sit upstream of the only fact that matters — what a stranger's client receives when it opens a connection to your server. Every one of them can be perfectly healthy while the live site is broken, because none of them is in the path.

Your own browser is the fourth, and it fails from the opposite direction: it can show you a stale result after you have already fixed the problem. Two mechanisms cause this, and both are ordinary HTTPS behaviour rather than anything going wrong. Connections get reused — an open keep-alive connection has already negotiated its certificate and keeps using it until it closes, so a reload cannot retroactively change a connection that predates it. And TLS session resumption lets a client re-establish a connection from cached session state, skipping a full renegotiation.

That gives you a cheap way to tell a lingering artefact from a real failure. If a private window, a different browser, or a phone on mobile data shows the new certificate while your usual browser still shows the old one, you are looking at connection reuse and the fix already worked. If a fresh client on a fresh network still sees the old certificate, the fix did not work, and one of the four hiding places above is the reason.

How to see what your server is actually serving

The only authoritative answer comes from opening a new connection and reading the certificate off the wire. On the command line:

openssl s_client -connect yourdomain.com:443 -servername yourdomain.com

The -servername flag is not optional, and leaving it off is a reliable way to misdiagnose this entire problem. It sets the Server Name Indication field — the hostname the client announces during the handshake, which is how a server with dozens of sites on one address knows which certificate to send. Omit it and you get whatever the default site presents, which may be a perfectly valid certificate for somebody else's domain, and you will spend an afternoon wondering why the dates look fine. Append | openssl x509 -noout -dates -subject -issuer to cut the output down to the four things you actually want: the validity window, the hostname, and who signed it.

Run it from outside your own network if you can. From inside, split-horizon DNS or a stray hosts entry may quietly send you to a different machine than the one your visitors reach — which is, once again, the wrong server answering a question you thought you were asking about the right one.

If you would rather not touch a terminal, an external checker does the same thing: a fresh handshake from off your network, reporting the certificate genuinely being presented right now. That is what the check below does — it performs a real TLS handshake from outside and reads the live certificate, so what it shows you is what a first-time visitor gets, not what your filesystem or your renewal log believes.

One honest caveat, for exactly the reason described above: if your domain sits behind a proxy or CDN, any external check — this one included — sees the edge certificate, because that is the certificate being served to the public internet. It cannot see your origin. To check an origin behind a proxy you have to query it directly, by its own hostname or address, bypassing the edge.

Making the fix permanent

Reloading by hand fixes today and guarantees a repeat, because the gap between issuance and deployment is still there and the next renewal will fall straight into it. The durable fix is to make deployment part of renewal rather than a separate thing a human has to remember.

Every serious ACME client supports a hook that runs after a certificate is actually replaced — certbot calls it --deploy-hook. Put your reload command there and the two halves become one operation. It is worth understanding why a deploy hook beats the obvious alternative of reloading nightly from cron: the hook fires only when a certificate genuinely changed, so it does nothing on the many days when nothing was renewed, and it runs immediately rather than up to a day late. A blind nightly reload also tends to paper over the failure — the site heals overnight, so nobody ever discovers that deployment was never wired up in the first place.

Then verify from outside on a schedule, because hooks fail too. A hook can exit non-zero, reload the wrong service, or work perfectly on the node it runs on while a second node drifts. A renewal pipeline cannot audit itself. The only check that can't be fooled is the one that looks at the live handshake the way a visitor does.

Why this gets worse every year

None of the above is new. What is new is how often it will bite you.

Under CA/Browser Forum ballot SC-081v3, the maximum lifetime of a public TLS certificate is falling in stages: it dropped from 398 days to 200 in March 2026, falls to 100 in March 2027, and reaches 47 in March 2029. A certificate renewed roughly once a year becomes one renewed roughly eight times a year. A silent deployment gap that used to get one chance a year to hurt you gets eight.

The second effect is subtler and worse. Renewal typically begins about a third of the way before expiry, which historically left something like a month of slack between the new certificate being issued and the old one dying. If deployment silently failed, you had weeks of margin in which to notice. On a 47-day certificate that margin collapses to a couple of weeks — and it is a couple of weeks during which nobody is watching, because the entire point of automating renewal was that you stopped watching.

That is the part worth taking away. Automating renewal did not remove this failure; it changed its character. Manual renewal failed loudly and predictably — you knew the date, you missed it, the site broke. Automated renewal fails silently, at an unpredictable moment, in a step you never knew existed, on a schedule that is getting eight times denser. The defence isn't more automation. It is periodically checking the certificate your server is really serving, from outside, the way a stranger sees it — which is the one question none of your logs, dashboards, or files can answer.

Sources