Transport Layer Security β€” encrypts and authenticates TCP (and UDP, via DTLS / QUIC). Successor of SSL. Today, only TLS 1.2 and TLS 1.3 are acceptable; everything below is deprecated.

What TLS gives you

  1. Confidentiality β€” symmetric encryption of records (AES-GCM, ChaCha20-Poly1305).
  2. Integrity β€” AEAD tags or HMAC.
  3. Authentication β€” server identity proved by an X.509 certificate signed by a trusted CA; client auth optional.
  4. Forward secrecy β€” ephemeral key exchange (ECDHE/DHE) so a future key compromise doesn’t decrypt past sessions.

Handshake β€” TLS 1.3 (1-RTT, the modern default)

Client                                                  Server
  β”‚                                                        β”‚
  β”‚  ── ClientHello   (versions, ciphers, key_share,       β”‚
  β”‚                    SNI, ALPN, …)        ────────────►  β”‚
  β”‚                                                        β”‚
  β”‚  ◄── ServerHello       (chosen params, key_share)      β”‚
  β”‚      {EncryptedExtensions, Certificate, CertVerify,    β”‚
  β”‚       Finished}                                        β”‚
  β”‚                                                        β”‚
  β”‚  ──► {Finished}                                        β”‚
  β”‚                                                        β”‚
  β”‚  ══ application data ═════════════════════════════════ β”‚
  • key_share extension carries an ECDHE public point so the shared secret is derived immediately β€” no separate KeyExchange round.
  • Everything after ServerHello is encrypted ({…} above).
  • 0-RTT (β€œearly data”) on resumption: client may send app data on the first flight using a pre-shared key (PSK). Risk: replay; only use for idempotent requests.

Handshake β€” TLS 1.2 (2-RTT)

ClientHello β†’ ServerHello, Certificate, ServerKeyExchange, ServerHelloDone
ClientKeyExchange, ChangeCipherSpec, Finished β†’
                                                ← ChangeCipherSpec, Finished

Cipher suites

TLS 1.2 names: Kx_Auth_WITH_Cipher_Mac (e.g. ECDHE_RSA_WITH_AES_128_GCM_SHA256). TLS 1.3 simplified: AEAD + hash only (e.g. TLS_AES_128_GCM_SHA256); key exchange and signature are negotiated separately.

ComponentModern picks
Key exchangeECDHE over X25519 or secp256r1; PQ hybrid X25519+Kyber768
Signatureed25519, ecdsa-secp256r1, rsa-pss-rsae-sha256
AEADAES-128-GCM, AES-256-GCM, ChaCha20-Poly1305
HashSHA-256, SHA-384

Drop forever: RC4, DES/3DES, MD5, SHA-1, anything _EXPORT_, anonymous DH, static RSA key exchange (no forward secrecy).

Versions (status as of 2026)

VersionStatusNotes
SSL 2/3ForbiddenPOODLE, etc.
TLS 1.0DeprecatedRemoved from major browsers
TLS 1.1DeprecatedSame
TLS 1.2OKStill widely deployed
TLS 1.3PreferredRFC 8446; AEAD-only, 1-RTT, fewer pitfalls

Certificate chain

Server cert (leaf)  ─signed by─►  Intermediate CA  ─signed by─►  Root CA
                                                                  (in OS / browser trust store)

Server should send leaf + intermediates (NOT the root). Client validates: signatures, validity dates, SAN matches hostname, revocation status (OCSP/CRL/CT).

Common name (CN) is legacy β€” modern clients (Chrome) require Subject Alternative Name for hostname matching.

Extensions you’ll see

ExtensionPurpose
SNI (Server Name Indication)Pick a vhost during the handshake (plaintext today; ECH hides it)
ALPNNegotiate app protocol (h2, http/1.1, h3)
supported_versionsList of TLS versions client accepts
key_shareECDHE public key(s) β€” TLS 1.3
signature_algorithmsAcceptable cert signature schemes
psk_key_exchange_modesResumption mode
ECH (Encrypted Client Hello)Hides SNI from on-path observers (rolling out)
OCSP StaplingServer attaches a fresh OCSP response
Certificate TransparencySCTs proving the cert is logged

