Home › Security › Crypto primitives
algorithm medium

TLS 1.3

Problem Any network service must keep traffic between clients, or between internal services, private, tamper-proof and authenticated to the right server, without hand-building a secure channel.

The standard encrypted, authenticated transport; one round trip, only forward-secret AEAD suites, and little left to misconfigure.

full handshake 1 RTT (0-RTT on resumption, replayable); handshake CPU order of 0.1-1 ms; bulk AEAD at GB/s per core
tlstransport-securityhandshakecertificateshttps
Overview

TLS gives a byte stream confidentiality, integrity and server (optionally client) authentication. TLS 1.3 cut the protocol down: ephemeral (EC)DHE key exchange only, AEAD-only ciphers, encrypted certificates, and a 1-RTT handshake. Most of the security now comes from using a maintained library, verifying certificates properly and automating renewal. Government standards, cloud load balancers and IoT platforms all require or offer it.

Key facts

Use when

  • Any network traffic, including inside your datacenter and between services (mTLS).
  • Government: NIST SP 800-52r2 requires TLS 1.3 support on government servers and clients (NIST); the CNSA profile covers TLS 1.3 (RFC 9151).
  • IoT: AWS IoT Core supports TLS 1.3 and 1.2 for device connections (AWS).
  • Cloud load balancers: AWS ALB security policies include TLS 1.3-only options (AWS).

Avoid when

  • Never avoid it; for UDP use QUIC or DTLS, which carry the same handshake.

Deep dive

How it works

Parts:

  • Client and server — the two endpoints; the server must prove its identity.
  • Key exchange — ephemeral X25519 (or hybrid X25519MLKEM768) gives a fresh shared secret per connection.
  • Key schedule — HKDF turns the secret and transcript into handshake and traffic keys.
  • Certificate chain and CA roots — bind the server's public key to its hostname.
  • Record layer — AEAD (see AEAD) protects every record after ServerHello.
  • Session tickets — PSKs that allow resumption and 0-RTT.
  1. Both sides compute the ECDHE shared secret; HKDF derives handshake keys, so everything after ServerHello is encrypted.
  2. Server proves key ownership: CertificateVerify = signature over the handshake transcript with its certificate key.
  3. Client validates the chain to a trusted root, the hostname (SAN), validity dates, and the signature.
  4. Finished MACs over the transcript stop downgrade and tampering; traffic keys are derived; later key updates are possible.
  5. Resumption: the server issues a PSK ticket; next connection can skip certificates and optionally send 0-RTT early data.
Client                                         Server
ClientHello: versions, suites, key_share(X25519 pub), SNI, ALPN ──►
                 ◄── ServerHello: key_share(server pub)
                     {EncryptedExtensions} {Certificate} {CertificateVerify} {Finished}
{Finished} + application data ──►                      (1 RTT total)

Code

nginx (Mozilla "intermediate" profile, condensed):

server {
    listen 443 ssl;
    http2 on;
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    # TLS 1.2 only: ECDHE + AEAD. TLS 1.3 suites are fine by default.
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
    ssl_prefer_server_ciphers off;
    ssl_session_timeout 1d;
    ssl_session_cache shared:TLS:10m;
    ssl_session_tickets off;          # nginx never rotates ticket keys: weakens forward secrecy
    ssl_early_data off;               # 0-RTT replay risk

    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always;
}

Rust server and client with rustls = "0.23" (verification on by default, no knobs to weaken by accident):

use rustls::pki_types::{pem::PemObject, CertificateDer, PrivateKeyDer, ServerName};
use std::sync::Arc;

pub fn server_config() -> Result<Arc<rustls::ServerConfig>, Box<dyn std::error::Error>> {
    let certs: Vec<CertificateDer<'static>> =
        CertificateDer::pem_file_iter("fullchain.pem")?.collect::<Result<_, _>>()?;
    let key = PrivateKeyDer::from_pem_file("privkey.pem")?;
    let mut cfg = rustls::ServerConfig::builder()      // safe defaults: TLS 1.2+1.3, AEAD only
        .with_no_client_auth()
        .with_single_cert(certs, key)?;
    cfg.alpn_protocols = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
    Ok(Arc::new(cfg))
}

