What a firewall sees in a TLS 1.3 handshake, message by message
Walk a TLS 1.3 handshake in Wireshark from ClientHello to Finished: why SNI stays visible, the certificate does not, and 0-RTT can be replayed.
Intermediate11 min read
┌─────────┐ ┌─────────┐ │ CLIENT │ TLS 1.3 · 1-RTT │ SERVER │ └─────────┘ └─────────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ─ plain: readable on the wire ═ {…}: encrypted record capture>
On this page
- How is TLS 1.3 different from TLS 1.2?
- How can you watch a TLS 1.3 handshake yourself?
- Message 1: ClientHello (in the clear)
- Message 2: ServerHello (the last thing in the clear)
- Messages 3 to 6: the encrypted server flight
- What can a firewall see without decryption?
- What is 0-RTT, and why can it be replayed?
- Which TLS versions does current guidance allow?
- How to set decryption policy and troubleshoot TLS 1.3
If you look after firewalls or proxies, TLS is part of almost every ticket: a decryption rule that breaks an app, a URL category that matches on the server name, a "certificate error" that only happens through the proxy. Most of these make sense once you know what is in each message of the handshake and which messages travel in the clear.
This post walks through a TLS 1.3 handshake the way a packet capture shows it, one message at a time. I lean on two references throughout: the protocol itself, RFC 8446, and The Illustrated TLS 1.3 Connection, which annotates a real connection byte by byte. If you want to follow along, open that page in another tab.
How is TLS 1.3 different from TLS 1.2?
TLS 1.3 needs one round trip instead of two, removes legacy cryptography, encrypts everything after the ServerHello, and adds a 0-RTT mode. RFC 8446 was published in August 2018. Its section 1.2, "Major Differences from TLS 1.2", is worth reading in full, but these are the changes that matter most on the wire:
- One round trip instead of two. In TLS 1.2 the client waits two round trips before it can send encrypted data on a new connection; TLS 1.3 needs one, because the client sends its key share in the very first message (Cloudflare, "A Detailed Look at RFC 8446").
- Legacy cryptography removed. Only AEAD ciphers remain. Static RSA and Diffie-Hellman cipher suites are gone, so every public-key key exchange now provides forward secrecy. Compression and custom DHE groups were removed too (RFC 8446, section 1.2).
- Everything after the ServerHello is encrypted, including the server's certificate (RFC 8446, section 1.2).
- A 0-RTT mode lets a returning client send data in its first flight, "at the cost of certain security properties", which we'll come back to.
That third change is the one that matters most for network devices, so keep it in mind as we go.
How can you watch a TLS 1.3 handshake yourself?
Capture your own browser traffic in Wireshark and filter with tls. You don't need special hardware. The fields used below are all in the Wireshark TLS display filter reference: tls.handshake.type, tls.handshake.extensions_server_name, tls.handshake.extensions.supported_version, tls.handshake.extensions_key_share_group and tls.record.opaque_type.
To see inside the encrypted part, use a key log file. Set the SSLKEYLOGFILE environment variable before you start Firefox or Chrome, then point Wireshark's "(Pre)-Master-Secret log filename" preference at that file. The Wireshark wiki notes that the old method of loading the server's RSA private key does not work for TLS 1.3 or for any (EC)DHE cipher suite (Wireshark wiki: TLS). That's forward secrecy working as designed, and it's also why a firewall can't decrypt TLS 1.3 traffic just by being given a server's private key. It has to sit in the middle of the connection, as we'll see.
The handshake message types you'll filter on are defined in RFC 8446, section 4: client_hello(1), server_hello(2), new_session_ticket(4), encrypted_extensions(8), certificate(11), certificate_verify(15) and finished(20).
┌─────────┐ ┌─────────┐ │ CLIENT │ TLS 1.3 · 1-RTT │ SERVER │ └─────────┘ └─────────┘ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ─ plain: readable on the wire ═ {…}: encrypted record capture>
Message 1: ClientHello (in the clear)
The client speaks first, and this is the richest message for anyone watching the network. Things to notice in the capture:
The version fields are deliberately misleading. legacy_version in the ClientHello is 0x0303, which is the TLS 1.2 value. The real offer is in the supported_versions extension, which must list 0x0304 for TLS 1.3. When supported_versions is present, the server must not use legacy_version for version negotiation (RFC 8446, section 4.2.1). In the record header you may even see 0x0301, which is allowed for the first ClientHello (section 5.1). The reason is ossification: during standardisation, some middleboxes broke on minor wire changes like a new version number, so TLS 1.3 was made to look like TLS 1.2 session resumption (Cloudflare).
key_share carries the client's half of the key exchange. The client guesses which group the server will accept (X25519 in the Illustrated TLS example) and sends a public value for it straight away. That guess is what saves the round trip. If the server supports none of the offered shares, it answers with a HelloRetryRequest naming a group it does accept, and the client tries again (RFC 8446, sections 4.1.4 and 4.2.8). In a capture, a HelloRetryRequest looks like a ServerHello but carries a fixed special value in its Random field.
server_name (SNI) is the hostname, in plain text. It is one of the extensions a TLS-compliant application must implement, unless an application profile says otherwise (section 9.2).
cipher_suites is short. TLS 1.3 defines five suites, from TLS_AES_128_GCM_SHA256 (0x1301) to TLS_AES_128_CCM_8_SHA256 (0x1305). A suite now names only the AEAD cipher and the hash; key exchange and signatures are negotiated separately in extensions. TLS_AES_128_GCM_SHA256 is mandatory to implement (RFC 8446, section 9.1 and appendix B.4).
Other extensions you'll see here are supported_groups, signature_algorithms and application_layer_protocol_negotiation (ALPN, for example h2). RFC 8446's extension table lists server_name and ALPN as appearing in the ClientHello and the EncryptedExtensions (section 4.2). So the client's offer is visible, but the server's answer is not.
Message 2: ServerHello (the last thing in the clear)
The server picks a cipher suite and returns its own key_share in the same group, plus supported_versions set to 0x0304. Two details are worth knowing:
- Downgrade protection lives in the Random field. A TLS 1.3 server that negotiates TLS 1.2 or older puts a fixed marker in the last eight bytes of ServerHello.random (the ASCII letters
DOWNGRDplus one byte), and a TLS 1.3 client that sees it aborts (RFC 8446, section 4.1.3). - Both sides now have a shared secret. With the two key shares, client and server each derive the handshake traffic keys. From this point on, nothing else in the handshake is readable to an observer.
You may also see a ChangeCipherSpec record right after the ServerHello. It does nothing in TLS 1.3. It's a dummy record from "middlebox compatibility mode", sent so the exchange looks like a TLS 1.2 resumption to old devices (RFC 8446, appendix D.4; the Illustrated TLS capture shows one in each direction).
Messages 3 to 6: the encrypted server flight
Without keys, Wireshark shows the next few records as "Application Data". That's by design: every encrypted TLS 1.3 record has an outer content type of 23 (application_data) and a version of 0x0303, and the real type is hidden inside the encryption (RFC 8446, section 5.2). With a key log loaded, they turn out to be:
- EncryptedExtensions: the server's answers that used to be sent in the clear, such as the chosen ALPN protocol.
- Certificate: the server's certificate chain. Earlier TLS versions sent it over the wire in plain text (RFC 8744); in TLS 1.3 it's protected with the handshake keys (RFC 8446, Figure 1).
- CertificateVerify: a signature with the certificate's private key over the handshake so far. This proves the server holds that key and binds the certificate to this specific handshake (section 4.4.3).
- Finished: a MAC over the whole transcript, so any tampering with earlier messages is detected (section 4.4.4).
The server may send application data straight after its Finished. The client replies with its own encrypted Finished (plus a Certificate and CertificateVerify if the server asked for a client certificate), and the handshake is done. Later, the server usually sends one or more NewSessionTicket messages, also encrypted, which the client can use to resume. A ticket's lifetime can't exceed seven days (section 4.6.1).
What can a firewall see without decryption?
Without decryption, a firewall still sees IP addresses, ports and the whole ClientHello, including the SNI and the ALPN offer. It does not see the server's certificate or the server's extension responses. In detail:
┌─ MIDDLEBOX VIEW (no decryption) ───●─┐
client │ │ server
────────────│ │────────────
│ │
│ dst ip │
│ sni │
│ alpn │
│ versions │
│ │
│ cert │
│ payload │
│ │
└──────────────────────────────────────┘
readable: who and where · sealed: what was said Still visible: IP addresses and ports, and everything in the ClientHello: SNI, the offered ALPN protocols, supported versions, cipher suites and key share groups. The ServerHello's chosen suite is visible too, as are record sizes and timing.
No longer visible: the server's certificate and the server's extension responses. RFC 8744 describes the result well. Because TLS 1.3 encrypts the certificate, hiding the SNI is now worth more: in earlier versions the same name was visible in the cleartext certificate anyway. It also lists the devices that rely on SNI, including enterprise firewalls that block sites and devices that decide which traffic to exempt from inspection.
Two practical consequences:
- A rule that relies on reading the server certificate (to check its issuer, expiry or subject) can't work on undecrypted TLS 1.3. Palo Alto Networks says this directly: for TLS 1.3 traffic that isn't decrypted, the firewall has no view of the certificate, so it can't block sessions with expired certificates or untrusted issuers (PAN-OS docs: TLS 1.3 Decryption).
- SNI may not stay readable. RFC 9849, TLS Encrypted Client Hello, published as a Proposed Standard in March 2026, encrypts the real ClientHello inside an outer one. The outer SNI normally carries the public name of the provider's client-facing server. An observer can still tell which provider you're connecting to, and ECH doesn't hide the destination IP or plaintext DNS lookups. But a policy that keys on the exact hostname from SNI will see less as ECH is adopted.
What is 0-RTT, and why can it be replayed?
0-RTT lets a client that has a ticket from an earlier connection send application data (early data) in its first flight, and that early data has no replay protection between connections. To do this the client must include both the pre_shared_key and early_data extensions (RFC 8446, section 4.2.10). This early data is encrypted, but RFC 8446 is clear about the trade-offs (section 2.3):
- it isn't forward secret, because it's protected only by keys derived from the PSK;
- there's no guarantee against replay between connections. Normal 1-RTT data is protected by the server's fresh Random value, which 0-RTT data can't depend on.
0-RTT resumption ┌────────┐ ┌─────────●┐ │ CLIENT │──────═════════───────────────────────────│ SERVER │ └────────┘ ┆ └──────────┘ ┆ ┆ seen CH: (o o) ─┴─ on-path copy rule: send in 0-RTT only what is safe to repeat
Appendix E.5 spells out why this matters: a replayed request can repeat an action with side effects, such as a purchase or a money transfer. Servers can defend themselves with single-use tickets or by recording ClientHellos within a time window, and RFC 8446 adds that clients must only send early data that is safe to replay (section 8). For HTTP, RFC 8470 adds the 425 (Too Early) status code and the Early-Data header, so a server or gateway can ask the client to retry outside early data.
The guidance documents are cautious. RFC 9325 says applications must avoid 0-RTT unless their protocol has an explicit specification for it. NIST SP 800-52 Rev. 2 (section 3.6) says that in general a server should not accept 0-RTT data, and if it does, it should use single-use tickets (NIST SP 800-52r2).
Which TLS versions does current guidance allow?
TLS 1.0 and 1.1 must not be used. TLS 1.2 must be supported, and TLS 1.3 should be supported and preferred. The documents behind this:
| Document | What it says |
|---|---|
| RFC 8996 (2021) | TLS 1.0 and TLS 1.1 MUST NOT be used. Reasons include handshake integrity that depends on SHA-1, and older cipher suites without modern AEAD options. |
| RFC 9325 (BCP 195, 2022) | Never negotiate SSL 2.0/3.0 or TLS 1.0/1.1. TLS 1.2 MUST be supported, TLS 1.3 SHOULD be, and when 1.3 is supported it MUST be preferred. |
| NIST SP 800-52 Rev. 2 (2019) | US government TLS servers must support TLS 1.2 and must support TLS 1.3 by 1 January 2024. Government-only servers shall not use TLS 1.0, SSL 3.0 or SSL 2.0. Its list of TLS 1.3 suites (section 3.3.1.2) has four of the five, without ChaCha20-Poly1305. |
NIST SP 800-52 is written for US federal systems, but its tables make a handy checklist when you write your own decryption or SSL/TLS service profiles.
How to set decryption policy and troubleshoot TLS 1.3
To decrypt TLS 1.3, a firewall has to act as the server toward the client and as the client toward the real server. RFC 8446 requires a middlebox that terminates TLS to behave as a compliant TLS server to the client and as a compliant TLS client to the server, including verifying the real server's certificate (section 9.3). Some notes I keep in mind, using PAN-OS as the example because its documentation is explicit:
- Version range. Palo Alto Networks recommends a decryption profile with minimum TLSv1.2 and maximum "Max", so newer versions are picked up automatically (TLS 1.3 Decryption).
- Pinned apps break. Many mobile apps pin certificates. The same page suggests capping the maximum version at TLSv1.2 for rules that cover those apps, or not decrypting that traffic.
- Use exclusions for technical breakage. Certificate pinning, mutual (client-certificate) authentication, incomplete certificate chains and unsupported ciphers are the listed reasons for the SSL decryption exclusion list. Traffic you choose not to decrypt for business, regulatory, personal or other reasons belongs in a policy-based exclusion instead (Decryption Exclusions).
- QUIC skips your TLS policy. QUIC runs over UDP and the firewall can't decrypt it. The usual approach is to block the QUIC application and UDP 80/443 so browsers fall back to TLS, which the firewall can decrypt (Define Traffic to Decrypt).
- Read the capture in order. When a site fails only through the firewall, work through the handshake. Did the ClientHello go out with the SNI you expected? Did a ServerHello come back, or an alert? If the session was decrypted, which certificate did the client receive, the firewall's or the server's? Each answer points to a different layer: routing and policy, the server's TLS settings, or the decryption profile and its trust chain.
Once you've read a handshake message by message a few times, "TLS problems" become much easier to sort into ones the network can fix and ones it can't.
Sources
- RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3
- The Illustrated TLS 1.3 Connection
- Cloudflare: A Detailed Look at RFC 8446 (a.k.a. TLS 1.3)
- RFC 8744: Issues and Requirements for Server Name Identification (SNI) Encryption in TLS
- RFC 9849: TLS Encrypted Client Hello
- RFC 8470: Using Early Data in HTTP
- RFC 8996: Deprecating TLS 1.0 and TLS 1.1
- RFC 9325: Recommendations for Secure Use of TLS and DTLS (BCP 195)
- NIST SP 800-52 Rev. 2: Guidelines for the Selection, Configuration, and Use of TLS Implementations (PDF)
- Wireshark wiki: TLS
- Wireshark display filter reference: TLS
- Palo Alto Networks: TLS 1.3 Decryption
- Palo Alto Networks: Decryption Exclusions
- Palo Alto Networks: Define Traffic to Decrypt
Keep reading
Related posts
Network security
How SSL decryption on a firewall works, and what not to decrypt
Forward proxy and inbound inspection step by step, why clients trust the firewall's CA, what can't or shouldn't be decrypted, and what TLS 1.3 changes.
Advanced10 min read
- SSL Decryption
- TLS Inspection
- Firewalls
- PAN-OS
- FortiGate
- Cisco Secure Firewall
- TLS 1.3
Network security
Change windows: write and test the rollback before you start
A before, during and after checklist for firewall changes: written go/no-go checkpoints, config snapshots and a tested rollback, mapped to NIST SP 800-128.
Intermediate9 min read
- Change Management
- Rollback
- Firewalls
- NIST
- SRE
- Checklist
Network security
Inside a firewall HA failover: heartbeats, session sync, failback
How Palo Alto, FortiGate and Cisco FTD pairs detect a failure, sync sessions, move the virtual MAC and fail back, and how to test it safely.
Advanced12 min read
- High Availability
- Firewalls
- Palo Alto
- FortiGate
- Cisco FTD
- VRRP
- Change Management