Networking Tutorial · Core Protocols · Lesson 14 of 28
HTTPS and TLS
How TLS encrypts connections and proves a server's identity with certificates, and the errors you'll meet.
What TLS does
TLS (Transport Layer Security) sits between TCP and HTTP. HTTPS is simply HTTP over TLS. It provides:
- Encryption, so nobody on the path can read the traffic.
- Integrity, so nobody can change it unnoticed.
- Authentication: the server proves it really is
shop.example.comwith a certificate.
The TLS 1.3 handshake
The client says which versions and ciphers it supports and sends a key share. The server answers with its key share, its certificate and a signature proving it holds the certificate's private key. Both sides derive the same session keys. TLS 1.3 needs one round trip (TLS 1.2 needed two).
Certificates and the chain of trust
A certificate binds a name (shop.example.com, or *.example.com) to a public key and is signed by a
certificate authority (CA). Browsers trust a set of root CAs; servers send their certificate plus any
intermediate certificates, and the client follows the chain up to a trusted root.
TRUSTED_ROOTS = {"Example Root CA"}
certs = {
"shop.example.com": {"issuer": "Example Intermediate CA", "names": ["shop.example.com", "www.shop.example.com"]},
"Example Intermediate CA": {"issuer": "Example Root CA"},
}
def verify(hostname, sent_chain):
leaf = sent_chain[0]
if hostname not in certs[leaf]["names"]:
return "hostname mismatch"
current = leaf
while certs[current]["issuer"] not in TRUSTED_ROOTS:
issuer = certs[current]["issuer"]
if issuer not in sent_chain:
return f"unable to get issuer certificate ({issuer} not sent)"
current = issuer
return "trusted"
print(verify("shop.example.com", ["shop.example.com", "Example Intermediate CA"]))
print(verify("shop.example.com", ["shop.example.com"]))
print(verify("api.example.com", ["shop.example.com", "Example Intermediate CA"]))
Output
trusted unable to get issuer certificate (Example Intermediate CA not sent) hostname mismatch
Common TLS errors
| Error | Usual cause |
|---|---|
| Certificate has expired | Renewal failed or wasn't automated |
| Hostname mismatch | Certificate is for a different name, or the client used an IP address |
| Unable to get local issuer certificate | Server didn't send the intermediate certificate |
| Self-signed certificate | Internal CA not trusted by the client |
Automate renewal (Let's Encrypt via ACME, AWS Certificate Manager, Azure Key Vault, Google-managed certificates) and alert well before expiry.
$ openssl s_client -connect example.com:443 -servername example.com </dev/null | openssl x509 -noout -dates -subject
Test yourself
Browsers accept your certificate, but a Python client fails with 'unable to get local issuer certificate'. What's the likely fix?
Configure the server to send the intermediate certificate (the full chain)Switch the server to HTTPDisable certificate verification in the clientUse a longer RSA key
Show answer
Answer: Configure the server to send the intermediate certificate (the full chain)
Browsers often cache or fetch missing intermediates; many other clients don't. Serve the full chain; never disable verification.