What HTTPS adds to HTTP

Plain HTTP (see HTTP in Depth) sends every request and response as readable text. Anyone on the path — the café Wi-Fi, a compromised router, an internet provider — can read it, and change it. HTTPS (Hypertext Transfer Protocol Secure) is the same HTTP, carried inside a Transport Layer Security (TLS) connection, by default on TCP port 443 instead of 80. Nothing about methods, headers or status codes changes; TLS wraps around them.

TLS adds three guarantees:

  • Encryption (confidentiality): someone on the path sees that you talk to a server, how much and when — but not the URL path, the headers, the cookies or the body.
  • Integrity: every record carries an authentication tag; a changed byte makes the connection fail instead of delivering altered content.
  • Server authentication: the server proves it holds the private key for a certificate that a trusted certificate authority issued for the name you asked for. How that trust works is explained in PKI and Certificates.

What stays visible: the server's IP address and, in most connections today, the host name, because the client sends it in the clear as Server Name Indication (SNI) so that one server can pick the right certificate. DNS lookups are also visible unless the device uses encrypted DNS.

What the padlock does and does not mean

The padlock — or, in recent Chrome versions, a neutral settings icon in the same place — means one thing: this connection is encrypted, and the other end proved it holds a valid certificate for exactly this host name. It says nothing about who runs the site or whether it is safe. A phishing site gets a free, valid certificate for its own look-alike name in minutes. Teach users to read the name, not the icon (see the padlock in PKI and Certificates).

The padlock also says nothing about what happens after the server: a reverse proxy may decrypt the traffic and pass it on in plain HTTP inside the data center (see TLS termination), and a company proxy may inspect it with its own root certificate.

The TLS 1.3 handshake, simplified

Before any HTTP is sent, client and server run a TLS handshake: they agree on a TLS version and cipher suite, exchange key shares from which both compute the same secret session keys, and the server proves its identity with its certificate. In TLS 1.3 (RFC 8446, 2018) this costs a single round trip after the TCP connection (three-way handshake) is up.

TLS 1.3 handshake: after the TCP connection, the browser sends ClientHello with versions, cipher suites, key share and SNI. The server answers with ServerHello and its key share, followed by its certificate chain, CertificateVerify and Finished, already encrypted. The browser sends Finished and the first HTTP request; the server sends the HTTP response. The handshake takes one round trip. Browser portal.example.com TCP connection first (SYN, SYN-ACK, ACK) ClientHello: versions, cipher suites, key share, SNI ServerHello: chosen cipher suite, key share encrypted: certificate chain, CertificateVerify, Finished Finished + first HTTP request (encrypted) HTTP response (encrypted) one round trip
TLS 1.3: the server's certificate already travels encrypted, and the browser can send its request together with its Finished message.
  1. ClientHello. The browser lists the TLS versions and cipher suites it supports, sends a key share (its half of an ephemeral Diffie–Hellman exchange), the host name (SNI) and the application protocols it can speak, such as h2 and http/1.1 (Application-Layer Protocol Negotiation, ALPN).
  2. ServerHello. The server picks the version and cipher suite and sends its own key share. From the two shares both sides now compute the same secret, so everything that follows is encrypted.
  3. Certificate, CertificateVerify, Finished. The server sends its certificate chain and a signature over the handshake made with its private key — the proof that it owns the certificate. Finished seals the handshake against tampering.
  4. Client checks and Finished. The browser validates the chain, the name and the dates (next section). If all is well it sends its own Finished and, without waiting, its first HTTP request.

Because the session keys come from throwaway key shares, recorded traffic cannot be decrypted later even if the server's private key leaks (forward secrecy). TLS 1.3 also allows a resumed connection to send data in its very first flight (0-RTT, "early data"); that data can be replayed by an attacker, so servers should accept it only for requests that are safe to repeat, such as a plain GET. HTTP/3 builds the same TLS 1.3 handshake into QUIC, so the transport and the encryption are set up together (see HTTP in Depth).

Which TLS versions to allow

Version Status (as of 2026)
SSL 2.0, SSL 3.0 Broken and prohibited; disable everywhere
TLS 1.0, TLS 1.1 Formally deprecated by RFC 8996 (March 2021); browsers no longer accept them
TLS 1.2 Still widely used and acceptable with modern cipher suites (forward secrecy, AES-GCM or ChaCha20-Poly1305)
TLS 1.3 Current version (RFC 8446); fewer options, all of them strong

