Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

The TLS 1.3 Handshake Explained

By Kokil Thapa | Last reviewed: September 2026

Every HTTPS request your browser or API client sends starts with a negotiation most developers never inspect. The TLS 1.3 handshake explained here walks through that negotiation message by message, so you can debug slow connections, certificate errors, and cipher mismatches on real servers. If you maintain Linux production hosting, Laravel APIs, or client portals that handle sensitive documents, understanding this handshake is not academic—it directly affects latency, security, and compliance. This guide maps the wire protocol to the config files you actually edit.

What happens during the TLS 1.3 handshake step by step?

Transport Layer Security sits between TCP and HTTP. Before any GET or POST payload travels, client and server agree on protocol version, authentication, and encryption keys. TLS 1.3, defined in RFC 8446, rewrote that negotiation to be faster and stricter than TLS 1.2.

The handshake has two visible phases on the wire: key exchange and authenticated encryption setup. Application data never flows in cleartext. Even the server's certificate chain travels inside encrypted records after the first few messages.

TLS 1.3 Handshake — 1 Round TripClientBrowser / APIServerNginx / Apache1. ClientHello + key_share2. ServerHello + key_share3. EncryptedExtensions4. Certificate + verify5. Finished (both sides)6. Encrypted HTTP — AES-256-GCM or ChaCha20Application data protected by derived session keys
TLS 1.3 handshake message sequence: one client flight, one server flight, then encrypted traffic

Step 1 — ClientHello

The client opens the TCP connection and immediately sends ClientHello. That single message carries the highest TLS version the client supports, a list of cipher suites, extensions, and—critically—a key_share extension with an ephemeral public key.

Common extensions include Server Name Indication (SNI) for virtual hosts, supported groups (X25519, secp256r1), and signature algorithms. The client also advertises whether it supports TLS 1.3 exclusively or falls back to 1.2.

Step 2 — ServerHello and key agreement

The server picks TLS 1.3 if both sides support it. It selects a cipher suite, echoes a compatible group, and returns its own key_share public value. Both parties now run an Elliptic Curve Diffie-Hellman (ECDH) computation locally.

Neither side transmits the shared secret across the wire. An eavesdropper who captures every packet still cannot derive session keys without breaking the elliptic-curve problem.

Step 3 — Encrypted handshake records

From this point forward, remaining handshake messages ride inside encrypted records. The server sends EncryptedExtensions, its certificate chain, a CertificateVerify signature, and Finished. The client responds with its own Finished message.

Finished messages are HMACs over the entire handshake transcript. If anyone tampered with ClientHello or ServerHello bytes, verification fails and the connection aborts.

Step 4 — Application data

Once both Finished messages exchange successfully, derived keys protect HTTP headers and bodies. For a typical page load, the browser may reuse the session through resumption or pre-shared keys on subsequent visits.

On legal-tech portals I have shipped—such as client document portals with payment flows—this handshake runs before every form submission. A misconfigured intermediate certificate or weak cipher shows up here, not in your Laravel controller.

How does TLS 1.3 reduce handshake latency compared to TLS 1.2?

TLS 1.2 needed two full round trips before application data in the common full-handshake case. TLS 1.3 compresses the core exchange into one round trip (1-RTT). That difference matters on mobile networks in Nepal and on international API calls where RTT can exceed 200 ms.

TLS 1.3 also removed obsolete steps. Renegotiation, static RSA key transport, and many legacy cipher suites are gone. Fewer branches mean fewer bytes and less CPU on both ends.

Round Trips Before HTTP DataTLS 1.2 — 2-RTTTLS 1.3 — 1-RTTRTT 1: ClientHello ↔ ServerHelloRTT 2: Key exchange + FinishedThen HTTP responseRTT 1: Hello + keys + certThen HTTP response~2 × RTT delay~1 × RTT delay0-RTT resumption trades speed for replay risk — use with care
TLS 1.3 cuts full-handshake latency roughly in half by eliminating an entire round trip

Session resumption and 0-RTT

