How HTTPS and TLS Really Work: Handshake and Certificates

Security2026-10-27TryQuickToolBox

You add an SSL certificate, point your domain at a server, and the padlock appears. Most developers stop there. Then one day an API client fails with UNABLE_TO_VERIFY_LEAF_SIGNATURE, a mobile app rejects your certificate on older Android, or a load balancer terminates TLS and your app sees everything as plain HTTP. Suddenly you need to know what actually happens between the TCP connection and the first HTTP request.

This article walks through how HTTPS and TLS really work: what the handshake does, how certificates are validated, what changed in TLS 1.3, and how to diagnose the errors you will actually encounter.

What HTTPS Actually Adds

HTTPS is HTTP sent over a TLS connection. TLS provides three guarantees:

Note what is not on that list: the client is not authenticated by default. TLS authenticates the server; your application authenticates the user.

The TLS 1.2 Handshake, Step by Step

TLS 1.2 is still common on older infrastructure and in many corporate proxies, so it is worth understanding. A full handshake looks like this:

  1. ClientHello — the client sends supported TLS versions, a random value, a list of cipher suites, and (if applicable) the SNI hostname.
  2. ServerHello — the server picks a version and cipher suite, returns its own random value, and sends its certificate chain.
  3. Key exchange — with ECDHE, the server sends an ephemeral public key; the client sends its own. Both derive the same shared secret. The server signs the handshake transcript with its certificate's private key.
  4. Finished — both sides send a MAC over the handshake transcript, confirming nothing was tampered with.
  5. Application data — HTTP requests and responses flow encrypted.

The critical detail is ephemeral key exchange. With ECDHE, a new key pair is generated per connection. Even if an attacker records years of traffic and later steals the server's private key, they cannot decrypt past sessions. That property is called forward secrecy, and it is the main reason static RSA key exchange has been deprecated.

How TLS 1.3 Changed the Game

TLS 1.3 simplifies the handshake to a single round trip and removes legacy algorithms entirely.

AspectTLS 1.2TLS 1.3
Round trips (full handshake)21
Key exchangeRSA or ECDHEECDHE only (forward secret)
Cipher suitesSeparate cipher, MAC, key exchangeAEAD suites only, e.g. TLS_AES_128_GCM_SHA256
Static RSA, CBC, RC4AllowedRemoved
Session resumptionSession IDs / ticketsPSK with 0-RTT option

In TLS 1.3 the client guesses the key exchange group and sends its share in the first message, so the server can complete key agreement immediately. Certificates are still sent, but they are now encrypted, which means passive observers can no longer see which hostname is being served — only the SNI in the ClientHello leaks it unless Encrypted Client Hello is in use.

0-RTT resumption lets a returning client send application data with the first flight. It is fast, but 0-RTT data is replayable, so never use it for non-idempotent requests like payments.

Certificates and Chain Validation

A certificate binds a public key to a hostname. Validation is a chain walk:

The most common cause of unable to verify errors is a missing intermediate certificate. Browsers sometimes paper over this by caching intermediates, but command-line tools and mobile apps fail hard. Always serve the full chain, leaf first, then intermediates — never the root.

Practical Debugging with OpenSSL

When something breaks, inspect what the server actually sends before blaming the client.

# Show the certificate chain the server presents
openssl s_client -connect example.com:443 -servername example.com -showcerts

# Negotiate only TLS 1.3 and list the chosen cipher
openssl s_client -connect example.com:443 -tls1_3

# Check certificate expiry in one line
echo | openssl s_client -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -dates -subject -ext subjectAltName

If s_client reports verify error:num=21:unable to verify the first certificate, the chain is incomplete. If it reports a hostname mismatch, check the SAN list — wildcard certificates cover one label only, so *.example.com matches api.example.com but not a.b.example.com.

Common Misconfigurations

A Reasonable Server Configuration

For Nginx, a modern baseline looks like this:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options "nosniff" always;

Let the server choose ciphers in TLS 1.3 — client preference there is fine because the suites are all strong. Keep session tickets off unless you rotate ticket keys, since a leaked ticket key can compromise resumption.

FAQ

Does HTTPS encrypt the hostname?

Not by default. The SNI extension in the ClientHello is sent in plaintext so the server knows which certificate to present. Encrypted Client Hello (ECH) hides it, but adoption is still limited. The path and query string are always encrypted.

Why does my certificate work in a browser but fail in curl or a mobile app?

Almost always a missing intermediate certificate. Browsers often fetch or cache intermediates on their own, while stricter clients require the server to send the full chain. Verify with openssl s_client -showcerts.

Is TLS 1.3 always faster than TLS 1.2?

For new connections, yes — one round trip instead of two. For resumed connections the gap narrows, and 0-RTT can make TLS 1.3 faster still, at the cost of replay risk on non-idempotent requests.

If you are auditing a web server after a TLS change, log analysis often reveals the real impact: broken redirects, unexpected 4xx spikes, or clients still hitting HTTP. You can drop an access log into the Nginx log analyzer to see status code distributions and spot clients that never completed the upgrade to HTTPS.