# How TLS works: encryption, integrity and identity explained

> How TLS protects HTTPS: key exchange, certificates, symmetric encryption, the record layer, and one request traced from TCP to HTTP/3.

Source: https://ssl.sh/learn/how-tls-works/
Last updated: 2026-11-10

TLS (Transport Layer Security) protects HTTPS in two phases: a short handshake that uses public-key cryptography to authenticate the server and agree on fresh keys, then fast symmetric encryption and authentication of all application data. An attacker cannot read the data or alter it undetected, and cannot pose as the server without a certificate for its domain.

## What TLS protects: three guarantees

TLS provides confidentiality, integrity and authentication, and each comes from a different mechanism.

| Guarantee | What it means | Mechanism in TLS 1.3 | Attack it stops |
|---|---|---|---|
| Confidentiality | Only the two endpoints can read the data | AEAD ciphers (AES-GCM, ChaCha20-Poly1305) with keys from an ephemeral key exchange | Passive sniffing on Wi-Fi, ISPs, backbone taps |
| Integrity | Any change to the data is detected | Authentication tag on every record, sequence numbers in the nonce | Injecting ads or malware, reordering or replaying records |
| Authentication | You are talking to the real `example.com` | Server certificate chain plus a signature over the handshake (CertificateVerify) | Man-in-the-middle with a fake server |

