Every time you visit a site with a padlock icon, a small cryptographic ceremony happens before a single byte of your page loads. That ceremony is the TLS handshake, and itβs the reason nobody between you and the server β your ISP, the coffee shop Wi-Fi, a state-level adversary β can read or tamper with the data in transit.
If youβve read our intro to HTTPS, you know the βwhat.β This post is the βhowβ β the actual mechanics of TLS 1.3, the protocol that secures the modern web.
Why HTTPS Matters
HTTP sends everything in plaintext. Passwords, cookies, API tokens, form data β all readable by anyone who can observe the network. HTTPS wraps HTTP inside a TLS tunnel, giving you three guarantees:
- Confidentiality β data is encrypted; only the client and server can read it.
- Integrity β data canβt be modified in transit without detection.
- Authentication β the server proves its identity via a certificate, so you know youβre talking to the real
example.comand not an impersonator.
Without HTTPS, things like JWTs and session cookies are just plaintext strings flying across the wire. TLS is the foundation everything else sits on.
Two Kinds of Encryption (Quick Version)
TLS uses both types of encryption, each for a different job:
Asymmetric (public-key) encryption uses a key pair β a public key anyone can have and a private key only the owner holds. Itβs mathematically expensive, but it solves a critical problem: two strangers can establish a shared secret without ever having met. TLS uses this during the handshake.
Symmetric encryption uses a single shared key for both encrypting and decrypting. Itβs fast β orders of magnitude faster than asymmetric β so TLS switches to it for the actual data transfer once both sides agree on a key.
The handshakeβs entire purpose is to use asymmetric crypto to safely agree on a symmetric key, then get out of the way.
The TLS 1.3 Handshake, Step by Step
Hereβs what happens in the ~50β100 milliseconds before your browser renders anything. TLS 1.3 completes this in a single round trip (1-RTT):
Client (Browser) Server
| |
| 1. ClientHello |
| - supported cipher suites |
| - key share (DH public value) |
| - supported TLS versions |
| ββββββββββββββββββββββββββββββββββΊ |
| |
| 2. ServerHello |
| - chosen cipher suite |
| - server key share (DH public val) |
| ββββββββββββββββββββββββββββββββββ |
| |
| 3. Encrypted Extensions |
| + Certificate |
| + CertificateVerify |
| + Finished |
| ββββββββββββββββββββββββββββββββββ |
| |
| 4. Finished |
| ββββββββββββββββββββββββββββββββββΊ |
| |
| βββ Encrypted application data βββ |
| ββββββββββββββββββββββββββββββββββΊ |
Letβs walk through each step.
1. ClientHello
The browser opens a TCP connection (after DNS resolution) and immediately sends a ClientHello message. This contains:
- Supported cipher suites β the combinations of algorithms the client can use (e.g.,
TLS_AES_256_GCM_SHA384). - Key share β a Diffie-Hellman public value. This is the big TLS 1.3 optimization: the client guesses which key exchange group the server will pick and sends its half of the key exchange upfront, before the server even responds.
- Supported versions β signals that the client supports TLS 1.3.
- SNI (Server Name Indication) β the hostname the client wants, so the server knows which certificate to present (important for shared hosting).
2. ServerHello
The server picks a cipher suite and a key exchange group from the clientβs list, then responds with:
- Chosen cipher suite β e.g.,
TLS_CHACHA20_POLY1305_SHA256. - Server key share β the serverβs half of the Diffie-Hellman exchange.
At this point, both sides independently compute the same shared secret using their private DH values and the other sideβs public DH value. No secret ever crosses the wire. This is the magic of Diffie-Hellman key exchange.
3. Server Sends Certificate + Finished
Now encrypted with keys derived from the shared secret, the server sends:
- Certificate β the serverβs X.509 certificate, containing its public key and signed by a Certificate Authority.
- CertificateVerify β a signature over the handshake transcript using the serverβs private key. This proves the server actually owns the certificate (not just copied it).
- Finished β a MAC over the entire handshake so far, confirming nothing was tampered with.
4. Client Finished
The client verifies the certificate chain, checks the signature, and sends its own Finished message. The handshake is done. Application data flows immediately.
The entire exchange: 1 round trip. The client sends, the server responds, and encrypted data can start flowing.
Certificates and Certificate Authorities
A certificate is the serverβs proof of identity. It contains the domain name, the serverβs public key, an expiration date, and a digital signature from a Certificate Authority (CA).
How trust works: Your browser and OS ship with a pre-installed list of ~100β150 trusted root CAs. When a server presents a certificate signed by one of these CAs (or by an intermediate CA that chains up to a root), the browser trusts it. If the chain is broken or the CA is unknown, you get the βYour connection is not privateβ error.
What the CA actually verified depends on the certificate type:
- Domain Validation (DV) β the CA checked that you control the domain (most common, what Letβs Encrypt issues).
- Organization Validation (OV) β the CA also verified the organizationβs legal identity.
- Extended Validation (EV) β the most thorough check, though browsers have largely stopped giving EV certs special UI treatment.
Why TLS 1.3 Is Faster Than 1.2
TLS 1.2 required 2 round trips before encrypted data could flow:
- ClientHello β ServerHello (negotiate parameters)
- Key exchange β Finished (establish keys)
TLS 1.3 collapses this to 1 round trip by having the client send its key share in the very first message. The server can respond with everything β its key share, certificate, and Finished β in a single flight.
Thereβs also 0-RTT resumption: if a client has connected to a server before, it can send encrypted application data in the very first message using a pre-shared key from the previous session. The tradeoff is that 0-RTT data is replayable, so itβs only safe for idempotent requests (like GETs).
The practical result: TLS 1.3 adds almost no perceptible latency. The βHTTPS is slowβ argument died years ago.
Letβs Encrypt and the Free Certificate Revolution
Before 2015, getting a TLS certificate meant paying a CA, dealing with manual verification, and remembering to renew. Letβs Encrypt changed everything by offering:
- Free DV certificates for any domain.
- Automated issuance and renewal via the ACME protocol (tools like
certbothandle it). - 90-day certificate lifetimes β short enough to limit damage from key compromise, with automated renewal making it painless.
Thereβs no good reason to run a public site without HTTPS anymore. Letβs Encrypt removed the last barrier.
Certificate Pinning
Certificate pinning is a technique where an application hardcodes (or βpinsβ) the expected certificate or public key for a server. Instead of trusting any certificate signed by any CA, the app only trusts a specific one.
This defends against a compromised or rogue CA issuing a fraudulent certificate for your domain. Mobile apps used this heavily, but itβs fallen out of favor for websites because:
- Itβs easy to brick your app if you rotate certificates without updating the pins.
- HTTP Public Key Pinning (HPKP) was deprecated by browsers due to the risk of site operators accidentally locking users out.
- Certificate Transparency logs now provide a public, auditable record of every certificate issued, which catches rogue certs without the brittleness of pinning.
Common TLS Errors Developers Hit
Mixed content warnings β Your page loads over HTTPS, but some resources (images, scripts, stylesheets) are loaded over plain HTTP. Browsers block or warn about this. Fix: use relative URLs or https:// everywhere. If youβre dealing with certificate issues in development, check our SSL certificate troubleshooting guide.
Expired certificates β Certificates have a validity period. If you forget to renew, browsers will refuse to connect. Letβs Encrypt + certbot with a cron job or systemd timer solves this. Always monitor your cert expiry dates.
Self-signed certificate errors β In development, you might generate your own certificate. Browsers wonβt trust it because no CA signed it. For local dev, tools like mkcert create locally-trusted certs. Never use self-signed certs in production.
Certificate name mismatch β The certificate is for example.com but youβre hitting api.example.com. Make sure your cert covers all the subdomains you need (wildcard certs like *.example.com help here).
TLS version mismatch β Older clients that only support TLS 1.0 or 1.1 canβt connect to servers that (correctly) only allow TLS 1.2+. This is a feature, not a bug β those old versions have known vulnerabilities.
The Takeaway
TLS is one of those things that works so well most developers never think about it. But understanding the handshake β the Diffie-Hellman exchange, the certificate chain, the switch from asymmetric to symmetric β makes you better at debugging connection failures, configuring servers, and reasoning about whatβs actually secure versus what just feels secure.
The protocol is elegant: use expensive asymmetric crypto once to agree on a key, then switch to fast symmetric crypto for everything else. TLS 1.3 trimmed the fat, cutting the handshake to a single round trip and removing legacy algorithms that were more liability than feature.
Next time you see that padlock, youβll know what happened in the 80 milliseconds before your page loaded.