A sensible server configuration today offers TLS 1.2 and 1.3 and nothing older. If an old device or script suddenly cannot connect after a server upgrade, it probably only speaks TLS 1.0 or 1.1: the fix is to update the client, not to re-enable the old protocol.

Certificates as the browser sees them

Click the icon in the address bar and open the certificate viewer: you see the fields a browser actually checks. How certificates, certificate authorities and the chain of trust work is covered in PKI and Certificates; here is what to look at when something is wrong.

  • Subject Alternative Name (SAN): the list of host names the certificate is valid for. Browsers match only against this list; the old Common Name (CN) in the subject is ignored for matching. A wildcard such as *.portal.example.com covers exactly one extra label on the left — api.portal.example.com, but not portal.example.com itself and not v2.api.portal.example.com.
  • Chain: leaf certificate, then one or more intermediate certificates, ending at a root that the device already trusts. The server must send the leaf and the intermediates (see root and intermediate CAs).
  • Validity: Not Before and Not After. The client compares them with its own clock — which is why a device with a wrong date sees errors on every site.

Client settings and name matching in Python

Python's HTTPS clients (urllib.request, http.client) use the context from ssl.create_default_context(). Printing its settings shows what "verify the certificate" means in practice:

import ssl

# The context Python's HTTPS clients (urllib, http.client) use by default
ctx = ssl.create_default_context()
print("check_hostname: ", ctx.check_hostname)
print("verify_mode:    ", ctx.verify_mode.name)
print("minimum_version:", ctx.minimum_version.name)

# Make the floor explicit instead of leaving it to the system's OpenSSL
ctx.minimum_version = ssl.TLSVersion.TLSv1_2
print("minimum_version:", ctx.minimum_version.name)

# What "just make the certificate error go away" really switches off
unsafe = ssl._create_unverified_context()
print("unverified:     ", unsafe.check_hostname, unsafe.verify_mode.name)
check_hostname:  True
verify_mode:     CERT_REQUIRED
minimum_version: MINIMUM_SUPPORTED
minimum_version: TLSv1_2
unverified:      False CERT_NONE

CERT_REQUIRED means the chain must lead to a trusted root; check_hostname means the name must be in the SAN. On this system it prints MINIMUM_SUPPORTED: Debian-family builds leave the floor to the system's OpenSSL, whose 3.x defaults already refuse TLS 1.0 and 1.1. Python.org builds (3.10 and later) set TLS 1.2 as the minimum themselves. Either way, setting TLSv1_2 explicitly makes your intent visible. The unverified context turns both checks off: the connection is still encrypted, but to anyone who answers — exactly what an on-path attacker needs.

The name and date checks themselves are simple. This example applies them to a certificate written out as a Python dictionary, in the shape getpeercert() returns — no network needed:

import ssl

# A certificate as Python's ssl module reports it (getpeercert())
cert = {
    "subject": ((("commonName", "portal.example.com"),),),
    "subjectAltName": (
        ("DNS", "portal.example.com"),
        ("DNS", "*.portal.example.com"),
    ),
    "notBefore": "Sep  1 00:00:00 2026 GMT",
    "notAfter": "Mar 19 23:59:59 2027 GMT",
}


def name_matches(pattern, host):
    """One SAN entry against a host name (case-insensitive)."""
    pattern, host = pattern.lower(), host.lower()
    if not pattern.startswith("*."):
        return pattern == host
    # A wildcard stands for exactly one label, in the leftmost position
    first, _, rest = host.partition(".")
    return first != "" and rest == pattern[2:]


def check(cert, host, now):
    names = [value for kind, value in cert["subjectAltName"] if kind == "DNS"]
    if not any(name_matches(n, host) for n in names):
        return "name mismatch"
    if now < ssl.cert_time_to_seconds(cert["notBefore"]):
        return "not yet valid (check the clock)"
    if now > ssl.cert_time_to_seconds(cert["notAfter"]):
        return "expired"
    return "OK"


today = ssl.cert_time_to_seconds("Oct  1 12:00:00 2026 GMT")
for host in ["portal.example.com", "PORTAL.example.com",
             "api.portal.example.com", "v2.api.portal.example.com",
             "example.com"]:
    print(f"{host:28} {check(cert, host, today)}")