Authentication is normally one-way: the server proves its identity, the client stays anonymous at the TLS layer and logs in later with a password or token. When both sides present certificates it is called mutual TLS; see [mutual TLS](https://ssl.sh/learn/mtls/).

TLS does **not** hide everything. The server's IP address, the size and timing of traffic, and usually the hostname (through DNS and the [SNI](https://ssl.sh/glossary/sni/) field) remain visible to the network. TLS also says nothing about whether the site is honest: a domain-validated certificate only proves that the requester controlled the domain at issuance time (see [DV, OV and EV](https://ssl.sh/learn/dv-ov-ev/)).

## Two kinds of cryptography working together

TLS combines slow asymmetric cryptography for setup with fast symmetric cryptography for bulk data. Neither alone is enough.

| | Asymmetric (public-key) | Symmetric |
|---|---|---|
| Examples in TLS | ECDHE (X25519, P-256), ML-KEM in hybrids, RSA and ECDSA signatures | AES-128-GCM, AES-256-GCM, ChaCha20-Poly1305 |
| Keys | Key pair: public and private | One shared secret key per direction |
| Speed | Thousands of operations per second | Gigabytes per second with hardware support |
| Job in TLS | Agree on secrets, prove identity | Encrypt and authenticate records |

The handshake runs an ephemeral Diffie-Hellman exchange. Each side generates a throwaway key pair, sends the public half, and combines its private half with the peer's public half to get the same shared secret. An eavesdropper sees both public values but cannot compute the secret. Because the key pairs are discarded after the handshake, stealing the server's long-term private key later does not decrypt recorded traffic. This property is called [forward secrecy](https://ssl.sh/glossary/forward-secrecy/), and TLS 1.3 makes it mandatory for full handshakes.

The shared secret is never used directly. It goes through HKDF, a key derivation function, which produces separate keys and IVs for each direction and each phase of the connection. From then on, all data is protected with an [AEAD](https://ssl.sh/glossary/aead/) cipher, which encrypts and authenticates in one step.

Since November 2024 (Chrome 131), Chrome offers the hybrid group X25519MLKEM768 by default, combining classic X25519 with the post-quantum ML-KEM-768. OpenSSL 3.5, released in April 2025, also offers it by default. The session keys then stay safe even if a future quantum computer breaks X25519; see [post-quantum TLS](https://ssl.sh/learn/post-quantum-tls/).

## Where certificates fit in

Key exchange alone does not tell you *who* you agreed a key with. An attacker in the middle could run one key exchange with you and another with the real server. Certificates solve that.

A TLS server certificate is an [X.509](https://ssl.sh/glossary/x509/) document that binds a public key to one or more domain names, listed in the Subject Alternative Name extension, and is signed by a [certificate authority](https://ssl.sh/glossary/certificate-authority/). During the handshake the client:

1. Receives the server's certificate and the intermediate certificates.
2. Builds a chain from the server certificate up to a [root certificate](https://ssl.sh/glossary/root-certificate/) in its trust store (operating system or browser).
3. Checks that the requested hostname matches a SAN entry, that the current time is within the validity period, that key usage allows TLS server authentication, and, depending on the client, revocation status and Certificate Transparency proofs.
4. Verifies the server's **CertificateVerify** signature over a hash of the whole handshake so far. Only the holder of the certificate's private key can produce it, so a stolen or copied certificate is useless without the key.

If any step fails the browser shows an interstitial such as [NET::ERR_CERT_AUTHORITY_INVALID](https://ssl.sh/errors/net-err-cert-authority-invalid/) or [NET::ERR_CERT_COMMON_NAME_INVALID](https://ssl.sh/errors/net-err-cert-common-name-invalid/). The details of chain building are in [certificate chains](https://ssl.sh/learn/certificate-chains/).

## The handshake in one paragraph

In TLS 1.3 the client opens with a ClientHello that already contains key shares for the groups it expects the server to pick, the cipher suites and signature algorithms it supports, the hostname (SNI) and the application protocols it wants (ALPN). The server answers with a ServerHello containing its own key share. Both sides now derive handshake keys, so everything after that is encrypted: EncryptedExtensions, the Certificate chain, CertificateVerify and Finished. The client checks the certificate and signature, sends its own Finished, and starts sending application data. The whole exchange costs one round trip. Each message is dissected in [the TLS handshake](https://ssl.sh/learn/tls-handshake/).

## The record layer: how data moves

After the handshake, everything travels in TLS records, small framed chunks of at most 16 KB of plaintext. Each record has a 5-byte header followed by the encrypted payload and an authentication tag:

```text
+--------+---------+---------+------------------------------------+-----------+
| type   | version | length  | encrypted payload                  | auth tag  |
| 1 byte | 2 bytes | 2 bytes | (data + real content type + pad)   | 16 bytes  |
+--------+---------+---------+------------------------------------+-----------+
  0x17     0x0303    <= 2^14+256
  (application_data, always, in TLS 1.3)
```

A few details matter in practice:

- **Size.** Plaintext per record is capped at 2^14 = 16,384 bytes. Large HTTP responses are split across many records.
- **Nonces.** The AEAD nonce is the per-direction IV XORed with a 64-bit record sequence number. Records cannot be dropped, reordered or replayed without the next tag check failing.
- **Hidden content type.** In TLS 1.3 the outer type is always `application_data` (23), and the real type (handshake, alert or data) is inside the encryption. Optional padding can hide record lengths.
- **Alerts.** Errors are sent as alert records, for example `handshake_failure` or `bad_certificate`. A clean shutdown sends `close_notify`, so the receiver can tell a complete response from a truncated one.

TLS runs over a reliable byte stream, normally TCP. DTLS is the datagram variant for UDP, and QUIC takes yet another approach, described below.

## One HTTPS request, end to end

A first visit to `https://example.com/` over HTTP/1.1 or HTTP/2 with TLS 1.3 looks like this:

```text
Browser                                              example.com (203.0.113.10)
   |  DNS: example.com A/AAAA/HTTPS?  ---> resolver            |
   |  <--- 203.0.113.10                                         |
   |                                                            |
   |  TCP SYN  ------------------------------------------------>|   RTT 1
   |  <------------------------------------------------ SYN-ACK |   (TCP)
   |  ACK + TLS ClientHello (key_share, SNI, ALPN h2) --------->|
   |                                                            |   RTT 2
   |  <---- ServerHello + {EncryptedExtensions, Certificate,    |   (TLS)
   |          CertificateVerify, Finished}                      |
   |  verify chain, hostname, signature                         |
   |  {Finished} + [HTTP GET /] -------------------------------->|
   |  <-------------------------------------- [HTTP 200 + body] |   RTT 3
   |                                                            |
   { } = encrypted with handshake keys   [ ] = application keys
```

So the first byte of the response arrives after three round trips on a cold connection, not counting DNS. TLS 1.2 adds one more round trip because its full handshake takes two. Resumed TLS 1.3 sessions can send the request inside the first flight using 0-RTT early data, at the cost of replay risk (see [TLS 1.3](https://ssl.sh/learn/tls-1-3/)).

## TLS, HTTP/2 and HTTP/3

TLS is the foundation for all modern HTTP versions, but each version uses it a bit differently.

| | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| Transport | TCP | TCP | QUIC over UDP |
| Security | TLS 1.2 or 1.3 | TLS 1.2+ (browsers only support HTTP/2 over TLS) | TLS 1.3 handshake inside QUIC |
| Negotiation | ALPN `http/1.1` or none | ALPN `h2` | ALPN `h3`, discovered via `Alt-Svc` header or HTTPS DNS record |
| Records | TLS records | TLS records | QUIC packet protection, no TLS records |
| Round trips before request (new connection) | 2 (TLS 1.3) | 2 (TLS 1.3) | 1 |

The choice between HTTP/1.1 and HTTP/2 happens inside the TLS handshake through [ALPN](https://ssl.sh/glossary/alpn/): the client lists `h2,http/1.1` and the server picks one in EncryptedExtensions. RFC 9113 also requires HTTP/2 over TLS 1.2 to avoid a long list of weak cipher suites.

HTTP/3 is different. QUIC carries the TLS 1.3 handshake messages in its own CRYPTO frames and uses the keys TLS derives to protect QUIC packets, so the transport and cryptographic handshakes complete together in a single round trip. QUIC cannot run without TLS 1.3, which is why there is no unencrypted HTTP/3.

## TLS versions at a glance

Only TLS 1.2 and TLS 1.3 should be enabled on public servers.

| Version | Year | Specification | Status |
|---|---|---|---|
| SSL 2.0 | 1995 | (Netscape) | Prohibited by RFC 6176 |
| SSL 3.0 | 1996 | RFC 6101 (historic) | Deprecated by RFC 7568 (POODLE) |
| TLS 1.0 | 1999 | RFC 2246 | Deprecated by RFC 8996 (March 2021) |
| TLS 1.1 | 2006 | RFC 4346 | Deprecated by RFC 8996 |
| TLS 1.2 | 2008 | RFC 5246 (obsoleted; requirements now in RFC 9846) | Supported; safe with AEAD and ECDHE suites |
| TLS 1.3 | 2018 | RFC 8446, revised as RFC 9846 (July 2026) | Current version; preferred |

All major browsers removed TLS 1.0 and 1.1 around 2020. If an old client cannot connect after you disable them, the browser usually reports [ERR_SSL_VERSION_OR_CIPHER_MISMATCH](https://ssl.sh/errors/err-ssl-version-or-cipher-mismatch/).

In July 2026 the IETF published RFC 9846, a backward-compatible revision of TLS 1.3 that keeps the version number 0x0304. It clarifies text that implementers found unclear and tightens a few rules, for example forbidding the reuse of key shares between connections and forbidding negotiation of TLS 1.0 and 1.1. It also obsoletes RFC 5246, so the requirements for TLS 1.2 implementations now live in the same document.

## See it for yourself

`openssl s_client` runs a real handshake from your terminal and prints what was negotiated:

```bash
openssl s_client -connect example.com:443 -servername example.com -brief </dev/null
```

```text
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN=example.com
Hash used: SHA256
Signature type: ecdsa_secp256r1_sha256
Verification: OK
Negotiated TLS1.3 group: X25519MLKEM768
DONE
```

That output, from OpenSSL 3.6, already tells most of the story: TLS 1.3, an AES-256-GCM AEAD suite, an ECDSA P-256 certificate signature, a verified chain, and a hybrid post-quantum key exchange. `curl -v https://example.com/` shows similar lines. For more variations (STARTTLS, client certificates, forcing versions) see the [connection testing cookbook](https://ssl.sh/cookbook/test-connections/), or run the [SSL checker](https://ssl.sh/tools/ssl-checker/) for a full report.

## Common misconceptions

**"The certificate encrypts the traffic."** It does not. The certificate's key only signs the handshake. Traffic is encrypted with session keys from the ephemeral key exchange, which is why a leaked private key does not expose past TLS 1.3 sessions.

**"Longer RSA keys mean stronger encryption."** Certificate key size affects the strength of server authentication, not the bulk encryption. A 2048-bit RSA or a P-256 ECDSA certificate is fine as of November 2026; the data cipher is chosen separately (see [cipher suites](https://ssl.sh/learn/cipher-suites/)).

**"HTTPS is only for login pages."** Without TLS any network hop can read cookies or inject scripts into any page. Browsers mark plain HTTP as not secure and block [mixed content](https://ssl.sh/glossary/mixed-content/) on HTTPS pages.

**"Once configured, TLS is done."** Certificates expire, and maximum lifetimes keep shrinking: from 398 to 200 days on 15 March 2026, to 100 days on 15 March 2027 and to 47 days on 15 March 2029. Renewal has to be automated; see [47-day certificates](https://ssl.sh/learn/47-day-certificates/) and [automating renewal](https://ssl.sh/learn/automate-certificate-renewal/).