When a client reconnects to a server it recently visited, TLS 1.3 can resume using a pre-shared key (PSK) from the prior session. In the best case, the client sends encrypted application data in its first flight—zero round trips before data, called 0-RTT.

0-RTT is fast but dangerous for non-idempotent requests. An attacker who captures a 0-RTT blob can replay it. Never enable 0-RTT on endpoints that create orders, transfer funds, or mutate server state without explicit replay protection.

Impact on Core Web Vitals

Time to First Byte includes TCP setup, the TLS handshake, and server processing. Shaving one RTT from TLS directly improves TTFB on cold connections. That ties into broader page-speed optimisation work and technical SEO audits where HTTPS performance affects rankings.

What cipher suites and key exchange does TLS 1.3 allow?

TLS 1.3 shrank the cipher-suite menu deliberately. All approved suites use Authenticated Encryption with Associated Data (AEAD). There is no CBC mode, no RC4, and no RSA key transport.

Key exchange always uses ephemeral Diffie-Hellman—either elliptic curve (ECDHE) or finite field (DHE). Forward secrecy is mandatory. If tomorrow someone steals your server's private key, yesterday's captured traffic stays encrypted.

Key Derivation in TLS 1.3Clientkey_share pubServerkey_share pubECDH → shared secretNever sent on wireHKDF-Expand-Label (handshake traffic keys)Client write keyAES-GCM / ChaCha20Server write keyAES-GCM / ChaCha20
TLS 1.3 derives separate client and server write keys from the ECDH shared secret using HKDF

Approved AEAD cipher suites

Production servers typically offer three TLS 1.3 cipher suites. OpenSSL and modern web servers negotiate the first mutually supported option:

  • TLS_AES_256_GCM_SHA384 — AES-256 in GCM mode; common default on x86 servers with AES-NI.
  • TLS_CHACHA20_POLY1305_SHA256 — preferred on mobile ARM devices without hardware AES acceleration.
  • TLS_AES_128_GCM_SHA256 — lighter AES-128 variant; still considered secure for most workloads.

TLS 1.3 cipher suites do not include the key exchange or signature algorithm in the suite name. Those are negotiated separately through supported groups and signature_algorithms extensions.

Certificate signatures

The server certificate still uses RSA or ECDSA for authentication, but not for encrypting the premaster secret—that pattern died with TLS 1.2. Let's Encrypt and most public CAs now issue ECDSA P-256 certificates by default. Pair them with ECDHE key exchange for consistent elliptic-curve performance.

For background on chain files and expiry, see our guide on SSL/TLS certificates explained. Certificate problems surface during the encrypted Certificate message—after keys are already partially derived.

FeatureTLS 1.2TLS 1.3
Full handshake RTTs2 round trips1 round trip
RSA key transportAllowed (no forward secrecy)Removed
CBC cipher modesCommonRemoved
RenegotiationSupportedRemoved
0-RTT dataNot availableOptional (replay risk)
Downgrade protectionPartial (SCSV, extensions)Built into Finished transcript
Encrypted cert messageCertificate sent in cleartextCertificate encrypted after ServerHello

For server-side tuning differences, read TLS 1.3 vs 1.2 configuration for Nginx and HAProxy load balancing with TLS.

How do you configure and debug TLS 1.3 on production web servers?

Most Ubuntu 22/24 and similar Linux hosts I maintain run OpenSSL 3.x with TLS 1.3 enabled by default. Problems usually come from outdated OpenSSL builds, restrictive cipher directives, or missing intermediate certificates—not from TLS 1.3 itself.

Nginx configuration

A practical Nginx SSL block for a Laravel or WordPress site looks like this:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;

ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;

ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

With TLS 1.3, ssl_prefer_server_ciphers has no effect on 1.3 suites—the protocol orders them internally. Keep TLS 1.2 ciphers explicit for older clients. Certbot on Ubuntu renews Let's Encrypt chains automatically; verify the fullchain.pem includes the intermediate.

Apache configuration

On Apache with mod_ssl:

SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
SSLHonorCipherOrder off