later = ssl.cert_time_to_seconds("Apr  1 12:00:00 2027 GMT")
print(f"{'portal.example.com in 2027':28} {check(cert, 'portal.example.com', later)}")
portal.example.com           OK
PORTAL.example.com           OK
api.portal.example.com       OK
v2.api.portal.example.com    name mismatch
example.com                  name mismatch
portal.example.com in 2027   expired

Real clients do more — they verify every signature in the chain, key usage and revocation — but the name and date rules are exactly these. Note the lifetime of this certificate: just under 200 days, the current maximum (next sections).

HSTS: HTTPS only, from the first click

Most people type portal.example.com, not https://portal.example.com. The first request then goes out over plain HTTP, and the server answers with a redirect (301) to HTTPS. That first plain request is a gap: an attacker on the path can answer it instead and keep the victim on HTTP.

HTTP Strict Transport Security (HSTS), RFC 6797, closes that gap after the first visit. The server sends this header over HTTPS:

Strict-Transport-Security: max-age=31536000; includeSubDomains

For the next max-age seconds (here one year), the browser rewrites every http:// link to that host to https:// before sending anything, and — just as important — it no longer lets the user click through a certificate warning for that site. includeSubDomains extends the rule to every subdomain. Browsers ignore the header when it arrives over plain HTTP, because an attacker could forge it there.

The preload list

HSTS still trusts the very first visit. The preload list removes that: it is a list of domains built into the browser itself, so even the first request goes over HTTPS. Chrome maintains it, and Firefox, Safari and Edge derive their lists from it. To be accepted at hstspreload.org a domain needs a valid certificate, a redirect from HTTP to HTTPS, HTTPS on all subdomains, and a header with max-age of at least one year plus includeSubDomains and preload.

According to hstspreload.org, Chrome and Safari now also try HTTPS first for ordinary navigations. HSTS remains useful: it makes the upgrade mandatory instead of best effort, and it blocks click-through on certificate errors.

Automated certificates with ACME

The Automatic Certificate Management Environment (ACME), RFC 8555, is the protocol that lets software obtain and renew certificates without a person: an ACME client on (or near) the server creates a key pair, asks the CA for a certificate, proves control of the domain, and installs the result. Let's Encrypt made it popular; today many CAs, web servers (Caddy does it by default), load balancers and hosting panels speak ACME. A short overview is also in ACME and Let's Encrypt.

Proving control: the challenges

Challenge How the client proves control Good for
HTTP-01 Serves a token at http://<name>/.well-known/acme-challenge/<token> on port 80 Ordinary web servers reachable from the internet
DNS-01 Publishes a TXT record at _acme-challenge.<name> Wildcard certificates, servers not reachable from the internet; needs API access to DNS
TLS-ALPN-01 Answers a special TLS handshake on port 443 Proxies and servers that handle TLS themselves

A firewall that blocks port 80 "because we only use HTTPS" is a classic reason why HTTP-01 renewals suddenly fail: the CA must reach port 80 to fetch the token.

Let's Encrypt today

  • No more OCSP. Let's Encrypt shut down its Online Certificate Status Protocol (OCSP) service on 6 August 2025, for privacy reasons (OCSP tells the CA which sites a user visits). Revocation is now published only through certificate revocation lists (CRLs) — see revocation.
  • Six-day certificates (the "shortlived" profile, 160 hours) and certificates for IP addresses have been generally available since 15 January 2026. They are opt-in and not planned to become the default; IP address certificates are always short-lived.
  • 90 days, then less. The default (the "classic" profile) is still 90 days as of October 2026. Let's Encrypt has announced 64 days from 10 February 2027 and 45 days from 16 February 2028; the opt-in "tlsserver" profile already issues 45-day certificates since 13 May 2026.

Shrinking certificate lifetimes

The certificate lifetime is the period between a certificate's Not Before and Not After dates. For publicly trusted TLS certificates the CA/Browser Forum — the body of certificate authorities and browser makers that sets the rules — caps it. Ballot SC-081v3, passed on 11 April 2025, shrinks the cap in steps. At the time of writing the schedule is:

Issued from Maximum lifetime
Before 15 March 2026 398 days
15 March 2026 200 days
15 March 2027 100 days
15 March 2029 47 days

All these dates are in the ballot itself; some CAs issue a day less (199 days) to keep a margin. The same ballot also shortens how long a CA may reuse an earlier domain check, to 10 days by 2029. Any material that says "certificates last a year" is out of date.

