TLS in 10 Minutes

TLS turns a readable network conversation into an authenticated encrypted channel. The hard part is rarely the cipher. It is proving the right hostname, serving the full certificate chain, and renewing before a quiet date becomes an outage.

🎙️ Published & recorded: ·

01Plain HTTP exposes more than passwords

Plain HTTP provides no confidentiality and no trustworthy server identity. Someone on the path can read requests, steal session cookies, change downloaded JavaScript, or redirect an API call. “This page has no password form” is not a reason to keep HTTP. A modified script can wait until the user enters a password somewhere else.

# HTTP: readable and modifiable on the path
GET /account HTTP/1.1
Host: example.com
Cookie: session=secret-value

# HTTPS: HTTP travels inside a TLS-protected connection
https://example.com/account
Use HTTPS everywhere

Redirect port eighty to HTTPS, then add HSTS only after every required subdomain works over HTTPS. Cookies carrying sessions need Secure, HttpOnly, and an appropriate SameSite policy. TLS protects data in transit. It does not repair a vulnerable application, malicious endpoint, or leaked server key.

02The TLS handshake agrees and authenticates

Before HTTP begins, the client offers protocol versions, cryptographic choices, and the hostname it wants. The server chooses compatible settings, sends its certificate chain, and proves possession of the matching private key. The client validates the chain and hostname. Both sides derive temporary session keys, then encrypt application data.

client                         server
  |--- ClientHello ------------>|  versions, cipher suites, SNI
  |<-- ServerHello -------------|  chosen parameters
  |<-- Certificate chain -------|  site cert + intermediates
  |<-- CertificateVerify -------|  proof of private key
  |--- Finished --------------->|
  |<-- Finished ----------------|
  |=== encrypted HTTP =========>|

Modern TLS uses ephemeral key agreement so stealing the certificate key later should not decrypt old captured sessions. Do not manually choose exotic cipher lists from an old blog post. Use a maintained server or platform's modern TLS policy, disable obsolete protocol versions, and spend your attention on certificates, private-key access, and renewal.

03A certificate binds a key to a hostname

A site certificate contains a public key, validity window, issuer, and Subject Alternative Names. The requested hostname must appear in those names. The server normally sends its leaf certificate plus intermediate certificates. The client links that chain to a trusted root already in its trust store. The root usually should not be sent by the server.

# Inspect names, issuer, and dates from the live service
openssl s_client -connect example.com:443 -servername example.com \
  -showcerts </dev/null

# Inspect one saved certificate
openssl x509 -in cert.pem -noout -subject -issuer -dates \
  -ext subjectAltName
Wrong hostname, fixed

Chrome shows NET::ERR_CERT_COMMON_NAME_INVALID when the certificate does not cover the requested name. One: confirm the browser URL, including the subdomain. Two: inspect Subject Alternative Names with the command above. Three: issue a certificate containing that exact hostname. Four: install it on the virtual host that serves the name. A wildcard for *.example.com covers api.example.com, not example.com or v2.api.example.com.

04SNI selects the certificate before HTTP

One IP address often hosts many HTTPS sites. The server needs to choose a certificate before it can read the encrypted HTTP Host header. Server Name Indication solves that by putting the requested hostname in the ClientHello. If SNI is missing or the server mapping is wrong, you may receive the default site's certificate.

# Correct: connect to the IP but send SNI for the hostname
openssl s_client -connect 203.0.113.10:443 \
  -servername api.example.com </dev/null

# Test the same routing with curl while preserving hostname
curl -v --resolve api.example.com:443:203.0.113.10 \
  https://api.example.com/health
Do not test by IP alone

Opening https://203.0.113.10 asks for a certificate valid for the IP and may select the default virtual host. That does not reproduce a user's connection to api.example.com. Use --resolve or -servername so DNS is bypassed while the real hostname remains in SNI and certificate verification.

05ACME automates proof and issuance

ACME lets a certificate authority verify that you control a domain and issue a short-lived certificate automatically. HTTP-01 places a token under a well-known HTTP path. DNS-01 publishes a TXT record and is required for wildcard certificates. TLS-ALPN-01 proves control through a special TLS response on port four-four-three.

# HTTP-01 must be publicly reachable at this exact path
http://example.com/.well-known/acme-challenge/TOKEN

# DNS-01 publishes a temporary TXT value
_acme-challenge.example.com.  TXT  "challenge-value"

# Check public DNS before blaming the ACME client
dig +short TXT _acme-challenge.example.com
Challenge failure, fixed

A client may report unauthorized: Invalid response from http://example.com/.well-known/acme-challenge/.... One: request the exact challenge URL from outside the server. Two: confirm DNS points at the machine answering the challenge. Three: exempt that path from login redirects and application rewrites. Four: allow inbound port eighty for HTTP-01, or switch to DNS-01 with narrowly scoped DNS credentials.