Session resumption

  • Session ID (TLS 1.2): server caches state, client presents ID. Mostly obsolete.
  • Session tickets: server encrypts the session state and gives it to the client to present later. Stateless on the server.
  • PSK (TLS 1.3): tickets reframed as pre-shared keys. Enables 0-RTT.

mTLS (mutual TLS / client certs)

Server requests a cert from the client with CertificateRequest. Common in service-to-service authentication and zero-trust networks. Validate against an internal CA, not public CAs.

Anatomy of a record

+----+----+----+----+----------------------------+
|type| ver | length |       payload + tag        |
+----+----+----+----+----------------------------+
type: 20 ChangeCipherSpec | 21 Alert | 22 Handshake | 23 ApplicationData

After the handshake, only encrypted ApplicationData records (and occasional Alerts) appear on the wire.

OpenSSL / CLI

# Generate Ed25519 key + self-signed cert
openssl genpkey -algorithm ed25519 -out key.pem
openssl req -x509 -key key.pem -days 90 -subj "/CN=localhost" -out cert.pem
 
# Modern RSA / ECDSA keys
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out key.pem
openssl ecparam -name prime256v1 -genkey -noout -out key.pem
 
# CSR + sign (with your own CA)
openssl req -new -key key.pem -out req.csr -subj "/CN=example.com" \
        -addext "subjectAltName=DNS:example.com,DNS:www.example.com"
openssl x509 -req -in req.csr -CA ca.pem -CAkey ca.key -CAcreateserial \
        -out cert.pem -days 365 -extfile <(printf 'subjectAltName=DNS:example.com')
 
# Inspect
openssl x509 -in cert.pem -noout -text
openssl x509 -in cert.pem -noout -dates -subject -issuer -fingerprint -sha256
openssl req  -in req.csr  -noout -text
openssl pkey -in key.pem  -noout -text -pubout
 
# Talk to a server (TLS 1.3 only, with SNI + ALPN)
openssl s_client -connect example.com:443 -servername example.com \
        -tls1_3 -alpn h2,http/1.1 -showcerts < /dev/null
 
# Save the certificate chain
openssl s_client -showcerts -connect example.com:443 -servername example.com \
        < /dev/null | openssl crl2pkcs7 -nocrl -certfile /dev/stdin \
        | openssl pkcs7 -print_certs -out chain.pem
 
# Check expiry of remote cert
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
      | openssl x509 -noout -enddate
 
# Convert formats
openssl x509 -in cert.pem -outform DER -out cert.der
openssl pkcs12 -export -out bundle.p12 -inkey key.pem -in cert.pem -certfile chain.pem

Easy testing

# curl over HTTPS with verbose handshake
curl -v --tls-max 1.3 https://example.com
 
# Pin to a known cert / CA
curl --cacert internal-ca.pem https://internal.example.com
 
# Test which versions/ciphers a server supports
nmap --script ssl-enum-ciphers -p 443 example.com
testssl.sh example.com                     # excellent external tool

ssllabs.com/ssltest β€” full external grade.

Common error messages, decoded

MessageCause
unknown CAClient doesn’t trust the issuer (missing root or intermediate)
certificate has expiredSelf-explanatory; check NotAfter
hostname mismatch / SNIHostname not in SAN (CN is no longer enough)
handshake_failureNo common cipher / version / signature algorithm
bad_certificate, certificate_unknownChain validation failed
no_application_protocolALPN list didn’t include anything the server supports
decryption_failed_reserved / bad_record_macOften MITM or middlebox tampering, or version mismatch

Best-practice cheatsheet

  • Disable TLS ≀ 1.1; serve TLS 1.2 + 1.3.
  • Cipher order: prefer AEAD + ECDHE; let TLS 1.3 negotiate itself.
  • Always send the intermediate chain; don’t rely on AIA fetching.
  • HSTS + preload for browser-facing sites; pair with HTTPS-only cookies.
  • OCSP stapling + must-staple if your CA supports it.
  • Automate renewal (ACME / Let’s Encrypt); 90-day certs make outages cheap-to-recover and force you to keep the pipeline working.
  • For internal services, run a private CA (step-ca, Vault PKI, smallstep, cert-manager) β€” issue short-lived certs and skip pinning headaches.
  • Don’t pin in mobile apps unless you have a rotation strategy β€” pin to a backup key too.