Why shorter? A stolen key or a wrongly issued certificate is useful to an attacker only until it expires, and revocation has never worked reliably. And short lifetimes force automation, which removes the most common outage of all: the certificate someone forgot to renew. With 47-day certificates, a team that renews by hand would do so roughly eight times a year per certificate.

Certificate transparency

A CA that is tricked or compromised can issue a certificate for your domain to someone else. Certificate transparency (CT) makes that visible: CAs submit every publicly trusted certificate to several public, append-only logs, and the logs hand back signed receipts (signed certificate timestamps, SCTs) usually embedded in the certificate. Chrome, Safari and, since 2025, Firefox reject public certificates without them. The protocol is described in RFC 6962 (2013) and its successor RFC 9162 (2021), both experimental RFCs.

Because the logs are public, anyone can search them — for example at crt.sh. Two practical uses: monitor the logs for certificates issued for your own domains that you did not request, and remember that every host name you put in a public certificate becomes public too. A name like new-merger-test.example.com appears in the logs within minutes.

Mixed content

Mixed content is an HTTPS page that loads some of its parts — scripts, stylesheets, images, frames — over plain HTTP. Those parts can be read and changed on the way, which undoes the protection of the page: a replaced script controls the whole page.

  • Scripts, stylesheets, frames and requests made by scripts ("active" content) are blocked by browsers.
  • Images, audio and video are upgraded to HTTPS automatically by modern browsers and blocked if that fails. Either way, the browser's developer tools console shows a mixed-content message (see developer tools).

The fix is to load every resource over HTTPS — absolute https:// URLs or URLs relative to the page. During a migration, the Content Security Policy directive upgrade-insecure-requests tells the browser to rewrite http:// subresources to https:// (see Content Security Policy). Hard-coded http:// links in a database or a content management system are the usual leftover.

Troubleshooting certificate errors

Certificate errors look alarming, but four causes cover almost all of them. The codes are Chrome's; other browsers and tools use similar wording.

Symptom Likely cause How to confirm Fix
Expired (NET::ERR_CERT_DATE_INVALID) for everyone Renewal failed, or the new certificate was never loaded Expire date in the certificate viewer or curl -v; renewal client logs Renew, reload the server, automate and monitor
Name mismatch (NET::ERR_CERT_COMMON_NAME_INVALID) The name typed is not in the SAN: example.com vs www.example.com, an internal alias, an IP address, or two subdomain levels under a wildcard Compare the address bar with the SAN list Reissue with every name users actually use, or redirect to a covered name
Untrusted issuer (NET::ERR_CERT_AUTHORITY_INVALID); works in one browser, fails in scripts or apps Missing intermediate on the server, a self-signed or internal CA, or a TLS-inspecting proxy whose root is not on this device Look at the issuer and at how many certificates the server sends (openssl s_client -showcerts) Configure the full chain file; install the internal root through device management
Date errors on every HTTPS site from one device The device's clock is wrong (dead CMOS battery, no time sync), so valid certificates look expired or not yet valid Check the date and time on the device Fix time synchronization (NTP)

"Works in my browser, fails in curl or Python" points almost always to the missing intermediate: browsers often repair an incomplete chain themselves (from a cache or by downloading it), while most other clients do not. The PKI page has more on reading certificate problems and inspecting certificates with OpenSSL.

Watching the handshake with curl

curl -v (see curl and wget) shows the handshake, the negotiated version and the certificate it checked. Lines starting with * are curl's own commentary; the exact wording differs between curl versions.

$ curl -v -o /dev/null https://portal.example.com/
*   Trying 203.0.113.10:443...
* Connected to portal.example.com (203.0.113.10) port 443
* ALPN: curl offers h2,http/1.1
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / id-ecPublicKey
* ALPN: server accepted h2
* Server certificate:
*  subject: CN=portal.example.com
*  start date: Sep  1 00:00:00 2026 GMT
*  expire date: Mar 19 23:59:59 2027 GMT
*  subjectAltName: host "portal.example.com" matched cert's "portal.example.com"
*  issuer: C=US; O=Example CA; CN=Example Issuing CA R1
*  SSL certificate verify ok.
> GET / HTTP/2
> Host: portal.example.com
...
< HTTP/2 200

The two failures you will meet most often look like this:

curl: (60) SSL certificate problem: unable to get local issuer certificate
curl: (60) SSL: no alternative certificate subject name matches target host name 'example.com'

The first means the chain could not be completed to a trusted root (missing intermediate, internal CA, inspecting proxy); the second is a name mismatch.