Networking Fundamentals for DevOps Engineers

The TLS Handshake — What Happens When You Type HTTPS

Your API returns 502. The load balancer logs say: SSL handshake failed. Your on-call partner says "just restart nginx." But the error comes back.

You need to understand what the TLS handshake IS — what messages are exchanged, what can go wrong at each step, and what "handshake failed" actually means — before you can fix what broke.

This lesson walks through every step of the TLS handshake, from the first byte to encrypted data. By the end, you will be able to read openssl s_client output like a book and diagnose handshake failures in minutes instead of hours.


Part 1: What the Handshake Accomplishes

Before a single byte of HTTP data is encrypted, the client and server must agree on three things:

  1. Which cryptographic algorithms to use (cipher suite negotiation)
  2. That the server is who it claims to be (certificate authentication)
  3. A shared secret key for encrypting data (key exchange)

All of this happens before the first HTTP request. The handshake is overhead — pure latency with no application data — so speed matters enormously. Every extra round trip adds real milliseconds to every new connection.

KEY CONCEPT

The TLS handshake adds 1-2 round trips before any HTTP data can flow. On a connection with 100ms round-trip time, that is 100-200ms of latency added to the first request. This is why TLS 1.3 reduced the handshake to 1 round trip, and why HTTP/2 and HTTP/3 aggressively reuse connections to avoid repeated handshakes.


Part 2: The TLS 1.2 Handshake — Step by Step

TLS 1.2 is still widely deployed and understanding it makes TLS 1.3 improvements obvious. The full handshake requires 2 round trips.

Round Trip 1: Hello and Certificate

Client sends ClientHello:

  • Supported TLS versions (e.g., TLS 1.0, 1.1, 1.2)
  • List of supported cipher suites (ordered by preference)
  • A random 32-byte value (Client Random)
  • Supported compression methods
  • Extensions (SNI hostname, supported groups, signature algorithms)
ClientHello
  Version: TLS 1.2
  Random: 7b2e5f...  (32 bytes)
  Cipher Suites (17 suites):
    TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
    ...
  Extensions:
    server_name: devopsbeast.com
    supported_groups: x25519, secp256r1
    signature_algorithms: ecdsa_secp256r1_sha256, rsa_pss_rsae_sha256
PRO TIP

The SNI (Server Name Indication) extension in ClientHello is how a single IP address can serve TLS certificates for multiple domains. The client tells the server which hostname it wants BEFORE the certificate is selected. Without SNI, virtual hosting over HTTPS would not work. Note that SNI is sent in plaintext — encrypted SNI (ECH) is still being standardized.

Server responds with ServerHello + Certificate + ServerKeyExchange + ServerHelloDone:

  • ServerHello: chosen TLS version, chosen cipher suite, Server Random (32 bytes)
  • Certificate: the server's X.509 certificate chain (leaf + intermediates)
  • ServerKeyExchange: the server's ECDHE public key (for key exchange)
  • ServerHelloDone: signals the server is finished
ServerHello
  Version: TLS 1.2
  Random: 4a1c8d...  (32 bytes)
  Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384

Certificate
  Subject: CN=devopsbeast.com
  Issuer: CN=R3, O=Let's Encrypt
  Validity: 2026-03-01 to 2026-05-30

ServerKeyExchange
  Named Curve: x25519
  Public Key: 3e7f2a...  (32 bytes)

ServerHelloDone

Round Trip 2: Key Exchange and Finish

Client sends ClientKeyExchange + ChangeCipherSpec + Finished:

  • ClientKeyExchange: the client's ECDHE public key
  • Both sides now compute the pre-master secret from the ECDHE exchange, then derive the master secret and session keys
  • ChangeCipherSpec: "I am switching to encrypted mode now"
  • Finished: a hash of all handshake messages so far, encrypted with the new keys (proves no tampering)

Server sends ChangeCipherSpec + Finished:

  • ChangeCipherSpec: "I am also switching to encrypted mode"
  • Finished: server's hash of all handshake messages, encrypted

After both Finished messages are verified, the handshake is complete. Application data flows encrypted.

TLS 1.2 Full Handshake — 2 Round Trips

Click each step to explore

The Math: Handshake Latency

