HTTPS
Every request and response in the previous article traveled as plain text. Anyone positioned between the client and the server — a compromised Wi-Fi access point, a router along the path, an ISP — could read a POST /login body byte for byte, including the password in it, or quietly rewrite a 200 OK response before it reached the browser. HTTPS is HTTP run over an encrypted, authenticated connection instead of a bare TCP socket, and it exists specifically to close that gap. The "S" doesn't change a single method, header, or status code from the previous article — it changes what's wrapped around them before they ever go on the wire.
What TLS adds underneath HTTP
HTTPS is HTTP layered on top of TLS (Transport Layer Security), which itself sits on top of TCP. In terms of the TCP/IP model, HTTP is still the same application-layer protocol; TLS inserts a security layer between it and the transport layer, and every HTTP byte — request line, headers, body — passes through TLS encryption before TCP ever sees it. A packet capture of HTTPS traffic shows TCP segments carrying opaque encrypted payload; nothing about the HTTP request line or headers is visible without the session keys.
TLS gives the connection three properties HTTP alone has no way to provide:
- Confidentiality. Data is encrypted so an eavesdropper on the path — the compromised-Wi-Fi scenario above — sees ciphertext, not the request or response content.
- Integrity. Every TLS record carries an authentication tag; if even a single bit is altered in transit, the receiving side detects it and the connection fails, rather than silently accepting tampered data.
- Authentication. The client gets cryptographic proof that it's actually talking to the server it intended to reach, not an impostor sitting in the middle — this is what a certificate is for.
Getting from a plain TCP connection to an encrypted one takes an extra round trip before the first HTTP byte moves: the TLS handshake, where the client and server agree on a TLS version and cipher suite, the server proves its identity with a certificate, and both sides derive a shared symmetric key used to encrypt everything that follows.
sequenceDiagram
participant C as Client
participant S as Server
C->>S: TCP three-way handshake
C->>S: ClientHello (supported TLS versions, ciphers)
S->>C: ServerHello + certificate + key material
Note over C: Verify certificate against a trusted CA
C->>S: Finish key exchange
Note over C,S: Symmetric session key now shared
C->>S: Encrypted HTTP request
S->>C: Encrypted HTTP response
This is a deliberately high-level picture. The exact handshake mechanics — what a ClientHello actually contains, how the shared key gets derived, what changed between TLS 1.2 and 1.3 — are covered in depth later in this course, in the module on network security fundamentals. What matters here is the shape: the handshake happens once, up front, and every HTTP exchange that follows on that same connection rides on the encrypted channel it produced.
Certificates: proving the server is who it claims to be
Encryption alone stops an eavesdropper from reading traffic, but it doesn't stop someone from intercepting the connection entirely and pretending to be the real server — a man-in-the-middle attack, covered in more depth later in this course. Certificates are what closes that gap: a certificate binds a public key to a domain name, and it's signed by a certificate authority (CA) — an organization like Let's Encrypt or DigiCert that the client's operating system or browser already trusts by default, with the CA's own public key baked in ahead of time.
When a browser connects to https://example.com, the server presents a certificate claiming to be example.com. The browser checks that the certificate's signature traces back to a CA it already trusts, that the certificate hasn't expired, and that the domain name on the certificate actually matches example.com. If any of those checks fail, the browser refuses to proceed silently — it shows a hard warning, because silently continuing would defeat the entire point of the check.
openssl s_client shows a server's certificate chain directly, which is useful for troubleshooting a certificate problem before it becomes a browser warning a customer reports:
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null | openssl x509 -noout -dates -subject -issuer
subject=CN=example.com
issuer=C=US, O=Let's Encrypt, CN=R11
notBefore=May 1 00:12:03 2026 GMT
notAfter=Jul 30 00:12:02 2026 GMT
The -servername flag matters and is easy to forget: it sends SNI (Server Name Indication), the TLS-layer equivalent of HTTP's Host header, telling a virtually-hosted server which certificate to present before the encrypted session even exists — a server hosting multiple HTTPS sites on one IP address, exactly the situation the previous article described for plain HTTP's Host header, needs SNI to know which certificate applies, since without it, encryption would already be underway before the server could ask which site was wanted.
Port and the plaintext fallback
HTTPS's well-known port is 443, distinct from HTTP's port 80 — a server can, and usually does, listen on both simultaneously, serving encrypted and unencrypted traffic side by side. What happens on port 80 in a properly configured production site is rarely "serve the site over plaintext anyway": it's almost always an immediate 301 Moved Permanently redirect to the https:// version of the same URL, so a client that types a bare domain into its address bar without a protocol, or follows an old plain HTTP link, gets bounced onto the encrypted connection before any sensitive data is exchanged.
That redirect has a well-known weakness on its own: the very first request, before the redirect happens, is unencrypted, giving an attacker on the path one brief window to intercept it — a technique called SSL stripping. The Strict-Transport-Security response header (HSTS) closes that window for repeat visits: once a browser has seen it from a given site, it refuses to make a plain HTTP request to that domain at all for the duration specified, rewriting the request to HTTPS internally before it ever leaves the machine.
Mixed content: the trap of "mostly" encrypted
A page served over HTTPS that loads even one subresource — an image, a script, a stylesheet — over plain http:// creates mixed content, and modern browsers actively block the insecure subresource rather than silently allowing it, because a script loaded in cleartext can be replaced in transit by anyone on the path, defeating the page's own encryption regardless of how the HTML itself arrived. This is a genuinely common production issue: a site migrates to HTTPS, but a handful of hardcoded http:// image URLs from years-old content survive the migration, and the browser console — not a user complaint — is usually where it first surfaces, as broken images and a Mixed Content warning rather than an outright page failure.
Practical scenario: an outage that looks like DNS but isn't
A production API starts returning connection failures for every client at almost the exact same moment, with no deployment, no DNS change, and no infrastructure alert in the preceding hour. The on-call engineer's first instinct is to suspect DNS or routing, but dig and a plain TCP connection test both succeed — the server is reachable, and name resolution is fine.
openssl s_client -connect api.example.com:443 -servername api.example.com </dev/null 2>/dev/null | openssl x509 -noout -dates
Today's date is past notAfter. The TLS certificate expired, and every TLS-aware client — browsers, curl, every properly implemented HTTP library — refuses to complete the handshake against an expired certificate by design, which is exactly why the failure looked total and instantaneous rather than gradual: it isn't a capacity or routing problem at all, it's every client independently rejecting the same handshake at once. The proximate cause is almost always a lapsed renewal automation — a certbot renewal cron job that silently stopped running, or a manually issued certificate nobody scheduled a reminder for. The fix is renewing the certificate; the actual follow-up work is adding monitoring that alerts on certificate expiry days in advance, rather than relying on production traffic failing as the notification mechanism.
A certificate renewal on a live server briefly interrupts TLS for connections mid-handshake
Reloading a web server's configuration to pick up a renewed certificate is usually fast and low-risk, but on a server handling significant traffic it's still worth doing during a lower-traffic window when possible, and confirming the new certificate is live afterward — openssl s_client against the production hostname, same as above — rather than assuming the reload succeeded silently.
Practice exercises
- Run the
openssl s_clientcommand above against a production HTTPS site you use regularly, and identify the certificate's issuer, expiry date, and subject. - Explain why a browser treats an expired certificate as a hard failure rather than a warning the user can dismiss and continue past, the way some browsers still allow for a self-signed certificate in a lab environment.
- A teammate proposes fixing a mixed-content warning by changing the flagged image tag from
http://cdn.example.com/logo.pngto//cdn.example.com/logo.png(a protocol-relative URL) instead ofhttps://cdn.example.com/logo.png. Explain whether this actually solves the problem on a page being served over HTTPS, and why.
HTTP and HTTPS, as covered across these two articles, describe a single request/response exchange on a single connection. Both articles have shown that exchange succeeding. Production traffic is mostly the other cases — a status code that names which component failed, and a set of headers deciding what may be cached, who the client really was before three proxies rewrote the connection, and whether a browser will let JavaScript read the answer at all.