Reload the web server after changes. PHP-FPM and opcache are unrelated to TLS, but I have seen teams restart only PHP and wonder why cipher changes never applied—the termination happens in Nginx or Apache, not in PHP.

Debugging with OpenSSL s_client

Inspect the live handshake from your laptop or the server itself:

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

Look for Protocol : TLSv1.3 and the negotiated cipher in the output. To test TLS 1.2 fallback:

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

Wireshark decrypts TLS 1.3 only if you capture the session keys or use a server that logs secrets—plan accordingly before debugging in production. For encoding or inspecting PEM artefacts during setup, the Base64 encoder and decoder on this site helps verify stripped headers in chain files.

TLS Termination in ProductionBrowserTLS 1.3 clientCDN / LBOptional terminateNginxCert + ciphersLaravelHTTP upstreamHandshake terminates at the edge — know which hop owns the certMismatch between CDN cert and origin cert causes opaque 525/526 errorsCheck chain filefullchain.pemVerify SNI matchCorrect vhostTest with s_clientBefore deployUse strong TLS 1.3 at edge; plain HTTP only on private loopback
Production TLS termination: identify which layer runs the handshake and holds the certificate

Application-layer considerations

Laravel apps behind Nginx should force HTTPS in middleware and set HSTS headers at the edge. API clients using Guzzle inherit system OpenSSL settings—test TLS 1.3 from the same PHP version your queue workers run, not only from your dev laptop.

Payment gateway callbacks (eSewa, Khalti, Stripe) require publicly trusted chains. A staging server with a self-signed cert will complete a local handshake but fail when the gateway POSTs back. For API-heavy platforms, see API development and integration services.

What are common TLS 1.3 deployment mistakes in real projects?

I've hit these repeatedly across shared EC2 hosts, client portals, and eCommerce deployments. They are predictable once you know where the handshake lives.

  1. Disabling TLS 1.3 accidentally. An old ssl_protocols TLSv1.2 line copied from a 2018 Stack Overflow answer blocks 1.3 entirely. Audit every include file Nginx pulls in.
  2. Broken certificate chains. Serving only the leaf cert saves bytes but breaks Android clients and some API libraries mid-handshake. Always use the full chain from your CA.
  3. Enabling 0-RTT without replay analysis. CDNs market 0-RTT for speed. Turn it off on admin panels, checkout flows, and document upload endpoints.
  4. Mixing termination layers. TLS ends at Cloudflare but origin still expects HTTPS on port 443 with a different cert—handshake succeeds at the edge while origin errors stay hidden.
  5. Ignoring clock skew. Certificate validity checks fail if the server clock drifts. NTP is part of TLS reliability on managed hosting setups.
  6. Weak DH parameters on TLS 1.2 fallback. Even with TLS 1.3 preferred, legacy clients may negotiate 1.2. Generate fresh 2048-bit or stronger DHE parameters instead of shipping defaults from 2014.

Sister sites on my Deployer 7 pipeline—legal portals like Notary Nepal and Court Marriage In Nepal—share the same Certbot renewal cron. One stale cron path after a deploy means certificates expire while the app code stays current. Monitor expiry independently of application health checks.

Container deployments add another layer. Kubernetes ingress controllers terminate TLS separately from pods; read Kubernetes ingress and TLS with cert-manager if that matches your stack. For ongoing cipher audits and renewals, support and maintenance plans catch drift before browsers do.

Generate unique credentials for staging environments using a proper password generator—never reuse production TLS keys or .env secrets on publicly reachable test hosts.

Key Takeaways

  • TLS 1.3 completes the core handshake in one round trip by combining key exchange with the initial Hello messages.
  • All TLS 1.3 cipher suites use AEAD; RSA key transport and CBC modes are gone, giving you forward secrecy by default.
  • Session keys derive from ECDH via HKDF—capture packets without the private keys and you still cannot decrypt traffic.
  • Test live handshakes with openssl s_client -tls1_3 after every cert or cipher change, not only after app deploys.
  • Disable or restrict 0-RTT on any endpoint that accepts state-changing POST requests.
  • Terminate TLS at a layer you control and monitor; certificate expiry belongs in infrastructure alerts, not bug trackers.