pub fn client_config() -> Arc<rustls::ClientConfig> {
    let roots = rustls::RootCertStore::from_iter(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
    Arc::new(rustls::ClientConfig::builder().with_root_certificates(roots).with_no_client_auth())
}

pub fn connect(cfg: Arc<rustls::ClientConfig>, host: &str) -> std::io::Result<()> {
    let name = ServerName::try_from(host.to_owned()).expect("valid DNS name"); // hostname is checked
    let conn = rustls::ClientConnection::new(cfg, name).map_err(std::io::Error::other)?;
    let sock = std::net::TcpStream::connect((host, 443))?;
    let _tls = rustls::StreamOwned::new(conn, sock); // Read + Write over TLS
    Ok(())
}

Use tokio-rustls for async; reqwest with the rustls-tls feature for HTTP clients.

Complexity and constants

  • Handshake CPU: X25519 order of 20-50 µs per side; certificate signature: ECDSA P-256 or Ed25519 order of tens of µs, RSA-2048 sign order of 1 ms. Prefer ECDSA certs on busy servers (serve RSA too only for very old clients).
  • Hybrid X25519MLKEM768 adds about 1.1 KB to ClientHello and ~1 KB to ServerHello; CPU cost is small.
  • Records: up to 16 KiB plaintext + about 22 B overhead. Bulk throughput is AEAD-bound, order of several GB/s per core with AES-NI.
  • Latency: 1 RTT for TLS on top of TCP's 1 RTT; QUIC combines them.

Efficiency

Cost: the bound is round-trip latency for the handshake and AEAD throughput afterwards (bulk AEAD at GB/s per core); handshake CPU (order of 0.1-1 ms) matters only on busy terminators.

Levers:

  • Reuse connections — keep-alive and HTTP/2 multiplexing amortize one handshake over many requests — all clients.
  • Resumption — a PSK ticket skips certificate validation and the signature; 0-RTT early data also saves the round trip but is replayable — idempotent requests only.
  • ECDSA P-256 certificate — signs in tens of µs against about 1 ms for RSA-2048 (order of), with a smaller chain; keep one intermediate (each certificate order of 1-2 KB) — busy servers.
  • Certificate compression (RFC 8879) — shrinks the chain on the wire — when both sides support it.
  • Cipher by hardware — AES-GCM with AES-NI or ARMv8 crypto extensions; ChaCha20-Poly1305 is faster on CPUs without them — bulk transfer.

Naive → standard → state of the art:

  • Naive: new connection and full handshake per request, RSA certificate · 1 RTT plus order of 1 ms signing per request
  • Standard: TLS 1.3, keep-alive or HTTP/2, ECDSA certificate, terminate once at the edge · one handshake per connection
  • State of the art: resumption with 0-RTT for idempotent requests, certificate compression · resumed connections skip certificate and signature, and 0-RTT sends data in the first flight

Implementation notes

  • Certificates: ACME (Let's Encrypt, ZeroSSL) with automatic renewal well before expiry. Maximum public certificate lifetimes are shrinking (CA/Browser Forum schedule down to 47 days by 2029), so manual renewal is not an option.
  • Keys: ECDSA P-256 is the common default; keep private keys readable only by the TLS process, or terminate in a load balancer/KMS.
  • Internal services: mTLS with a private CA and short-lived certs (SPIFFE/SPIRE, service mesh, or step-ca). Validate the client identity (SAN/URI), not just "signed by our CA".
  • HSTS: add once all subdomains are on HTTPS; preload only when sure, it's hard to undo.
  • Session tickets: if enabled, rotate ticket keys often (hours) and share them across the fleet; or use the session cache only.
  • Test with testssl.sh or SSL Labs after every config change.

Pitfalls

  • Disabling verification in clients (verify=False, InsecureSkipVerify, danger_accept_invalid_certs) "temporarily" and shipping it.
  • Verifying the chain but not the hostname (custom verifiers, raw OpenSSL usage).
  • TLS terminated at the load balancer, then plaintext across a "trusted" network to the app.
  • Expired certificates from a broken renewal cron: alert at 21 days before expiry.
  • Enabling 0-RTT for POST endpoints: replay can repeat payments.
  • Missing intermediate certificate in the served chain: works in browsers that cache it, fails in API clients.

Variants and alternatives

QUIC / HTTP/3TLS 1.3 handshake inside UDP transport; fewer RTTs, no head-of-line blocking.
mTLSclient certificates for service identity.
Noise protocol framework (WireGuard)for closed systems with pre-shared static keys, simpler than TLS.
Encrypted Client Hello (ECH)hides SNI; deployed by Cloudflare and browsers, still rolling out.

In production

  • Libraries: BoringSSL (Chrome, Cloudflare), OpenSSL 3, rustls, Go crypto/tls, s2n-tls (AWS), NSS (Firefox), Windows Schannel (TLS 1.3 suites listed in Microsoft docs).
  • Cloud and IoT: AWS Application Load Balancer policies (docs), AWS IoT Core (docs).
  • Government: NIST SP 800-52r2 (NIST).
  • Web transport: QUIC embeds the TLS 1.3 handshake (RFC 9000).

Implementations

namelangrepoinstallverdictlicencehealthas_ofnotes
rustlsRustrustls/rustlscargo add rustls————memory-safe TLS 1.2/1.3; safe defaults
s2n-tlsC / Rust bindingsaws/s2n-tlscargo add s2n-tls————AWS TLS library; Rust crate wraps the C core
boringRust bindingscloudflare/boringcargo add boring————BoringSSL bindings
OpenSSLCopenssl/opensslbrew install openssl@3————most deployed; cargo add openssl for Rust bindings
Go crypto/tlsGogolang/gon/a (standard library)————TLS 1.3 server and client in the Go standard library

Sources

  • Rescorla, RFC 8446: The Transport Layer Security (TLS) Protocol Version 1.3, 2018. rfc
  • Sheffer, Saint-Andre, Fossati, RFC 9325: Recommendations for Secure Use of TLS and DTLS, 2022. rfc
  • Moriarty, Farrell, RFC 8996: Deprecating TLS 1.0 and TLS 1.1, 2021. rfc
  • Ghedini, Vasiliev, RFC 8879: TLS Certificate Compression, 2020. rfc
  • Mozilla, Server Side TLS guidelines and SSL Configuration Generator. guidelines · generator

Builds on

Used by

CDN and edgeUsers far from your servers see slow responses, and traffic spikes or attacks hit the origin directly. This guide answers whether and how to put a CDN or edge compute in front of a service.
email delivery smtp · plannedOutbound SMTP: queues, MX lookup, retries, bounces, feedback loops and IP warm-up
onion routing · plannedLayered encryption through relay circuits: telescoping handshakes (ntor), fixed cells, stream multiplexing
QUICApplications on slow, lossy or mobile networks need a connection that starts quickly, survives a change of network, and does not let one lost packet stall unrelated data, all while staying encrypted.
tls interception · plannedTerminate and re-originate TLS with an on-the-fly CA for inspection; pinning and privacy limits
tls termination · plannedTerminate TLS at a proxy: SNI cert selection, session resumption, OCSP, kTLS offload (listed in Moved to the wiki)
traffic obfuscation · plannedMaking tunneled traffic look like something else (obfs4, domain fronting, meek) to evade blocking
webrtc · plannedWebRTC stack: ICE, DTLS-SRTP, RTP, congestion control and data channels for real-time media

Systems (53):

Alternatives and related