TLS & certificates
The padlock in the browser stands for TLS, the layer that turns HTTP into HTTPS. It does two jobs: it encrypts everything, so nobody on the way can read or change it, and it proves who you're talking to, so a fake site can't pretend to be your bank. That second job is all about certificates, and it's where almost every "SSL error" comes from.
You will learn
- What a certificate says, and who vouches for it (the chain of trust)
- What happens in a TLS handshake
- Reading certificates with
curl -v,openssl s_clientandopenssl x509 - The four classic errors (expired, wrong name, self-signed, missing intermediate) and how to fix each
- Trusting your own company's certificate authority, on each family
A certificate is a signed ID card
A certificate says "this public key belongs to good.example.com", plus a few facts:
- Subject and Subject Alternative Names (SANs): the names it's valid for. Browsers only look at the SANs.
- Issuer: who signed it, the certificate authority (CA).
- Validity: not before and not after. Let's Encrypt certificates last 90 days.
- The public key. The matching private key stays on the server, secret. Anyone with it can pretend to be the site.
The chain of trust
Your computer can't know every website, so it trusts a small set of root CAs, kept in the system's trust store. Roots don't sign websites directly: they sign an intermediate certificate, and the intermediate signs the site.
ISRG Root X1 # root: already in your trust store └─ R11 (Let's Encrypt) # intermediate: the server must send this one └─ good.example.com
The server sends its own certificate and the intermediates. Your side checks each signature up to a root it trusts, checks the dates, and checks that the name you asked for is in the SANs. One failed check and the connection stops.
The handshake, briefly
- Client hello: "I speak TLS 1.3, these ciphers, and I want
good.example.com" (that name is the SNI, so one server can host many HTTPS sites). - Server hello + certificate: the server picks the settings and sends its chain.
- The client verifies the chain, both sides agree on a secret key, and from then on everything is encrypted.
curl -v https://… prints every step. The line that matters most is SSL certificate verify ok.
Tools for looking
| Try | To… |
|---|---|
curl -v https://SITE | watch the handshake and the verdict |
openssl s_client -connect SITE:443 -servername SITE </dev/null | see the whole chain the server sends, and the Verify return code |
… -showcerts | print every certificate the server sends (not just the first) |
… | openssl x509 -noout -subject -issuer -dates | read the site's certificate |
openssl x509 -noout -ext subjectAltName | the names it's valid for |
openssl x509 -noout -checkend 2592000 | "will it expire in the next 30 days?" (perfect for monitoring) |
The four classic errors
| curl says | OpenSSL code | Means | Fix (on the server) |
|---|---|---|---|
certificate has expired | 10 | Past its not after date | Renew it, and make renewal automatic (certbot renew via a timer) |
no alternative certificate subject name matches | 62 | Valid certificate, but for a different name | Get one with the right name in its SANs (or use the right URL) |
self-signed certificate | 18 | Signed by itself; nobody vouches for it | Use a real CA, or trust your own CA on purpose (below) |
unable to get local issuer certificate | 20 | The chain can't be built: an intermediate is missing, or the root isn't trusted | Send the full chain (fullchain.pem, not cert.pem), or add your CA to the trust store |
curl -k (or verify=False in code) skips the checks. It's handy for one quick look, but it turns off the very thing that stops someone pretending to be the server. Never leave it in a script.
A sneaky one: a missing intermediate often works in a browser (browsers can go and fetch it) but fails in curl, scripts and apps. If "it works for me in Chrome" but not from a server, check the chain with -showcerts.
Trusting your own CA
Companies and clubs often run their own CA for internal sites (like wiki.club.lan), because public CAs won't sign private names. To make a server trust it, add the CA's certificate to the system trust store. This is very Linux-specific:
| Task | Rocky / RHEL | Ubuntu / Debian |
|---|---|---|
| Put the CA certificate in | /etc/pki/ca-trust/source/anchors/ | /usr/local/share/ca-certificates/ (the name must end in .crt) |
| Rebuild the trust store | sudo update-ca-trust | sudo update-ca-certificates |
| The bundle apps read | /etc/pki/tls/certs/ca-bundle.crt | /etc/ssl/certs/ca-certificates.crt |
| Where private keys usually live | /etc/pki/tls/private/ | /etc/ssl/private/ |
Only add CAs you really trust: a CA in your trust store can vouch for any website, including fake ones.
Try it: the certificate clinic 🔏
Five club websites, five HTTPS problems. Diagnose each one, fix the one you can fix from this server, and set up an early warning for the one that's about to expire.
Quick check
1. A site works in your browser, but curl on a server says "unable to get local issuer certificate". The certificate isn't expired. Most likely?
✓ Check with openssl s_client -showcerts: only one certificate means the chain is incomplete. Serve the full chain.
2. Which part of a certificate does a browser check against the name in the address bar?
✓ Modern clients ignore the old CN field and use only the SAN list.
3. A colleague "fixes" an HTTPS error in a script by adding -k. What's the problem?
✓ Fix the real cause (renew, send the chain, trust the right CA) instead.