People Also Ask

Is TLS 1.3 backward compatible with TLS 1.2?

Yes, in practice. Clients and servers negotiate the highest mutually supported version during ClientHello and ServerHello. A modern browser prefers TLS 1.3 but falls back to 1.2 against legacy servers. Your config should keep TLS 1.2 enabled with strong ciphers until you explicitly drop old clients from your analytics.

Why was the TLS 1.3 handshake designed to encrypt the certificate?

In TLS 1.2, the server certificate was visible on the wire. That leaked which hostname you accessed and aided passive fingerprinting. TLS 1.3 encrypts the certificate and subsequent handshake messages, exposing only the ClientHello and ServerHello in cleartext.

Does TLS 1.3 require a new SSL certificate?

No. Standard X.509 certificates from Let's Encrypt, DigiCert, or any public CA work with TLS 1.3. You may choose ECDSA certificates for performance, but RSA certificates remain valid. Renewal procedures are identical to TLS 1.2.

How long does a TLS 1.3 handshake take?

Wall-clock time equals roughly one network round trip plus a few milliseconds of CPU for ECDH and AEAD setup. On a 50 ms RTT link, expect about 50–80 ms before the first HTTP byte. Resumption and 0-RTT reduce that further when configured safely.

Put the TLS 1.3 handshake knowledge into production

The TLS 1.3 handshake explained above is the foundation for every HTTPS site you ship in 2026—from WooCommerce checkout pages to legal document portals. Correct cipher config, valid chains, and monitored renewals matter as much as application code quality. If you want help auditing TLS on an existing site or hardening a new web development project, review the portfolio of production deployments or reach out through contact us. For broader context on certificates and protocol history, continue with automating TLS with cert-manager and the API security guide—transport encryption is only the first layer.

Frequently Asked Questions

The TLS 1.3 handshake is the negotiation between client and server before any HTTPS payload travels. Defined in RFC 8446, it agrees on protocol version, authentication, and encryption keys. The client sends ClientHello with ciphers and a key share; the server replies with ServerHello, its certificate, and encrypted extensions; both sides derive session keys and begin encrypted application data in typically one round trip.

First, the client sends ClientHello with supported TLS versions, cipher suites, extensions like SNI and key_share, and an ephemeral public key. The server picks TLS 1.3, selects a cipher and group, and returns its own key_share. Both run ECDH locally without transmitting the shared secret. Remaining messages—EncryptedExtensions, certificate chain, CertificateVerify, and Finished—travel encrypted. The client sends Finished; once verified, derived keys protect HTTP headers and bodies.

TLS 1.2 needed two full round trips before application data in the common full-handshake case. TLS 1.3 compresses the core exchange into one round trip by combining key exchange with the initial Hello messages. That difference matters on mobile networks and international API calls where RTT can exceed 200 ms. TLS 1.3 also removed renegotiation, static RSA key transport, and many legacy cipher suites, cutting bytes and CPU on both ends.

Wall-clock time equals roughly one network round trip plus a few milliseconds of CPU for ECDH and AEAD setup. On a 50 ms RTT link, expect about 50–80 ms before the first HTTP byte. Session resumption and 0-RTT can reduce that further when configured safely.

No. Standard X.509 certificates from Let's Encrypt, DigiCert, or any public CA work with TLS 1.3. You may choose ECDSA P-256 certificates for performance, but RSA certificates remain valid. Renewal procedures are identical to TLS 1.2.

Yes, in practice. Clients and servers negotiate the highest mutually supported version during ClientHello and ServerHello. A modern browser prefers TLS 1.3 but falls back to TLS 1.2 against legacy servers. Your Nginx or Apache config should keep TLS 1.2 enabled with strong ciphers until you explicitly drop old clients from your analytics. Disabling TLS 1.3 accidentally via an old ssl_protocols TLSv1.2 line is a common production mistake.

