DNS & TLS·8 min read·

Diagnose Certificate Errors: Chain, Clock, Name Mismatch

Diagnose HTTPS errors by chain, clock skew, and name mismatch using openssl and browser clues before you reissue a certificate that already works.

NB

Netbay Developer Relations

Netbay Engineering

On this page

Most "the certificate is broken" tickets are not a bad Let's Encrypt issuance. They are a name that does not match the URL, a chain that omitted the intermediate, or a clock that thinks 2024 is next year. Re-running certbot feels productive and often does nothing, because the live files were already fine. This post is a diagnosis order: read the error, inspect the handshake, check time, check names, check the chain, then decide whether to reissue.

Do this from a second machine. The VPS that hosts the site can hide SNI problems if you curl localhost without -servername.

certificate error decision tree browser / curl error name mismatch SAN vs URL vs SNI date invalid expiry or clock unknown issuer chain / incomplete fix DNS or server_name NTP, then renew if expired serve fullchain.pem reissue last, after the handshake matches the URL

Capture the handshake, not the myth

Ask openssl what the server presents for the hostname you actually type. SNI is mandatory. Without -servername you may see the default_server certificate, which is often a leftover snakeoil file.

bash
echo | openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltName
echo | openssl s_client -connect 203.0.113.40:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
timedatectl
date -u

Write down: subject, SAN list, notBefore, notAfter, issuer. Compare the URL in the browser (including www versus apex). Compare the VPS clock to UTC. That is already half of tickets.

curl -vI https://example.com/ prints SSL verify errors in the verbose stream. curl --resolve example.com:443:203.0.113.40 https://example.com/ tests this IP even if DNS is still wrong.

Name mismatch: the URL is not on the cert

NET::ERR_CERT_COMMON_NAME_INVALID means the leaf does not list this hostname. Typical causes:

  • Cert is for example.com, user opened www.example.com (or the reverse).
  • nginx default_server answered because server_name did not match Host.
  • You connected by IP. IPs are not on Let's Encrypt certs.
  • A wildcard *.example.com does not cover the apex or a.b.example.com.

Fix the name or the vhost, not the CA. Add the missing name with certbot --expand -d example.com -d www.example.com if both should work. Or redirect www to apex in nginx after both names are on the cert. Connecting by IP will always warn; use the name.

Date invalid: expiry versus a lying clock

ERR_CERT_DATE_INVALID is either a cert outside its validity window or a clock that is wrong. If notAfter is in the past, renewal failed; restore HTTP-01 or DNS-01 and run certbot renew. If notAfter is in the future but the client clock is 2023, fix NTP. VPS images that boot without systemd-timesyncd produce this on first HTTPS test.

A clock far in the future can also make a brand-new cert look not yet valid (notBefore is tomorrow relative to a slow clock, or notBefore is in the past relative to a fast clock). timedatectl set-ntp true, then recheck. Do not reissue to paper over NTP.

Unknown issuer: the intermediate never left the disk

ERR_CERT_AUTHORITY_INVALID with a Let's Encrypt leaf usually means nginx is serving cert.pem instead of fullchain.pem. The leaf is signed by an intermediate the browser must see in the handshake. Some desktop browsers have the intermediate cached; mobile and curl -v do not. Point ssl_certificate at fullchain.pem, reload, and confirm s_client -showcerts prints two (or more) certificates.

A related bug is serving the wrong file after a renewal: ssl_certificate still names archive/.../cert1.pem. Use the live/ symlinks.

Self-signed or staging certs also produce unknown issuer. If you tested with --staging, users will not trust it. Issue on production ACME and replace the paths.

bash
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null | grep -E "s:|i:|Verify"
nginx -T 2>/dev/null | grep -E "ssl_certificate|server_name|listen"
ls -l /etc/letsencrypt/live/example.com/

A leftover default certificate is its own mismatch class. Ubuntu nginx packages used to ship a self-signed snakeoil file as the catch-all on 443. If your named server block never became default_server, clients that send an unknown Host still get snakeoil and an unknown-issuer error. Either make the real vhost default_server or add a 444 catch-all. The leaf in live/ can be perfect while the default_server is still wrong.

Order of work: handshake dump, clock, SAN versus URL, fullchain versus cert.pem, then renew. Reissuing first shuffles files and hides the vhost bug for a day. Keep openssl and nginx -T as the source of truth. You can reproduce every case on an Ubuntu 24.04 VPS at Netbay in Lucknow (DC01) with a real public IP and L3/L4 DDoS filtering in front — netbayhosts.in.

Keep reading

Follow along on a real VPS

Deploy Linux in under 60 seconds

These guides are written against Ubuntu, Debian, and RHEL-family images — the same ones on NetBay.

Deploy an instance