
September 12, 2026
12 min read
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.
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.
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.
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.
| Feature | TLS 1.2 | TLS 1.3 |
|---|---|---|
| Full handshake RTTs | 2 round trips | 1 round trip |
| RSA key transport | Allowed (no forward secrecy) | Removed |
| CBC cipher modes | Common | Removed |
| Renegotiation | Supported | Removed |
| 0-RTT data | Not available | Optional (replay risk) |
| Downgrade protection | Partial (SCSV, extensions) | Built into Finished transcript |
| Encrypted cert message | Certificate sent in cleartext | Certificate 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.
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.
- Disabling TLS 1.3 accidentally. An old
ssl_protocols TLSv1.2line copied from a 2018 Stack Overflow answer blocks 1.3 entirely. Audit every include file Nginx pulls in. - 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.
- Enabling 0-RTT without replay analysis. CDNs market 0-RTT for speed. Turn it off on admin panels, checkout flows, and document upload endpoints.
- 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.
- Ignoring clock skew. Certificate validity checks fail if the server clock drifts. NTP is part of TLS reliability on managed hosting setups.
- 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_3after 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
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.