In TLS 1.2, the server certificate was visible on the wire in cleartext. That leaked which hostname you accessed and aided passive fingerprinting. TLS 1.3 encrypts the certificate and subsequent handshake messages after ServerHello, exposing only ClientHello and ServerHello in cleartext. Certificate problems still surface during the encrypted Certificate message—after keys are already partially derived—so broken chains cause handshake failures even though the cert bytes are not publicly readable.

TLS 1.3 shrank the cipher menu deliberately. All approved suites use AEAD; there is no CBC mode, RC4, or RSA key transport. Key exchange always uses ephemeral Diffie-Hellman via ECDHE or DHE, making forward secrecy mandatory. Production servers typically offer TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, and TLS_AES_128_GCM_SHA256. OpenSSL negotiates the first mutually supported option. Suite names no longer include key exchange or signature algorithms—those are negotiated separately through supported groups and signature_algorithms extensions.

When a client reconnects to a recently visited server, TLS 1.3 can resume using a pre-shared key from the prior session. In the best case, the client sends encrypted application data in its first flight—zero round trips before data, called 0-RTT. That speed comes with replay risk: an attacker who captures a 0-RTT blob can replay it. Never enable 0-RTT on endpoints that create orders, transfer funds, mutate server state, or handle admin panels and document uploads without explicit replay protection.

Set ssl_protocols TLSv1.2 TLSv1.3, ssl_prefer_server_ciphers off, and explicit TLS 1.2 ciphers like ECDHE-ECDSA-AES128-GCM-SHA256. With TLS 1.3, ssl_prefer_server_ciphers has no effect on 1.3 suites—the protocol orders them internally. Point ssl_certificate to fullchain.pem including the intermediate, set ssl_session_tickets off, and reload Nginx after changes. TLS termination happens in Nginx, not PHP-FPM—restarting PHP alone will not apply cipher changes.

Run openssl s_client -connect example.com:443 -tls1_3 -servername example.com from your laptop or the server itself. Look for Protocol : TLSv1.3 and the negotiated cipher in the output. Test TLS 1.2 fallback with -tls1_2. Wireshark decrypts TLS 1.3 only if you capture session keys or use a server that logs secrets, so plan accordingly before debugging in production. Test from the same PHP version your queue workers run, not only your dev laptop.

Repeated production issues include disabling TLS 1.3 via outdated ssl_protocols lines, serving only the leaf certificate instead of the full chain, enabling 0-RTT on state-changing endpoints without replay analysis, mixing termination layers between CDN and origin with mismatched certs, ignoring clock skew that breaks validity checks, and weak DH parameters on TLS 1.2 fallback paths. Certificate expiry must sit in infrastructure alerts—a stale Certbot cron after deploy can expire certs while application health checks stay green.

The key_share extension carries an ephemeral public key in the client's first message. Supported groups typically include X25519 and secp256r1. The server echoes a compatible group and returns its own key_share public value in ServerHello. Both parties then run ECDH locally to derive session keys via HKDF. Neither side transmits the shared secret across the wire, so an eavesdropper who captures every packet still cannot derive session keys without breaking the elliptic-curve problem.

Forward secrecy means compromising a server's long-term private key does not decrypt previously captured traffic. TLS 1.3 makes this mandatory by requiring ephemeral Diffie-Hellman key exchange and removing static RSA key transport from TLS 1.2. Session keys derive from each connection's ECDH shared secret using HKDF, not from encrypting a premaster secret with the certificate's RSA key. If someone steals your server's private key tomorrow, yesterday's captured packets stay encrypted.

TLS termination happens at the web server or load balancer—Nginx, Apache, HAProxy, Cloudflare, or Kubernetes ingress—not inside PHP-FPM or Laravel controllers. The handshake runs before any GET or POST reaches your application. Laravel apps behind Nginx should force HTTPS in middleware and set HSTS headers at the edge. Payment gateway callbacks from eSewa, Khalti, or Stripe require publicly trusted chains at that termination layer; a self-signed staging cert completes locally but fails when the gateway POSTs back.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: