A developer-focused walkthrough of the TLS 1.3 handshake, certificate trust, forward secrecy, and how to debug the errors you actually hit.
Every time you load an https:// URL, your browser and the server run a short negotiation before a single byte of your request is sent. That negotiation is the TLS handshake. Most developers treat it as a black box until something breaks, and then a cryptic certificate error blocks a deploy. This post opens the box so you can reason about it.
TLS (the modern name for what people still call SSL) provides three guarantees on top of a plain TCP connection.
Encryption: Nobody between you and the server can read the traffic. A coffee-shop network, an ISP, or a compromised router sees only ciphertext.
Integrity: Nobody can silently modify the traffic. If a byte is flipped in transit, the receiver detects it and tears down the connection.
Authentication: You are talking to the real server, not an impostor. This is the part certificates handle, and it is the part that fails most often.
The clever trick is combining two kinds of cryptography. Asymmetric (public-key) math is used briefly to agree on a shared secret, because it lets two strangers establish a key over an open wire. Then both sides switch to symmetric encryption for the actual data, because symmetric ciphers like AES are far faster. The handshake is the bridge between these two worlds.
TLS 1.3 is the current default and it completes in one round trip (1-RTT). Here is the flow.
1. ClientHello. The client opens the conversation and says what it supports: a list of cipher suites, the TLS versions it speaks, and, crucially, a key share. Instead of waiting to be told which key-exchange group to use, the client guesses a reasonable one (usually an elliptic-curve group) and sends its ephemeral public key immediately. It also includes the Server Name Indication (SNI), the hostname it wants to reach, so a server hosting many sites knows which certificate to present.
2. ServerHello. The server picks a cipher suite from the client's list and replies with its own key share. Because both sides now hold each other's public key, they can each compute the same shared secret using ECDHE (Elliptic Curve Diffie-Hellman Ephemeral). At this point the server also sends its certificate and a Finished message, and everything after the ServerHello is already encrypted.
3. Client verification and Finished. The client verifies the server's certificate chain (more on that below), confirms the server's Finished message, and derives the same session keys. It sends its own Finished, and encrypted application data starts flowing.
That is one round trip: hello out, hello back, then real data. The key exchange, cipher selection, and certificate delivery all happen in the same pass.
TLS 1.2 needed two round trips. The client sent a ClientHello with only its cipher list, the server responded with its choice and certificate, and only then did the client send its key material, followed by another exchange of Finished messages before data could flow. TLS 1.3 collapses this by having the client speculatively send a key share in the first message and by trimming the cipher suites down to a small set of secure options. The result is faster connections and fewer footguns.
Authentication rests on a chain. Your server presents a leaf certificate for its hostname. That leaf is signed by an intermediate certificate, which is signed by a root certificate authority (CA). Your operating system and browser ship with a list of trusted roots.
To validate, the client walks the chain: the leaf must be signed by the intermediate, the intermediate by a trusted root, none may be expired, and the leaf's name must match the hostname you requested. If every link holds, the server is trusted. A common misconfiguration is a server that sends its leaf but forgets the intermediate, so clients that lack it cached cannot complete the chain.
Forward secrecy: Because ECDHE keys are ephemeral (generated fresh per connection and thrown away), an attacker who records your encrypted traffic today and steals the server's private key next year still cannot decrypt those old sessions. The keys that protected them no longer exist. TLS 1.3 makes forward secrecy mandatory.
SNI: Since the hostname travels in the ClientHello, one IP address can host thousands of TLS sites. The tradeoff is that classic SNI is sent in the clear, which is why Encrypted Client Hello exists to close that gap.
Session resumption and 0-RTT: After a successful handshake the server can hand the client a ticket. On the next visit the client presents the ticket and skips most of the negotiation. TLS 1.3 can even send early data in the very first flight (0-RTT). The caveat: 0-RTT data can be replayed by an attacker who captures it, so you should only send idempotent requests that way, never something like a payment.
Expired certificate: The leaf's validity window has passed. Automate renewal so this never surprises you at 2 a.m.
Hostname mismatch: The certificate is for example.com but you connected to www.example.com and it is not listed. Check the Subject Alternative Name entries.
Untrusted CA or self-signed: The chain does not terminate at a root the client trusts. Common in internal environments; fix it by installing the internal root, not by disabling verification in production.
Protocol or cipher mismatch: An old client and a hardened server share no common version or cipher suite, and the handshake aborts before any certificate is checked.
The fastest way to see what a server actually presents is openssl:
openssl s_client -connect example.com:443 -servername example.com
This prints the full certificate chain, the negotiated protocol and cipher, and any verification errors near the top (Verify return code). The -servername flag sends SNI, which you need on multi-site hosts. To confirm the negotiated version quickly, curl is handy:
curl -v https://example.com 2>&1 | grep -Ei 'SSL connection|subject|issuer'
The SSL connection using TLSv1.3 line and the subject and issuer of the certificate tell you at a glance whether trust and version look right.
Serve TLS 1.3 with TLS 1.2 as a fallback, disable everything older, and let the browser and server pick a modern cipher. Automate certificate renewal so expiry is never a manual step, and always send the full chain including intermediates. Reserve 0-RTT for genuinely idempotent requests. When a handshake fails, reach for openssl s_client before you guess, because the verification code usually names the exact problem.
For the wider context on how this sits above TCP and DNS, see our networking fundamentals guide. And once TLS is solid, harden what rides on top with proper HTTP security headers.
Get the latest tutorials, guides, and insights on AI, DevOps, Cloud, and Infrastructure delivered directly to your inbox.
Explore more articles in this category
A practical, step-by-step guide to putting Nginx in front of your app for TLS, routing, and load balancing.
You don't need a CCNA to ship reliable services, but you do need the core ideas. This is the map: DNS, TCP, TLS, proxies, and CDNs, minus the jargon.
A practical tour of the core load balancing algorithms, how each distributes traffic, and when to reach for one over another.
Evergreen posts worth revisiting.