Everything for SSL/TLS Certs in one place — pick a section below. 13 reviewed items across 4 content types.
Quick Answer
A TLS handshake is a negotiation where the client and server agree on a protocol version and cipher suite, the server proves its identity by presenting a certificate signed by a trusted authority, and both sides derive a shared symmetric encryption key without ever transmitting that key directly, using asymmetric key exchange. Validation failures are hard to debug because they can happen at many independent checks — hostname mismatch, expired certificate, untrusted chain, revoked certificate, unsupported cipher — and most client libraries collapse all of these into one generic error message rather than reporting which specific check actually failed.
Detailed Answer
Think about a high-security video call between two strangers who need to agree on which video conferencing app and quality settings to use, have one person show government-issued ID to a witness the other side trusts proving they are who they claim to be, and agree on a private code word for the rest of the conversation that nobody eavesdropping on the call setup could have figured out, even though the whole setup conversation happened in the open. All of that negotiation happens before either person actually says anything meaningful to the other.
TLS was designed to accomplish exactly this over a network connection that might be tapped by anyone: negotiate shared parameters, prove identity, and establish a secret key, all before any application data like an HTTP request is exchanged. The reason it's built as a multi-step negotiation rather than one direct message is that clients and servers may support different protocol versions and cryptographic algorithms, and the identity and trust piece, certificates, needs to be independently verifiable by the client without any prior direct relationship with the server, which is what makes HTTPS work reliably between total strangers on the internet.
The handshake proceeds: the client sends a ClientHello, listing supported TLS versions, cipher suites, and critically the SNI, Server Name Indication, telling the server which hostname it's trying to reach, since one IP address can host many TLS certificates. The server responds with a ServerHello, its chosen version and cipher, plus its certificate chain. The client then validates that chain, checking the signature path up to a trusted root, checking the certificate's notBefore and notAfter validity window, checking the certificate's subject or SAN matches the requested hostname, and optionally checking revocation status via OCSP or CRL. Both sides then perform a key exchange, commonly ECDHE, elliptic-curve Diffie-Hellman, which lets both sides compute the same shared secret without ever transmitting it, and send Finished messages confirming the handshake's integrity before any actual HTTP data flows across the connection.
In production, TLS handshake failures show up as elevated connection error rates on a service like checkout-worker calling an external payment gateway, and the operational challenge is that failure can originate from any single check in that validation chain: an expired certificate, a hostname that doesn't match, common after a service is renamed or a load balancer's certificate doesn't cover a newly added domain, an untrusted or incomplete chain, a revoked certificate, or simply no overlapping cipher suite between an old client and a server that's dropped support for outdated ciphers for security reasons.
The non-obvious gotcha: many HTTP client libraries and language runtimes report all of these distinct failure modes through one generic exception type, something like SSL handshake failed or certificate verify failed, without surfacing which specific check tripped. This means the fastest real debugging path is almost always bypassing the application entirely and running openssl s_client -connect host:443 -servername host directly, which prints the actual certificate details, chain, and specific validation error, rather than trying to interpret a vague error bubbling up from deep inside an application's HTTP library.
Code Example
# see the FULL handshake output including SNI, chain, and the specific validation error openssl s_client -connect user-auth-service.internal:443 -servername user-auth-service.internal </dev/null 2>/dev/null | grep -A2 'Verify return code' # check which TLS versions/ciphers a server actually supports openssl s_client -connect user-auth-service.internal:443 -tls1_2 openssl s_client -connect user-auth-service.internal:443 -tls1_3 # a return code other than 0 pinpoints the exact failed check (expired=10, untrusted=19, etc.)
Interview Tip
A junior engineer typically says 'TLS encrypts the connection,' which skips the identity-proving and negotiation aspects entirely. For a senior/architect role, the interviewer is actually looking for you to walk through the actual handshake sequence, ClientHello and ServerHello, certificate validation with its several independent checks, and key exchange, and specifically to recognize that generic 'handshake failed' errors from application libraries hide which check actually failed, which is why going straight to openssl s_client for raw handshake output is the efficient debugging path rather than guessing from a vague stack trace.
◈ Architecture Diagram
Client Server │ ClientHello │ │ ───────────────→│ (SNI, ciphers) │ ServerHello │ │ ←─────────────── │ (cert chain) │ validate chain │ │ key exchange │ │ ←──────────────→│ (ECDHE) │ Finished │ │ ←──────────────→│ │ HTTP data → │
💬 Comments
Quick Answer
An expired certificate fails a completely different check than a chain-of-trust problem: the chain can be perfectly valid and complete, but the client rejects the connection purely because the current date falls outside the certificate's notBefore/notAfter validity window. So openssl s_client showing a full, correct chain but a Verify return code 10, certificate has expired, immediately isolates it. The fast diagnosis is checking the expiry date directly rather than assuming a more complex chain or network issue; the real fix, though, is retroactive — automated expiry alerting and renewal well before this incident, because by the time you're debugging it live, the outage has already happened.
Detailed Answer
Think about a company badge that grants employees building access, set to automatically expire on a date printed on the card. Chain-of-trust is like the badge itself being fake or issued by an organization security doesn't recognize at all — the badge's authenticity is in question. Expiry is a completely different problem: the badge is 100 percent genuine, issued by a fully trusted authority, but the date printed on it has simply passed, and the door scanner is doing exactly its job refusing entry to an out-of-date badge, no matter how authentic it is otherwise.
TLS certificates carry an explicit validity window, notBefore and notAfter timestamps, specifically so compromised or outdated key material has a natural expiration, limiting how long a leaked or weakly-secured private key remains dangerous if it's ever exposed. This is a deliberate security design choice, not an arbitrary inconvenience, which is also why the industry, led by browser vendors, has steadily shortened maximum certificate lifetimes over the years, from years down to 398 days, with further reductions proposed, to force more frequent rotation across the entire ecosystem.
When a certificate expires, every TLS client's validation step explicitly checks the current time against the certificate's validity window as one of several independent checks, alongside chain validity, hostname match, and revocation status. Critically, this check can fail even when the certificate chain is otherwise completely valid and correctly served, which is why openssl s_client -connect internal-user-auth-service:443 -servername internal-user-auth-service showing a correct, complete chain but a nonzero verify return code specifically pointing to expiry, code 10, tells you immediately this is a pure date problem, not a chain, hostname, or trust problem — a much narrower, faster diagnosis path than starting from 'something's wrong with TLS' and checking everything from scratch.
In production, the operational lesson from a 2am silent expiry is almost never about handshake diagnostics — the real gap is that nothing was watching the expiry date proactively. Mature setups run a scheduled job, or use a dedicated certificate-monitoring tool, that checks every production certificate's days-until-expiry on a recurring basis and pages well before expiry, commonly at 30 days, 14 days, and 3 days out, specifically so a renewal failure, an ACME client bug, a manual process someone forgot, a certificate issued for the wrong hostname, gets caught and fixed during business hours, long before the actual expiry moment silently breaks things at 2am with nobody watching.
The non-obvious gotcha: an expired certificate incident often isn't actually about the certificate lifecycle at all — it's frequently a symptom of an automated renewal process that itself silently failed weeks earlier, a cron job for an ACME or Let's Encrypt renewal that started erroring out after a permissions change, or a rate limit hit against the CA, and nobody noticed because the only alert anyone had configured was 'did the certificate actually expire,' not 'is the automated renewal process itself healthy.' The fix isn't just renewing the certificate; it's diagnosing and repairing whatever quietly broke the automation days or weeks before the visible outage occurred.
Code Example
# fastest check: is this expiry, or something else entirely? openssl x509 -in /etc/ssl/certs/user-auth-service.crt -noout -enddate # confirm via the actual handshake - return code 10 means expiry specifically openssl s_client -connect user-auth-service.internal:443 -servername user-auth-service.internal </dev/null 2>/dev/null | grep 'Verify return code' # check whether the renewal automation itself is healthy, not just cert expiry systemctl status certbot-renew.timer journalctl -u certbot-renew.service --since '30 days ago' | grep -i error
Interview Tip
A junior engineer typically treats any TLS error the same way and starts checking the whole chain from scratch. For a senior/architect role, the interviewer is actually looking for you to recognize expiry as a distinct, independently-checked failure mode from chain-of-trust, diagnosable in seconds via the verify return code or a direct enddate check, and more importantly, to pivot the conversation to prevention: proactive expiry alerting well before the deadline, and specifically investigating whether the automated renewal pipeline itself silently failed days earlier, since that's usually the real root cause behind a surprise expiry.
◈ Architecture Diagram
chain-of-trust problem: WHO issued it? (untrusted) certificate expiry: WHEN is it valid? (date only) openssl verify code 10 → expired, chain otherwise fine openssl verify code 19/21 → chain/trust actually broken
💬 Comments
Quick Answer
Build or use a scanning job that connects to every known TLS endpoint on a schedule, extracts each certificate's expiry date, exposes days-until-expiry as a metric per service, and alert on tiered thresholds, for example a ticket at 30 days, a page at 7 days, and a critical page at 1 to 2 days, rather than a single binary expired or not-expired check. The hard part isn't the expiry check itself; it's maintaining an accurate, complete inventory of every certificate across every service, internal and external, since certificates nobody remembers to add to the inventory are exactly the ones that cause 2am surprises.
Detailed Answer
Imagine a large household with a dozen different subscriptions, insurance, magazine, gym membership, domain name registration, each with its own renewal date, some auto-renewing and some not. A well-organized household keeps one master calendar with every renewal date logged, checked weekly, with reminders set well in advance of each one. A disorganized household relies on each subscription's own notification email arriving on time and someone happening to read it, which works fine until one email lands in spam or a card on file expires, and suddenly a service everyone depends on stops working with no warning at all.
Certificate expiry monitoring at scale requires exactly that master-calendar approach rather than trusting individual reminders, because the TLS layer itself gives you no built-in fleet-wide visibility. A certificate expiring is invisible until a client tries to connect and fails, by which point it's already an outage. This is why mature platform teams build a dedicated inventory and scanning system rather than relying on whatever renewal notification email each CA happens to send, since those emails go to whichever address was used at issuance time, which drifts out of date as teams and ownership change over months and years.
A typical implementation runs a scheduled scanner, a cron job, or a tool like the Prometheus blackbox exporter's TLS probe module, or a dedicated cert-monitoring service, that iterates over a maintained list of every TLS endpoint, internal services like payments-api.internal and checkout-worker.internal, and external-facing domains, connects via TLS, extracts the leaf certificate's notAfter date, computes days-remaining, and exposes it as a labeled metric, something like ssl_cert_expiry_days with a service label. An alerting rule engine, Prometheus Alertmanager or equivalent, then evaluates that metric against tiered thresholds, routing a 30-days-out warning to a ticket or Slack channel, and a sub-7-day warning to an actual page, since at that point the risk of a missed manual renewal turning into an outage is genuinely high.
In production, the actual operational challenge is less about the scanning and alerting mechanics and more about inventory completeness. New services spin up constantly, and unless certificate provisioning is wired into the same automated pipeline that registers a new service into the scanning inventory, for example via infrastructure-as-code that both provisions a certificate and registers the endpoint with the monitoring scanner in the same change, it's extremely easy for a newly-launched service to silently fall outside the safety net until its first, and possibly only, certificate quietly expires months later without anyone noticing.
The non-obvious gotcha: teams often build solid expiry alerting for public-facing HTTPS endpoints but completely miss internal service-to-service certificates, mTLS certificates used inside a service mesh, or internal CA-issued certificates for database connections, because those aren't reachable by a scanner probing from outside the network the way a public website is. The monitoring coverage needs to explicitly include internal-only endpoints and internal CA-issued certificates, not just anything visible from the public internet, or you end up with a false sense of complete coverage that has a large blind spot exactly where outages are hardest to diagnose, deep inside internal infrastructure.
Code Example
# prometheus blackbox exporter probing internal + external TLS endpoints
modules:
tls_connect:
prober: tcp
tcp:
tls: true
tls_config:
insecure_skip_verify: false # verify chain AND surface expiry via ssl_cert_not_after
# scrape config covering both public and internal-only services
scrape_configs:
- job_name: tls-expiry
static_configs:
- targets:
- payments-api.internal:443
- checkout-worker.internal:443
# alertmanager rule: tiered thresholds, not a single binary check
- alert: CertExpiringSoon
expr: (ssl_cert_not_after - time()) / 86400 < 30
labels: { severity: ticket }
- alert: CertExpiringCritical
expr: (ssl_cert_not_after - time()) / 86400 < 7
labels: { severity: page }Interview Tip
A junior engineer typically says 'set up a script to check if certs are expired,' missing that the actual hard problem is maintaining a complete, accurate inventory of every certificate across the fleet, including internal mTLS certificates not visible from outside. For a senior/architect role, the interviewer is actually looking for tiered alerting thresholds, ticket versus page based on days-remaining, and specifically wants you to flag that certificate provisioning needs to be wired into the same automation that registers new endpoints for monitoring, otherwise new services silently fall outside coverage until they cause a surprise outage.
◈ Architecture Diagram
┌────────┐ 30d ┌────────┐ 7d ┌────────┐ 1d ┌──────────┐
│ Scanner│ ─────→│ Ticket │ ────→│ Page │────→│ Critical │
└────────┘ └────────┘ └────────┘ └──────────┘
│
✗ blind spot: internal mTLS certs not scanned from outside💬 Comments
Quick Answer
Automate renewal well before expiry, attempting it at roughly two-thirds of the certificate's total lifetime elapsed rather than at the last minute, using a tool like cert-manager in Kubernetes or an ACME client, retry with backoff on failure, and critically, alert loudly and immediately on a FAILED renewal attempt itself, not just on the certificate's eventual expiry, so humans have weeks of runway to intervene rather than discovering the problem only when the old certificate is about to run out. The design principle is treating 'renewal attempt failed' as an actionable incident on its own, independent of whether the current certificate has actually expired yet.
Detailed Answer
Think about a household with an automated bill-pay system for a mortgage. A well-designed system doesn't wait until the day payment is due to attempt the transfer — it tries several days early, specifically so that if the transfer fails, insufficient funds, an expired linked card, a bank system outage, there's still time to notice the failure and fix it manually before the actual due date arrives and the mortgage goes unpaid. A poorly-designed system that only attempts payment on the due date itself turns any hiccup into an automatic, immediate crisis with zero recovery time left.
Automated certificate renewal needs the same built-in buffer, and this is exactly how tools like cert-manager, the standard Kubernetes-native certificate automation controller, and most ACME clients are designed: renewal is attempted well before actual expiry, commonly at two-thirds of the certificate's lifetime elapsed, specifically to create a wide window for retries and human intervention if something goes wrong, rather than a renewal system that only tries once, right at the deadline, leaving no slack at all for anything to go sideways.
Internally, the automated flow typically works like this: a controller, cert-manager or a custom ACME client wrapper, tracks each certificate's expiry and schedules a renewal attempt at a defined threshold before expiry. The renewal process proves domain or service ownership to the certificate authority, via ACME's HTTP-01 challenge, serving a token at a well-known URL, or DNS-01, creating a specific TXT record, DNS-01 being necessary for wildcard certificates or internal-only hostnames that aren't publicly reachable. It then receives the newly issued certificate, and has to actually deploy or reload it into the running service, a Kubernetes Secret update triggering a pod restart, or a live reload in something like nginx or envoy, and that final deployment step is itself a place automation can silently fail even after successful issuance completes.
In production, the critical monitoring isn't just days-until-expiry, as in general certificate monitoring; it's specifically whether the last renewal attempt succeeded, exposed as its own metric or event, cert-manager emits Kubernetes events and conditions on the Certificate resource itself showing Ready or NotReady and the reason, with alerting on renewal failure treated as urgent regardless of how many days remain until actual expiry. A renewal failure today with 45 days of runway left is a fix-it-this-week problem, while the same failure discovered with 2 days left is an emergency — the failure event itself is the leading indicator you actually want to catch early, not the countdown to expiry.
The non-obvious gotcha: teams often get the issuance and renewal automation right but forget that the CA challenge step can fail in ways specific to infrastructure changes. For example, a DNS-01 challenge automation that writes a TXT record via a cloud provider API can silently break after an unrelated DNS provider migration or an IAM permission change, and because the certificate doesn't actually expire for weeks after that break, the failure sits invisible until the buffer window runs out. Teams that alert only on 'certificate expiring soon' rather than 'last renewal attempt status' discover this kind of silent automation breakage far too late, often only once actual expiry is imminent and options for a graceful fix have narrowed.
Code Example
# cert-manager: renewal attempted automatically well before expiry (default ~2/3 of lifetime)
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: checkout-worker-tls
spec:
secretName: checkout-worker-tls-secret
dnsNames:
- checkout-worker.internal
issuerRef:
name: internal-ca-issuer
kind: ClusterIssuer
# cert-manager schedules renewal at renewBefore (default: 2/3 of duration)
duration: 2160h # 90 days
renewBefore: 720h # attempt renewal 30 days before expiry
# check renewal health directly - alert on Ready=False, not just days-until-expiry
kubectl describe certificate checkout-worker-tls | grep -A5 ConditionsInterview Tip
A junior engineer typically says 'automate renewal so certs never expire,' treating automation as a solved problem once configured once. For a senior/architect role, the interviewer is actually looking for you to design in a wide buffer between renewal attempt and actual expiry, and specifically to alert on renewal FAILURE as its own urgent signal, independent of days-remaining, because that's the leading indicator that actually gives humans time to intervene. Recognizing that the ACME challenge step, DNS-01 particularly, can silently break from unrelated infrastructure changes, invisible until the buffer runs out, is a strong senior-level insight.
◈ Architecture Diagram
┌────────┐ ┌─────────┐ ┌────────┐ ┌────────┐
│ Issue │ → │2/3 life │ → │ Renew │ → │Deploy │
│ cert │ │ elapsed │ │attempt │ │ reload │
└────────┘ └─────────┘ └────────┘ └────────┘
│
✗ fail → alert NOW
(weeks of runway left)💬 Comments
Quick Answer
Browsers silently fetch missing intermediate certificates using the AIA extension embedded in the leaf certificate, and cache trusted intermediates from prior visits, so a server that forgets to send its intermediate certificate often still works fine in a browser. Most API clients, CLI tools, and backend libraries do not do this automatic fetching; they only trust what's explicitly presented during the TLS handshake, so the same misconfigured server fails chain validation for them. The fix is always on the server, configuring it to send the full chain, never a client-side workaround.
Detailed Answer
Imagine a bouncer at an exclusive club told to admit anyone showing a valid membership card and, if asked, a letter from the club's parent organization confirming the club itself is legitimate. Some visitors already have a copy of that confirmation letter in their wallet from a previous visit, so they don't need to be shown one again — they just flash their card and walk in, and it looks like the letter didn't even matter. A first-time visitor without that letter already in hand, though, gets stopped cold at the door if the club forgot to have a copy ready to show on the spot.
This is exactly the leaf, intermediate, root certificate chain problem. A server's TLS certificate, the leaf, is signed by an intermediate Certificate Authority certificate, which is itself signed by a root CA pre-trusted by operating systems and browsers. Browsers were engineered to be forgiving: modern browsers implement AIA chasing, meaning that if a server doesn't send the intermediate certificate, the browser follows a URL embedded in the leaf certificate, the Authority Information Access extension, to fetch the missing intermediate itself, and browsers also cache intermediates seen from previous connections to any site using the same CA. This forgiving behavior exists to maximize the chance a real user can browse the web without a broken-looking error page, even when server operators misconfigure their TLS setup.
Most non-browser clients — a Java HTTP client, Python's requests library, curl without special flags, mobile app TLS stacks — implement strict chain validation: they only build a trust path using exactly the certificates presented during the TLS handshake's Certificate message, plus their local trust store of root CAs. If the server's TLS configuration only serves the leaf certificate and omits the intermediate, these clients have no path from the leaf to a trusted root and fail validation outright, with an error like unable to get local issuer certificate, even though the exact same server works fine when checked in a browser that happened to have or fetch the missing piece on its own.
In production, the diagnostic step is openssl s_client -connect payments-api.internal:443 -showcerts, which shows exactly what the server actually sent during the handshake. If you see only one certificate in the output, the leaf, instead of the leaf plus one or more intermediates, that confirms the server is misconfigured. The fix is almost always concatenating the leaf certificate with the required intermediate certificate or certificates, in the correct order, into the server's configured certificate file, and reloading the web server or load balancer, never touching anything on the client side.
The gotcha: teams sometimes fix this by adding the missing intermediate to the client's trust store instead of fixing the server, which appears to solve the immediate problem but is fragile and wrong. It only fixes that one client, doesn't fix the underlying misconfiguration for every other API consumer or future service talking to the same endpoint, and if the CA ever rotates its intermediate certificate, which does happen periodically, the client-side workaround silently breaks again, while a properly-configured server would have kept working transparently through the rotation.
Code Example
# see EXACTLY what the server sent during the handshake openssl s_client -connect payments-api.internal:443 -showcerts </dev/null # if output shows only 1 certificate instead of leaf + intermediate(s), server is misconfigured # verify the chain explicitly against a trusted root, catching the same failure curl would hit openssl verify -CAfile /etc/ssl/certs/ca-bundle.crt -untrusted intermediate.pem leaf.pem # nginx: MUST serve leaf + intermediate concatenated, in that order, not just the leaf ssl_certificate /etc/nginx/ssl/payments-api-fullchain.pem; # leaf + intermediate(s) ssl_certificate_key /etc/nginx/ssl/payments-api.key;
Interview Tip
A junior engineer typically says 'add the missing cert to the trust store,' patching the symptom on one client only. For a senior/architect role, the interviewer is actually looking for you to explain why browsers and API clients behave differently — AIA chasing and intermediate caching versus strict handshake-only validation — diagnose using openssl s_client -showcerts to see exactly what the server transmits, and insist the fix belongs on the server, serving the full chain, rather than patching individual clients, because a client-side workaround doesn't scale across every consumer and breaks again silently on CA rotation.
◈ Architecture Diagram
Browser: leaf only sent → AIA fetch ✓ → works API client: leaf only sent → no AIA → ✗ fails Fix: server sends leaf+intermediate → both ✓ work
💬 Comments
Context
An enterprise tracking ~400 TLS certificates in a spreadsheet with calendar reminders: internal CAs, three public CAs acquired through M&A, manual CSR rituals per team, and two outages in eighteen months from expired certs nobody owned.
Problem
Manual issuance meant weeks of lead time and inconsistent key hygiene (keys emailed, 4096-bit-because-someone-said-so, SANs guessed); the spreadsheet drifted from reality within weeks of every audit; and shortening public-cert lifetimes made the manual process arithmetically unsustainable.
Solution
Standardized on ACME automation everywhere: public certs via a single CA with DNS-01 challenges automated through the DNS provider's API (wildcard strategy rationalized per domain), internal certs from a private CA exposing ACME (smallstep-style), cert-manager handling Kubernetes and certbot/native integrations on VMs — all renewing at 2/3 lifetime with deploy hooks reloading services. Discovery closed the unknown-cert gap: a scanner sweeps all listeners weekly, reconciling found certificates against the automated inventory; anything unmanaged gets a ticket and an owner. Expiry monitoring pages at 14 days (meaning: automation failed twice already) rather than tracking calendars.
Commands
cert-manager: Certificate {dnsNames, issuerRef: letsencrypt-dns01, renewBefore: 720h}VMs: certbot renew --deploy-hook 'systemctl reload nginx' (timer)
discovery: scan 443/8443/... fleet-wide -> diff vs inventory -> unmanaged-cert tickets
Outcome
Two years, zero expiry incidents; issuance lead time from weeks to minutes (self-service via annotations/API); the spreadsheet retired; the audit answer became a query. The discovery sweep found 60 certificates nobody had listed — including one production listener 9 days from expiry on day one.
Lessons Learned
Discovery is half the program — automating the certs you know about doesn't help with the ones you don't. The 14-day page threshold reframes alerts correctly: an alert means the automation needs fixing, not that a human should renew something.
💬 Comments
Context
A payments platform whose services (and hundreds of B2B partner integrations) pinned specific intermediate certificates 'for security' — a practice that had already caused breakage during routine CA intermediate rotations.
Problem
Intermediate pinning is brittle by design: CAs rotate intermediates without notice, and the partner-facing breakage from each rotation cost support weeks. With a major browser-driven CA distrust announced (a CA the platform's certs chained to), every pinned client faced hard failure at the migration deadline.
Solution
Moved the trust model from pins to policy: server certs re-issued from a new CA well before the deadline with dual-chain serving during transition (old and new chains selected by SNI/agility layer), partner guidance rewritten from 'pin this intermediate' to 'trust these roots + enforce hostname and revocation' with a partner test endpoint serving the new chain for validation, and internal clients standardized on the OS/runtime trust store plus CAA records and Certificate Transparency monitoring for issuance control (the security properties pinning was meant to provide, without the fragility). A staged cutover tracked partner readiness via the test endpoint's logs.
Commands
CAA: example.com. CAA 0 issue 'newca.example' — issuance control at DNS
CT monitoring: alert on any cert issued for our domains outside expected CA
partner test endpoint: new-chain.example.com — validation before cutover
Outcome
The distrust deadline passed as a non-event: 96% of partners validated ahead via the test endpoint, the stragglers hit a planned support process instead of an outage; internal breakage was zero. Intermediate pinning is now banned in the integration guide, replaced by CAA + CT monitoring.
Lessons Learned
The partner test endpoint converted an unknowable risk ('will they break?') into a tracked metric. CT monitoring plus CAA delivers the anti-mis-issuance goal pinning pretended to — audit-visible and rotation-proof.
💬 Comments
Symptom
Users suddenly cannot sign in or make authenticated requests; services report TLS handshake or certificate validation failures against an internal endpoint that was working normally moments before, with no recent deploy or config change to explain it.
Error Message
SSL routines:tls_process_server_certificate:certificate verify failed: certificate has expired
Root Cause
A TLS certificate on an internal or infrequently-touched endpoint reached its expiry date without being renewed, because it wasn't covered by automated renewal or wasn't tracked in whatever inventory the team relies on for expiry alerts. This exact pattern has caused major real-world outages: Microsoft Teams was down for roughly three hours in February 2020 due to an expired authentication certificate, and Ericsson's 2018 expired-certificate incident took down mobile networks for roughly 11 operators (including O2 UK for nearly 24 hours, affecting about 32 million customers) — both cases where a certificate expiry with no automated renewal or alerting brought down otherwise-healthy infrastructure instantly and completely.
Diagnosis Steps
Solution
Identify and renew the expired certificate immediately (reissue from the CA, or rotate to a pre-provisioned backup cert if one exists), deploy it to every endpoint/load balancer terminating TLS for that service, and restart/reload the affected processes so they pick up the new certificate rather than continuing to serve the cached expired one.
Commands
openssl s_client -connect host:443 -servername host </dev/null 2>/dev/null | openssl x509 -noout -dates
openssl x509 -in cert.pem -noout -enddate
curl -vI https://host 2>&1 | grep -i expire
Prevention
Inventory every certificate in use (including internal/infrequently-touched ones) and put all of them behind automated renewal (ACME/cert-manager or equivalent) rather than relying on manual tracking. Add expiry alerting at multiple lead times (30/14/3 days) that pages a human, not just an email that can be missed. Treat 'no automated renewal' as a finding to remediate, not an acceptable steady state, regardless of how rarely the endpoint changes.
💬 Comments
Symptom
After renewing/reissuing a TLS certificate (especially from a CA that changed its intermediate chain), some clients connect fine while others — often older clients, specific browsers, or non-browser HTTP clients — report a certificate trust/validation failure, even though the server's certificate itself is valid and unexpired.
Error Message
unable to get local issuer certificate (client-side) despite the leaf certificate showing as valid and current on the server
Root Cause
TLS servers must present the full chain (leaf plus intermediate certificates) on every connection; if the server config only serves the leaf certificate (common when a renewal script only swaps the leaf file and doesn't also update the intermediate bundle, especially after a CA rotates which intermediate it issues from), clients that don't already have the new intermediate cached or cross-signed in their trust store fail to build a valid chain, while clients with a more complete built-in trust store or chain-fetching may succeed — producing the confusing 'works for some clients, not others' pattern.
Diagnosis Steps
Solution
Rebuild and deploy the full certificate chain (leaf + intermediate(s), in the correct order) on every TLS-terminating endpoint, not just the leaf certificate, and verify with an external chain-validation tool against multiple client trust store assumptions before considering it resolved.
Commands
openssl s_client -connect host:443 -showcerts </dev/null
openssl verify -CAfile chain.pem leaf.pem
curl -v https://host 2>&1 | grep -A2 'SSL certificate'
Prevention
Treat certificate deployment as 'deploy the full chain bundle,' not 'deploy the cert,' in every renewal script/runbook. Add an external synthetic check (from outside your own infrastructure, ideally from multiple client environments) that validates the full chain after every renewal, not just an internal check against a trust store that may already have the new intermediate cached.
💬 Comments
Symptom
Customers report certificate-expired errors at 00:00 UTC; on-call confirms — yet certbot's logs show successful renewal 20 days earlier, and the certificate file on disk is valid for two more months. Only some hosts in the fleet are affected.
Error Message
Browser/clients: NET::ERR_CERT_DATE_INVALID. openssl s_client to the affected hosts: 'notAfter' 20 days in the past — the process is serving from memory a certificate that no longer exists on disk.
Root Cause
The renewal automation replaced files but the reload step failed silently on a subset of hosts: the deploy hook invoked systemctl reload nginx, which exited nonzero on hosts where an unrelated config error (introduced weeks earlier, latent because nothing had reloaded since) made nginx -t fail — so old workers kept serving the in-memory expired cert. The renewal system logged its half green; nobody monitored 'certificate as actually served' versus 'certificate on disk'.
Diagnosis Steps
Solution
Fixed the latent config error, reloaded the fleet, and rebuilt the monitoring to measure the truth: an external probe validates the served certificate (chain, expiry) per endpoint daily, alerting on <21 days remaining regardless of what disk or renewal logs claim. Deploy hooks now fail loudly (renewal marked failed if the reload fails, paging the owning team) and reload success is verified by re-probing the endpoint post-hook.
Commands
echo | openssl s_client -connect host:443 2>/dev/null | openssl x509 -noout -dates
openssl x509 -in /etc/letsencrypt/live/site/cert.pem -noout -dates # disk truth
probe: blackbox_exporter ssl_cert_expiry < 21d -> page
Prevention
Monitor served certificates from outside — disk state and renewal logs are proxies that this incident proves can all be green during an outage. Deploy hooks are part of the renewal transaction: their failure is a renewal failure. Latent config errors mean every reload is a mini-deploy; run config validation continuously, not only at change time.
💬 Comments