NetworkRound Trip TimeTLS 1.2 HandshakeTotal Before First Byte
Same datacenter1 ms2 ms~3 ms (+ TCP handshake)
Same region10 ms20 ms~30 ms
Cross-continent100 ms200 ms~300 ms
Mobile (4G)50-150 ms100-300 ms~150-450 ms
WARNING

On high-latency connections (mobile, cross-continent), the TLS 1.2 handshake adds hundreds of milliseconds to every new connection. This is a real user experience problem. It is the primary reason TLS 1.3 was designed with a 1-round-trip handshake, and why HTTP/2 multiplexes requests over a single connection to avoid repeated handshakes.


Part 3: The TLS 1.3 Handshake — Faster and More Secure

TLS 1.3 (RFC 8446, published 2018) is a significant redesign. It reduces the handshake to 1 round trip and removes insecure options that existed in TLS 1.2.

What Changed

The key insight: instead of negotiating the key exchange algorithm first, then exchanging keys, TLS 1.3 combines both steps. The client guesses which key exchange the server will accept and sends its key share in the very first message.

Client sends ClientHello + key_share:

  • Supported cipher suites (TLS 1.3 only has 5 cipher suites, all secure)
  • Client's ECDHE public key (sent immediately, not after negotiation)
  • Supported key exchange groups

Server sends ServerHello + key_share + EncryptedExtensions + Certificate + CertificateVerify + Finished:

  • All in one flight
  • Server's ECDHE public key
  • Certificate and signature (proving identity)
  • Finished (handshake integrity)

Client sends Finished:

  • Verifies server's Finished
  • Sends its own Finished

Application data can flow immediately after the client sends Finished. 1 round trip total.

TLS 1.2 vs TLS 1.3 Handshake

TLS 1.2

2 round trips to first encrypted data

Round trips2 (plus TCP handshake)
Messages~10 separate messages
Key exchangeNegotiated, then exchanged
Cipher suites37+ options (many insecure)
RSA key exchangeSupported (no forward secrecy)
CompressionSupported (vulnerable to CRIME)
RenegotiationSupported (complex, attack surface)
TLS 1.3

1 round trip to first encrypted data

Round trips1 (plus TCP handshake)
Messages~6 messages, mostly encrypted
Key exchangeKey share sent in ClientHello
Cipher suites5 options (all secure)
RSA key exchangeRemoved (forward secrecy mandatory)
CompressionRemoved (attack surface eliminated)
RenegotiationRemoved (replaced with post-handshake auth)

What TLS 1.3 Removed

TLS 1.3 is not just faster — it is more secure because it removed dangerous features:

  • Static RSA key exchange: no forward secrecy. Removed.
  • CBC mode ciphers: vulnerable to padding oracle attacks. Removed.
  • RC4, DES, 3DES: weak ciphers. Removed.
  • Compression: vulnerable to CRIME/BREACH attacks. Removed.
  • Renegotiation: complex state machine, attack surface. Removed.
KEY CONCEPT

TLS 1.3 has only 5 cipher suites, and all of them are considered secure. There are no configuration choices that lead to weak encryption. Compare this to TLS 1.2, where a misconfigured server could negotiate RC4 or 3DES. TLS 1.3 made it nearly impossible to configure TLS badly.

0-RTT Resumption — Sending Data Before the Handshake Completes

For repeat connections (where the client has connected to this server before), TLS 1.3 supports 0-RTT resumption:

  1. On the first connection, the server sends the client a session ticket
  2. On the next connection, the client includes early data encrypted with a key derived from the session ticket
  3. The server can process this data before the handshake completes
Connection 1: Normal TLS 1.3 handshake (1-RTT)
  → Server sends session ticket after handshake

Connection 2: Client sends ClientHello + early data (0-RTT)
  → Server processes early data IMMEDIATELY
  → Handshake completes in background
WARNING

0-RTT data is vulnerable to replay attacks. An attacker who records the 0-RTT data can resend it to the server, and the server may process it again. For this reason, 0-RTT should only be used for idempotent requests (GET, HEAD) — never for state-changing operations (POST, PUT, DELETE). Most servers disable 0-RTT by default for safety.


Part 4: Cipher Suites Decoded