06Terminate TLS at a deliberate boundary

A reverse proxy often owns the public certificate and forwards requests to the application. That centralizes renewal and modern TLS settings. The application must still know the original request was HTTPS, and it must trust forwarded headers only from known proxies. For traffic across an untrusted network, encrypt the proxy-to-app hop too.

# Minimal nginx shape
server {
  listen 443 ssl http2;
  server_name example.com;
  ssl_certificate     /etc/ssl/example/fullchain.pem;
  ssl_certificate_key /etc/ssl/example/privkey.pem;

  location / {
    proxy_pass http://127.0.0.1:3000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}
The redirect loop

If the browser reports ERR_TOO_MANY_REDIRECTS after TLS termination, the app may think every proxied request arrived over HTTP. One: inspect X-Forwarded-Proto at the app. Two: set it to the original scheme at the proxy. Three: configure the app's trusted-proxy list, not a blanket “trust everyone.” Four: keep exactly one HTTP-to-HTTPS redirect owner.

07mTLS authenticates clients with certificates

Ordinary HTTPS authenticates the server. Mutual TLS also asks the client for a certificate, which is useful for service-to-service links, devices, and tightly controlled partner APIs. It is strong transport identity, but it is operationally expensive. You must issue, rotate, revoke, and map client identities without locking out every caller at once.

# Call an mTLS endpoint
curl --cert client.crt --key client.key \
  --cacert service-ca.crt https://internal.example.com/health

# nginx client verification
ssl_client_certificate /etc/ssl/client-ca.pem;
ssl_verify_client on;
ssl_verify_depth 2;

Do not use one shared client certificate for a hundred services. Give each workload an identity so a compromise can be revoked narrowly and logs name the caller. Keep application authorization after certificate verification. A valid certificate can prove “inventory service,” but the application still decides whether that service may cancel an order.

08Debug the live chain, name, and clock

Start with the exact hostname and port the failing client uses. Inspect the live service rather than a certificate file you meant to deploy. Check SNI, Subject Alternative Names, validity dates, the served intermediates, and the client clock. Then compare trust stores; a browser and a container can disagree because they carry different root certificates.

# Fast HTTP and certificate trace
curl -Iv https://api.example.com/

# Verify what the server actually presents
openssl s_client -connect api.example.com:443 \
  -servername api.example.com -verify_return_error </dev/null

ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED]
certificate verify failed: unable to get local issuer certificate
Missing issuer, fixed

For unable to get local issuer certificate, one: run s_client -showcerts against the live host. Two: verify the server sends the leaf followed by the required intermediate certificate. Three: install the CA's full-chain file at the TLS terminator and reload it. Four: update the client's CA bundle if the chain is complete but its trust store is stale. Do not ship verify=False or curl -k; those switches remove the identity check TLS is meant to provide.

09Renewal is a production workflow

Automated issuance is not enough. Renewal must run, install the new files where the active process reads them, and reload that process. Monitor the certificate observed from outside, not merely a successful cron exit. Test renewal while months remain. The first failure should be an alert, not a customer screenshot.

# Show expiry from the public endpoint
echo | openssl s_client -connect example.com:443 \
  -servername example.com 2>/dev/null \
  | openssl x509 -noout -enddate

# Generic post-renewal checks
acme-client renew
nginx -t && systemctl reload nginx
curl -fsS https://example.com/health
Expired certificate, fixed

Clients may show certificate has expired. One: inspect the public endpoint's notAfter date and server clock. Two: renew or reissue the certificate. Three: deploy the new full chain and key to the actual TLS terminator. Four: reload it and inspect the public endpoint again. Five: alert well before expiry and alert when the observed serial number fails to change after renewal.

10TLS cheat sheet

These commands answer most first-response questions without disabling verification.

# inspect live TLS with correct SNI
openssl s_client -connect HOST:443 -servername HOST -showcerts
curl -Iv https://HOST/

# inspect certificate file
openssl x509 -in cert.pem -noout -subject -issuer -dates \
  -ext subjectAltName

# diagnose by symptom
wrong hostname → URL + SAN + SNI + virtual-host mapping
unknown issuer → served intermediates + client CA store
expired → public notAfter + renewal + reload
ACME failure → DNS + challenge path + reachable port

# rules worth keeping
serve leaf + intermediates · protect private key · automate renewal
monitor from outside · never use -k or verify=False in production

My blunt version: TLS libraries are usually not your problem. Deployment is. Serve the right certificate for the requested name, include the intermediate chain, automate renewal and reload, and test the endpoint from outside your network. When verification fails, fix the name, chain, date, or trust store. Turning verification off converts an actionable error into silent impersonation.

Tell me what missed

A correction is more useful than a compliment. This goes straight to the person who writes SwiftGrasp.

Was this page useful?
0/1000

Please do not include passwords, private keys, or personal information.