HTTP and TLS¶
What You'll Learn¶
- The structure of an HTTP request and response, and what status codes really tell you
- The headers that matter for operations: caching, proxies, and host routing
- What HTTP/2 and HTTP/3 change
- How TLS protects traffic, how certificates are validated, and how to debug TLS failures
Anatomy of an HTTP Request¶
> GET /orders/42 HTTP/2
> Host: api.example.com
> User-Agent: curl/8.9.1
> Accept: */*
>
< HTTP/2 200
< content-type: application/json
< cache-control: no-store
< x-request-id: 7f1c2a9e
<
{"id": 42, "status": "shipped"}
The Host header tells the server which site you want. One IP address can serve many hostnames — reverse proxies route on it.
Methods¶
| Method | Purpose | Safe (no changes) | Idempotent (repeatable) |
|---|---|---|---|
GET |
Read a resource | Yes | Yes |
HEAD |
GET without the body |
Yes | Yes |
POST |
Create, or trigger an action | No | No |
PUT |
Replace a resource | No | Yes |
PATCH |
Partially update | No | Not necessarily |
DELETE |
Remove | No | Yes |
Idempotency matters for retries: load balancers, clients, and service meshes can safely retry idempotent requests, but retrying a POST can create a duplicate order.
Status Codes¶
| Range | Meaning | Operational reading |
|---|---|---|
2xx |
Success | 200 OK, 201 Created, 204 No Content |
3xx |
Redirect | 301 permanent, 302/307 temporary, 304 Not Modified (cache hit) |
4xx |
The client's request was wrong | 400 bad input, 401 not authenticated, 403 not allowed, 404 not found, 429 rate limited |
5xx |
The server or something in front of it failed | 500 app error, 502 bad gateway, 503 unavailable, 504 gateway timeout |
For SLOs, 5xx responses are usually counted as errors and most 4xx are not — a user mistyping a URL isn't an outage. A sudden spike in 401, 403, or 429 still deserves an alert. Load Balancers explains what 502, 503, and 504 mean when a proxy is involved.
Headers That Matter in Operations¶
| Header | Why you care |
|---|---|
Host |
Virtual-host routing in proxies and ingress controllers |
X-Forwarded-For, X-Forwarded-Proto |
The original client IP and scheme after a proxy |
Cache-Control, ETag, Age |
Whether a CDN or browser serves a cached copy |
Strict-Transport-Security |
Forces HTTPS for future visits (HSTS) |
Retry-After |
How long a client should wait after 429 or 503 |
X-Request-Id / traceparent |
Correlates one request across logs and traces |
Connection: keep-alive |
Reuse TCP connections (default in HTTP/1.1) |
HTTP/1.1, HTTP/2, and HTTP/3¶
| HTTP/1.1 | HTTP/2 | HTTP/3 | |
|---|---|---|---|
| Transport | TCP | TCP | QUIC over UDP |
| Requests per connection | One at a time | Many, multiplexed | Many, multiplexed |
| Header format | Text | Binary, compressed | Binary, compressed |
| Head-of-line blocking | Per connection | At the TCP layer | Avoided — a lost packet delays only its own stream |
| TLS | Optional | Effectively required by browsers | Always built in |
HTTP/3 needs UDP port 443 open. If a firewall blocks it, clients fall back to HTTP/2 over TCP — slower to connect but it works.
gRPC runs over HTTP/2, so any proxy in front of a gRPC service must support HTTP/2 end to end.
Timing a Request¶
curl -s -o /dev/null -w '
dns: %{time_namelookup}s
connect: %{time_connect}s
tls: %{time_appconnect}s
first byte: %{time_starttransfer}s
total: %{time_total}s
status: %{http_code}
' https://api.example.com/health
Each value is cumulative from the start. A large jump between tls and first byte means the server is slow to respond; a large dns value points at resolution; a large connect points at network latency or a SYN queue problem.
TLS¶
TLS gives three guarantees: confidentiality (encryption), integrity (tampering is detected), and authentication (you're talking to the real api.example.com).
The TLS 1.3 handshake¶
sequenceDiagram
participant C as Client
participant S as Server
C->>S: ClientHello (SNI: api.example.com, key share, supported ciphers)
S-->>C: ServerHello (key share) + certificate + signature + Finished
Note over C: Verify the certificate chain and hostname
C->>S: Finished
Note over C,S: Encrypted application data (one round trip total)
- SNI (server name indication) sends the hostname in the first message, so one IP can serve certificates for many domains.
- TLS 1.3 completes in one round trip. TLS 1.0 and 1.1 are obsolete; TLS 1.2 is still widely supported.
- Current browsers, OpenSSL 3.5+, and major CDNs negotiate a hybrid post-quantum key exchange (
X25519MLKEM768) by default, protecting today's traffic against future quantum decryption. Its largerClientHellospans more than one packet, which occasionally breaks old firewalls and TLS-inspecting middleboxes: if new clients fail to connect through one specific network path while older clients work, suspect that.
How a certificate is validated¶
flowchart BT
Leaf["Leaf certificate<br/>api.example.com"] -->|"signed by"| Int["Intermediate CA"]
Int -->|"signed by"| Root["Root CA<br/>(in the client's trust store)"]
The client checks that:
- The chain leads to a root CA it already trusts.
- The hostname matches a Subject Alternative Name in the leaf certificate (the old Common Name field is ignored by modern clients).
- The current time is within the certificate's validity period.
- The certificate hasn't been revoked.
The server must send the intermediate certificates. Browsers sometimes fetch missing intermediates on their own, which hides the problem — while curl, Java, and Python clients fail.
Inspect a certificate¶
# What the server presents, including the chain
openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null
# Subject, issuer, SANs, and expiry of the leaf
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -ext subjectAltName -dates
# Days until expiry, for a monitoring check
end=$(openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
echo $(( ( $(date -d "$end" +%s) - $(date +%s) ) / 86400 )) days left
# A certificate file on disk
openssl x509 -in /etc/ssl/certs/app.pem -noout -text | less
Common TLS Errors¶
| Error | Cause | Fix |
|---|---|---|
certificate has expired |
Renewal failed or was never automated | Automate renewal (ACME, cert-manager, a cloud certificate manager) and alert 14+ days before expiry |
hostname mismatch / no alternative certificate subject name matches |
The requested name isn't in the SANs | Reissue with the right SANs, or fix the name clients use |
unable to get local issuer certificate |
Missing intermediate in the chain | Serve the full chain (fullchain.pem) |
self-signed certificate in certificate chain |
A private CA or a TLS-inspecting proxy the client doesn't trust | Add the CA to the trust store deliberately — never disable verification |
certificate is not yet valid |
Clock skew on the client or server | Fix NTP (timedatectl) |
wrong version number |
Speaking TLS to a plain HTTP port, or the reverse | Check the port and scheme |
handshake failure |
No shared protocol version or cipher | Update the client, or review server TLS settings |
| Connection reset during handshake, only from new clients or one network | A middlebox mishandling large post-quantum ClientHello messages |
Update or bypass the middlebox; confirm by comparing with an older client |
Never ship curl -k or verify=False
Disabling certificate verification removes authentication entirely — any machine in the path can impersonate the server. Fix the trust problem instead.
Certificate Lifetimes and Automation¶
Public TLS certificates are short-lived and getting shorter. Under the CA/Browser Forum's 2025 decision, the maximum validity of a publicly trusted certificate is falling on a fixed schedule:
| Issued on or after | Maximum validity |
|---|---|
| 15 March 2026 | 200 days |
| 15 March 2027 | 100 days |
| 15 March 2029 | 47 days |
Let's Encrypt certificates already last 90 days, with shorter options available. The practical consequence: a certificate renewed by hand once a year is no longer possible, and even a quarterly manual renewal will soon miss. Automate renewal everywhere, and alert on expiry anyway. Use:
- ACME clients such as Certbot for servers
- cert-manager in Kubernetes
- Managed certificates from cloud load balancers (AWS Certificate Manager, Google-managed certificates), which renew automatically
Paste a certificate's dates to check it, and see how much manual renewal will cost under each phase of the schedule:
Certificate lifetime planner (needs JavaScript). Without it: openssl x509 -noout -dates prints notBefore and notAfter; renew at two-thirds of the lifetime, and expect a 47-day maximum for public certificates from 15 March 2029.
Mutual TLS¶
In mTLS, the client also presents a certificate and the server verifies it. It's used for service-to-service authentication inside meshes (Istio, Linkerd) and for machine clients of sensitive APIs. The same chain and expiry rules apply in both directions.
What Most Engineers Miss¶
- Let's Encrypt no longer emails you before expiry. Expiration reminder emails ended on 4 June 2025. If those emails were your monitoring, you have none.
- OCSP is going away. Let's Encrypt removed OCSP URLs from new certificates in May 2025 and shut its OCSP service down on 6 August 2025, publishing revocation through CRLs only. Software that insists on OCSP stapling for those certificates needs updating.
- Domain validation gets short-lived too. A CA may reuse a successful domain check for 398 days today, falling to 10 days in 2029. Renewal will mean proving control of the domain again almost every time, which only works with ACME HTTP-01 or DNS-01 challenges automated end to end.
- Let's Encrypt is shortening its own default. It already offers 45-day (
tlsserver) and six-day (shortlived) profiles; the default drops from 90 to 64 days on 10 February 2027 and to 45 days on 16 February 2028. - The client's trust store is part of TLS. A container built from a three-year-old base image carries three-year-old root certificates. Update
ca-certificatesin every image build. - HTTP/2 features are an attack surface. Stream cancellation, a normal HTTP/2 feature, powered the largest DDoS attacks ever recorded in 2023, and a variant in 2025. Proxies and app servers that speak HTTP/2 need prompt patching.
Real incident: Let's Encrypt's root expiry (2021)
DST Root CA X3 expired on 30 September 2021. Modern clients already trusted Let's Encrypt's newer root, but devices and servers with stale trust stores, and OpenSSL 1.0.2, which tried the expired path and gave up, started failing TLS handshakes to millions of sites (Let's Encrypt). Many of the failures were servers calling APIs from old base images, not browsers.
Real incident: Ericsson's expired certificate (2018)
On 6 December 2018, an expired certificate inside Ericsson's mobile-core software took down mobile data for O2 in the UK for most of a day and for SoftBank in Japan for over four hours, across about 11 countries (TelecomTV). Certificates live in appliances and vendor software too. Inventory them all.
Real incident: HTTP/2 Rapid Reset (2023)
In August–October 2023, attackers opened HTTP/2 streams and cancelled them immediately, over and over, so servers did the work of starting requests that clients never waited for (CVE-2023-44487). Google measured a peak of 398 million requests per second; Cloudflare and AWS reported 201 and 155 million (Google Cloud). Nearly every HTTP/2 server shipped a patch that limits cancellations per connection.
Common Mistakes¶
- Serving only the leaf certificate, so non-browser clients fail with "unable to get local issuer certificate".
- Manually renewed certificates with no expiry monitoring — still one of the most common causes of full outages.
- Retrying non-idempotent
POSTrequests at the load balancer or client, creating duplicates. - Counting every
4xxas an error in SLOs, or ignoring a spike in401s after a deploy. - Using
curl -kin scripts and health checks, hiding real certificate problems. - Terminating TLS at a load balancer and sending plaintext across networks you don't control.
Check Yourself¶
The reminder that never came
A team's Let's Encrypt certificate on an internal tool expired in 2026, and nobody got a warning. What changed?
- Let's Encrypt shortened certificates to 30 days
- Let's Encrypt stopped sending expiry emails in June 2025, and the team had no other monitoring
- Browsers stopped trusting Let's Encrypt
Monitor expiry yourself: a check that alerts when notAfter is under 14 days away, on every endpoint you own.
Works in the browser, fails in the container
A Python service in an old container image fails with certificate verify failed against an API that works in your browser. The API's chain is valid. What's the likely cause?
- The image's CA bundle is outdated and doesn't include the root the chain now uses
- The API requires HTTP/3
- Python doesn't support SNI
Rebuild with current ca-certificates (and certifi, if the client uses it).
Interview Questions¶
- What's the difference between
502,503, and504? - Which HTTP methods are idempotent, and why does that matter for retries?
- Walk through a TLS 1.3 handshake. What is SNI for?
- A Python client fails with "unable to get local issuer certificate", but the site works in a browser. Why?
- How would you make sure certificates never expire unnoticed across hundreds of services?
Next¶
Continue to Load Balancers and Reverse Proxies.