A cipher suite is a named combination of algorithms for each part of the TLS connection. Understanding the naming tells you exactly what is happening.

TLS 1.2 Cipher Suite Format

TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
│    │     │        │   │   │   │
│    │     │        │   │   │   └─ Hash: SHA-384 (for HMAC/PRF)
│    │     │        │   │   └───── Mode: GCM (authenticated encryption)
│    │     │        │   └───────── Key size: 256-bit
│    │     │        └───────────── Cipher: AES (symmetric encryption)
│    │     └────────────────────── Authentication: RSA (certificate type)
│    └──────────────────────────── Key Exchange: ECDHE (ephemeral Diffie-Hellman)
└───────────────────────────────── Protocol: TLS

TLS 1.3 Cipher Suite Format (Simplified)

TLS_AES_256_GCM_SHA384
│    │   │   │   │
│    │   │   │   └─ Hash: SHA-384
│    │   │   └───── Mode: GCM
│    │   └───────── Key size: 256-bit
│    └───────────── Cipher: AES
└────────────────── Protocol: TLS

TLS 1.3 cipher suites are shorter because key exchange is always ECDHE and authentication is determined by the certificate type, not the cipher suite.

The 5 TLS 1.3 cipher suites:

Cipher SuiteEncryptionHashNotes
TLS_AES_256_GCM_SHA384AES-256-GCMSHA-384Most common, strong
TLS_AES_128_GCM_SHA256AES-128-GCMSHA-256Slightly faster
TLS_CHACHA20_POLY1305_SHA256ChaCha20-Poly1305SHA-256Better on devices without AES-NI
TLS_AES_128_CCM_SHA256AES-128-CCMSHA-256IoT use cases
TLS_AES_128_CCM_8_SHA256AES-128-CCM-8SHA-256IoT, shorter tag
PRO TIP

ChaCha20-Poly1305 is the cipher suite designed for mobile devices and ARM processors that lack hardware AES acceleration. On x86 servers with AES-NI, AES-256-GCM is faster. On mobile phones and ARM-based edge devices, ChaCha20 can be 3x faster. Modern TLS libraries automatically select the optimal cipher based on hardware.


Part 5: What "SSL Handshake Failed" Actually Means

When you see "SSL handshake failed" in your logs, it means the client and server could not complete the handshake. Here are the four main causes:

1. Protocol Version Mismatch

The client and server do not support any common TLS version.

# Server only supports TLS 1.0/1.1 (ancient, insecure)
# Client requires TLS 1.2+ (modern browsers, Go 1.18+, Python 3.10+)
# Result: handshake failure

# Test what versions a server supports
openssl s_client -connect example.com:443 -tls1_2
# If this succeeds but -tls1_3 fails, the server does not support TLS 1.3

2. Cipher Suite Mismatch

The client and server have no cipher suites in common.

# Force a specific cipher to test
openssl s_client -connect example.com:443 -cipher ECDHE-RSA-AES256-GCM-SHA384

# If the server only accepts ciphers the client does not support: handshake failure

3. Certificate Rejected

The client does not trust the server's certificate (expired, wrong domain, untrusted CA, incomplete chain).

# Test and see certificate details
openssl s_client -connect example.com:443 -servername example.com 2>&1 | head -30
# Look for: "Verify return code: 0 (ok)" or an error code

4. Client Certificate Required (mTLS)

The server requires a client certificate (mutual TLS) but the client did not provide one.

# Connect with a client certificate for mTLS
openssl s_client -connect example.com:443 \
  -cert client.crt \
  -key client.key \
  -CAfile ca.crt
WAR STORY

At 3 AM, a payment gateway integration broke with "SSL handshake failed." The vendor had rotated their TLS certificate and the new one used an intermediate CA that was not in our custom CA bundle. Our application pinned to the old CA chain. The fix was updating the CA bundle, but finding the root cause took two hours because the error message just said "handshake failed" with no detail. Lesson learned: always use openssl s_client as your first debugging step — it shows you exactly where in the handshake things broke.


Part 6: Debugging with openssl s_client

The openssl s_client command is the single most important TLS debugging tool. It simulates a TLS client handshake and shows you everything that happened.

