GAP·MAP

TLS and certificates

backend · Networkingmiddlesenior~9 min read

covers the TLS 1.3 handshake · chains & trust stores · SNI & host routing · ACME issuance · expiry & rotation · mTLS & termination

[ middle depth ]

What the certificate does in the handshake

TLS gives you two things at once: a channel nobody on the path can read, and proof that the server on the other end is the host you asked for. The certificate is the proof half. It carries the server's public key and its hostnames, signed by a certificate authority (CA) the client already trusts. The crypto that turns that into a shared key (the key exchange, forward secrecy) is its own topic; here the question is operational, where the certificate sits and what you serve.

In TLS 1.3 the handshake is one round trip. The client sends its hello and a key share, the server replies with its own share, and from that point on everything is encrypted, including the certificate itself. TLS 1.2 needed two round trips and sent the certificate in the clear.

ClientServerClientHello + key share + SNIServerHello + key share[ handshake keys derived, rest is encrypted ]Certificate + CertificateVerifyFinishedFinished[ one round trip, then app data flows ]
fig · TLS 1.3 handshake - where the certificate fits

CertificateVerify is the server signing the handshake so far with the private key that matches the certificate. That is what stops anyone from replaying a captured certificate they do not own the key for.

Serve the whole chain or some clients break

A certificate is not trusted on its own. Your leaf certificate (the one with your domain) is signed by an intermediate, and the intermediate is signed by a root that ships inside the client's trust store. The client's job is to build a path from your leaf up to a root it already has.

The server must send the leaf plus every intermediate. It must not send the root: the client either has that root already or it will not trust your copy anyway.

Skip an intermediate and you get the single most common TLS misconfiguration. It looks like this: the site works in desktop Chrome, and fails in your mobile app, in curl, and in a partner's server-to-server call, all against the same hostname.

Wrong "It loads in my browser, so the certificate is fine."

Right Desktop browsers paper over the bug. Chrome, Edge, and Safari follow the AIA (Authority Information Access) URL inside the leaf to download the missing intermediate. curl, most mobile stacks, and the Android platform do not, so they fail to build the chain. Test with a client that does no AIA fetching before you call it fixed.

Reach for openssl s_client when a client reports a certificate error:

openssl s_client -connect example.com:443 -servername example.com -showcerts

Read two things in the output. The Certificate chain block lists each cert with s: (subject) and i: (issuer); the issuer of one should be the subject of the next, all the way up. The Verify return code: line at the bottom is the verdict: 0 (ok) is good, while 21 (unable to verify the first certificate) or 20 (unable to get local issuer certificate) almost always means a missing intermediate. Pass -servername so you test the certificate a real client gets, not the server's default.

Certificates expire, and expiry is an outage

A certificate has a hard expiry date. Past it, every validating client refuses the connection, and there is no grace period. An expired public certificate is a full outage for that endpoint, and it usually lands on a weekend because nobody was watching the date.

The fix is to never renew by hand. An ACME client (certbot, Caddy, cert-manager, and others) requests and renews certificates automatically, well before expiry. Let's Encrypt certificates last 90 days and clients are expected to renew around the 60-day mark, so the whole cycle runs without a human. Automate it once and expiry stops being a thing you track.

Two operational habits follow from that. Monitor days-to-expiry as its own alert, not as part of "is the app up," because those are different questions (the next section on the senior side shows how they came apart in a real outage pattern). And when you rotate a certificate, remember it lives wherever TLS terminates: if a load balancer or CDN holds the certificate, the new one has to land there too, on the same box the old one lived on.

[ senior depth ]

What 1.3 changed, and what it did not

The middle note calls the TLS 1.3 handshake "one round trip." That is the full handshake: ClientHello with a key share, ServerHello with the matching share, then EncryptedExtensions, Certificate, CertificateVerify, and Finished, all encrypted under handshake keys the two sides can derive right after ServerHello. TLS 1.2 took two round trips and sent the certificate in cleartext, so a passive observer learned which certificate (and often which host) you were talking to. Restructuring the state machine to fold authentication into the first flight is most of where the latency went.

Two caveats a senior should keep straight. Resumption and 0-RTT (early data on a resumed session) change the security properties as well as the speed, and those properties (forward secrecy, replay) sit in encryption-fundamentals; do not claim every byte on a 1.3 connection has identical guarantees. And the certificate arriving encrypted protects it from a passive eavesdropper, though the client still has to validate it, which is where most production failures actually live.

Trust stores belong to the client

Validation is path building. The client starts from your leaf and tries to reach a root it already trusts, using the intermediates you sent to bridge the gap. The trust store that anchors this belongs to the client (the OS, the browser, the JVM, a container base image), and different clients ship different roots. A chain that validates on your laptop can fail on an old Android build or a minimal Alpine image whose CA bundle is stale or trimmed.

One leaf can have more than one valid chain. CAs cross-sign intermediates so a newer root and an older, more widely distributed root both lead to the same leaf, and you choose which chain to serve. The tradeoff is compatibility against size: the longer chain reaches older clients but costs bytes in every handshake. When a CA rotates roots (Let's Encrypt has moved between ISRG roots), the chain your ACME client fetches can change under you, so pin the behavior you tested rather than assuming today's default chain is permanent.

Wrong "The certificate validated in CI, so the chain is correct everywhere."

Right It validated against CI's trust store. Name the clients that matter (oldest supported mobile OS, any non-browser integration, your own base image) and test the chain against those, because none of them do the AIA fetching that hides a missing intermediate in a desktop browser.

SNI is plaintext, and it does the routing

The client puts the hostname it wants in the SNI extension of the ClientHello, in the clear, before any key is agreed. That is what lets one IP and one edge server host many domains: the edge reads SNI and picks the certificate (and often the backend) for that name. It is also a privacy leak. An observer cannot read your traffic, but SNI tells them which site you reached, and it is the main thing still exposed on a 1.3 connection.

Encrypted Client Hello (ECH) closes that gap by encrypting the whole ClientHello under a key the server publishes in DNS. It was standardized as RFC 9849 (published March 2026) and ships in some browsers and CDNs, but it is far from universal and depends on the DNS record being present, so treat SNI as visible in any current threat model. When you debug, this is also why -servername is not optional: omit it and you test the edge's default certificate, not the one a real client with SNI would be served.

Rotation is the real problem; revocation barely helps

Expiry is an outage class, and the industry's answer is shorter lifetimes. The public maximum is falling fast: the CA/Browser Forum has adopted a schedule (ballot SC-081v3) cutting it from 398 days toward 47 by 2029, and Let's Encrypt already offers opt-in six-day certificates alongside the 90-day default. At six-day or 47-day lifetimes, manual renewal cannot keep up and neither can a once-a-quarter job; renewal has to be continuous and boring, at roughly a third to two thirds of the lifetime, with monitoring on days-to-expiry as a distinct signal from application health.

Short lifetimes are also the revocation strategy now. Online revocation checking never worked well: browsers soft-fail (if the OCSP responder is unreachable they proceed), so a stolen key was rarely stopped by it, and OCSP leaked the visited host to the CA. Let's Encrypt shut its OCSP service down on 6 August 2025 over that privacy cost and now publishes revocation only via CRLs. The practical consequence: you cannot rely on a mis-issued or key-compromised certificate being rejected mid-life, so a short lifetime is what actually bounds the exposure. Design rotation so that cutting a certificate's life is cheap.

mTLS and termination: where the key lives, what it costs

Mutual TLS (mTLS) adds a client certificate to the handshake: the server sends a CertificateRequest, and the client answers with its own certificate and a CertificateVerify signed by its key. The server now authenticates the caller cryptographically instead of trusting a bearer token, which is why it anchors service-to-service auth in zero-trust networks and is the default inside service meshes. A stolen bearer token works for anyone; a stolen mTLS identity is bound to a key the attacker does not have.

The cost is certificate management multiplied by every workload. Five hundred services at three replicas is 1500 identities, each needing issuance, rotation, and a trust bundle, and hand-distributing client certs at that scale is where mTLS projects die. The working answer is automation: SPIFFE/SPIRE or a mesh control plane runs an internal CA that mints short-lived per-workload certificates (SVIDs) and rotates them through the sidecars, with no human in the loop. Note the boundary: this is machine-to-machine. It has nothing to do with end users, and requiring browsers to present client certificates is a different, much more painful thing that most consumer products avoid.

Where TLS terminates decides where the private key sits and how far plaintext travels.

  • Edge termination: the CDN or load balancer terminates and forwards plain HTTP to the origin. Fast, and the edge can do L7 routing, caching, and WAF, but the internal hop is cleartext and the edge holds your private key.
  • Re-encryption: the edge terminates, then opens a second TLS connection the origin also terminates. The origin needs its own certificate and key, and you pay a second handshake, in exchange for no plaintext hop.
  • Passthrough: the origin terminates the client's TLS directly and the edge only forwards bytes. End to end, but the edge is blind, so it cannot route on the hostname or path or add L7 features.

The senior instinct is to say the sentence out loud for each hop: a TLS connection protects records only between its two endpoints. Every termination point ends one connection and starts a separate decision about the next, including which principal is trusted to hold the key there.

dig deeper

Primary sources behind these notes - the specs and official docs worth reading in full.

gapmap pro

You've read the TLS and certificates notes. An interviewer will ask you to prove them.

Know you're ready. Don't hope.

The notes are free: every topic, senior depth. Pro is what turns reading into readiness you can prove:

  • Mock interviews on your topics

    An AI interviewer that pushes back with follow-ups and grades you honestly. Per module, fair-use unlimited.

  • Voice test interviews

    The dress rehearsal, out loud: questions from your map, a transcript, and a per-answer verdict.

  • A plan to your date

    Ready or not ready, per module, as the interview approaches. Recomposed whenever your goal changes.

  • A mentor inside every lesson

    Stuck on the senior notes? Ask, drill deeper, get re-quizzed on the spot.

Pro $20/mo · Pro+ $50/mo · every study note on the site stays free

builds on

more Networking

was this useful?