# Basic connection test
openssl s_client -connect devopsbeast.com:443 -servername devopsbeast.com

# Key lines in the output:
# ---
# Certificate chain
#  0 s:CN = devopsbeast.com          ← leaf cert (your server)
#    i:C = US, O = Let's Encrypt, CN = R3  ← signed by Let's Encrypt R3
#  1 s:C = US, O = Let's Encrypt, CN = R3  ← intermediate cert
#    i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
# ---
# Server certificate (PEM encoded)
# ---
# SSL handshake has read 3471 bytes and written 399 bytes
# ---
# New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
# Server public key is 256 bit
# Server Temp Key: X25519, 253 bits
# ---
# Verify return code: 0 (ok)          ← 0 means chain verified successfully
KEY CONCEPT

The "Verify return code" at the bottom of openssl s_client output is the single most important line. Code 0 means the certificate chain verified successfully. Any other code tells you exactly what is wrong: 10 = expired, 18 = self-signed, 19 = self-signed in chain, 20 = unable to get local issuer, 21 = unable to verify first certificate. Memorize codes 0, 10, 20, and 21 — they cover 90% of certificate errors.

# Show the full certificate chain
openssl s_client -showcerts -connect devopsbeast.com:443 -servername devopsbeast.com

# Test with a specific TLS version
openssl s_client -connect devopsbeast.com:443 -tls1_3

# Test with a specific cipher
openssl s_client -connect devopsbeast.com:443 -ciphersuites TLS_AES_256_GCM_SHA384

# Extract just the server certificate to a file
openssl s_client -connect devopsbeast.com:443 -servername devopsbeast.com 2>/dev/null \
  | openssl x509 -out server.crt

# Check certificate expiry from a remote server
echo | openssl s_client -connect devopsbeast.com:443 -servername devopsbeast.com 2>/dev/null \
  | openssl x509 -noout -dates
# notBefore=Mar  1 00:00:00 2026 GMT
# notAfter=May 30 23:59:59 2026 GMT
PRO TIP

Always use -servername with openssl s_client. Without it, no SNI extension is sent, and the server may return the wrong certificate (or a default certificate). This is one of the most common reasons people say "OpenSSL shows a different cert than my browser" — they forgot -servername.


Key Concepts Summary

  • The TLS handshake establishes cipher suite, authenticates the server, and exchanges keys — all before any HTTP data flows
  • TLS 1.2 requires 2 round trips — ClientHello/ServerHello, then key exchange/finish
  • TLS 1.3 requires 1 round trip — the client sends its key share in the first message, cutting latency in half
  • TLS 1.3 removed insecure features — static RSA, CBC ciphers, RC4, 3DES, compression, renegotiation
  • 0-RTT resumption allows data in the first packet for repeat connections, but is vulnerable to replay attacks
  • Cipher suites name every algorithm used: key exchange, authentication, encryption, and hash
  • Handshake failures happen for four reasons: version mismatch, cipher mismatch, certificate rejected, or client cert missing
  • openssl s_client is the essential debugging tool — it shows the full handshake, certificate chain, and verification result
  • Verify return code 0 means the chain is valid; any other code tells you exactly what is wrong

Common Mistakes

  • Not using -servername with openssl s_client, causing it to return the wrong certificate on servers with multiple domains
  • Confusing TLS version in the protocol with TLS version in the cipher suite — the negotiated version is what matters
  • Assuming "SSL handshake failed" is a certificate problem — it could be a version or cipher mismatch instead
  • Enabling 0-RTT for all requests including POST/PUT — 0-RTT is only safe for idempotent operations
  • Not testing TLS configuration changes before deploying — use openssl s_client to verify the new config accepts connections
  • Ignoring handshake latency on high-RTT connections — 200ms per new connection adds up fast on mobile networks

What is Next

You now understand what happens during the TLS handshake and how to debug handshake failures. But the handshake depends on one critical piece: the server's certificate. How does the client decide whether to trust it?

In the next lesson, we will open up an X.509 certificate and examine every field. You will learn how certificate chains work, why the "missing intermediate" error is the most common TLS problem, and how Let's Encrypt changed the certificate landscape forever.

KNOWLEDGE CHECK

What is the primary advantage of TLS 1.3 over TLS 1.2?