What HTTPS Actually Protects
Many people think HTTPS means "encryption". In reality it delivers three guarantees at once, and all three matter:
| Guarantee | Problem solved | Mechanism |
|---|---|---|
| Confidentiality | An observer cannot read the payload | Symmetric encryption (AES-GCM / ChaCha20-Poly1305) |
| Integrity | Tampering is detected immediately | AEAD authentication tag / HMAC |
| Authentication | You really are talking to example.com | Certificate chain + CA signature |
The third one is what newcomers most often miss. Encryption without authentication is meaningless — you can happily establish an encrypted channel with the attacker; it is just that the other end is the attacker. A certificate is, at its core, a trusted third party (a CA) vouching for the statement "this domain belongs to you."
To check whether your response headers are set correctly, walk through our HTTP headers cheat sheet entry by entry; for port-level basics (443, the 80 redirect) see the common ports cheat sheet.
The TLS 1.3 Handshake
TLS 1.2 needed two round trips before data could flow. TLS 1.3 compresses that to one — or zero, at the cost of replay risk. The full sequence:
Client Server
| |
|--- (1) ClientHello ------------------------------>|
| supported TLS versions, cipher suite list |
| key_share: client's (EC)DHE public key |
| signature algorithms, ALPN (h2/http1.1) |
| SNI: target hostname |
| |
|<-- (2) ServerHello -------------------------------|
| selected version and cipher suite |
| key_share: server's (EC)DHE public key |
| {Certificate chain} <- server identity |
| {CertificateVerify} <- signs handshake digest |
| {Finished} |
| |
|--- (3) Finished + application data -------------->|
| |
|<== application data (symmetric) =================>|
A few points deserve to stand alone:
key_sharegoes out in the ClientHello. This is precisely why TLS 1.3 saves a round trip over 1.2: key exchange material rides along on the first trip instead of waiting for a server round trip.CertificateVerifyis the actual proof of identity. The certificate itself is public; anyone can relay it. The server must sign a digest of the handshake so far with the private key matching the certificate, and the client verifies that signature with the public key in the certificate. Only then is possession of the private key proven.- Certificates travel in the clear. They are public by design — they need integrity (guaranteed by the signature), not secrecy. Never put anything sensitive in a certificate.
- SNI is also in the clear. An observer can see which hostname you are visiting. That is exactly what ECH (Encrypted Client Hello) is meant to fix, and its deployment is still limited.
Certificate Chains: How Trust Is Transferred
Browsers ship only around a hundred-odd root CA certificates locally. Your certificate is not signed by a root directly but by an intermediate CA, which the root then signs. This exists for safety: root private keys can stay offline in hardware security modules while intermediates handle day-to-day issuance. If an intermediate is compromised, it gets revoked without shaking the root.
Root CA (offline, shipped in OS/browser trust store)
└── Intermediate CA (online issuance, e.g. R3 / E1)
└── Leaf certificate (your domain, e.g. oltool.net)
The browser walks upward from the leaf, verifying each signature, until it reaches a root it already trusts. Two practical consequences:
- Always deploy the fullchain, never the leaf alone. The server must send the intermediate too. Omitting it is the single most common trust failure — see the troubleshooting table below.
- Cross-signing creates multiple paths. The same intermediate may be signed by two roots (during a root transition), and the browser tries every path — it trusts the chain if any one works. This is why some old devices succeed where new ones fail.
Choosing a Certificate Type
By validation level
| Level | What is verified | Issuance time | Address bar | Best for |
|---|---|---|---|---|
| DV Domain Validated | You control the domain | Seconds to minutes | Padlock | Blogs, tool sites, APIs, most sites |
| OV Organization Validated | Domain + a real legal entity (manual business record review) | 1–3 days | Padlock, org name visible in cert details | Corporate sites, B2B |
| EV Extended Validation | Strictest legal-entity vetting | 1–2 weeks | Modern browsers no longer show a green company name | Finance, payments, where compliance demands it |
EV's practical value has shrunk considerably: Chrome and Firefox both removed the green organization name from the address bar, so users cannot tell the difference. Unless your compliance audit explicitly requires EV, DV plus solid security practices is the better deal.
By domain coverage
| Type | Covers | Example | Caveat |
|---|---|---|---|
| Single domain | One exact hostname | www.oltool.net |
Usually bundles the bare domain or www — confirm before issuing |
| Wildcard | All subdomains at one level | *.oltool.net |
Does not match nested levels: a.b.oltool.net is not covered |
| Multi-domain SAN | Any set of hostnames, across domains | a.com + b.net + *.c.org |
The list lives in the SAN extension; growing it means reissuing |
Wildcards carry a practical hazard: they widen the blast radius of the private key. One *.example.com key must be distributed to every machine serving a subdomain, so any single compromise reaches all of them, and revocation has an enormous impact. Large deployments are better served by per-service certificates with automated management than by one convenient wildcard.
Three Ways to Get a Certificate
| Path | Cost | Lifetime | Automation | Best for |
|---|---|---|---|---|
| Let's Encrypt / ACME | Free | 90 days | Excellent (HTTP-01 / DNS-01 / TLS-ALPN-01) | Public web services — the default choice |
| Commercial CA | Paid | Up to ~1 year (and shrinking) | Varies by vendor | OV/EV needs, warranty, IP certificates, audits |
| Self-signed / private CA | Free | Your choice | You build it | Internal networks, service-to-service mTLS, dev |
Choosing an ACME challenge type:
HTTP-01: the CA fetches a file athttp://<domain>/.well-known/acme-challenge/. Simple, but requires port 80 to be reachable and cannot issue wildcard certificates.DNS-01: you add a_acme-challengeTXT record. More setup (you need DNS provider API credentials), but it supports wildcards and does not require the server to be publicly reachable — internal hosts can be issued too. This is the recommended option for production.
You can generate keys entirely inside your browser, with nothing uploaded and nothing crossing the network — for instance with our RSA key generator. To verify a digest or compare fingerprints use the hash generator, and for relative algorithm strength see the hash and encryption algorithm cheat sheet.
Keys and Algorithms: RSA or ECDSA
| Dimension | RSA 2048 | RSA 3072 | ECDSA P-256 | Ed25519 |
|---|---|---|---|---|
| Equivalent strength | 112 bit | 128 bit | 128 bit | 128 bit |
| Public key size | 256 B | 384 B | 64 B | 32 B |
| Handshake cost | Medium | High | Low | Low |
| Legacy client support | Excellent | Good | Android 4+ / most modern stacks | Poor |
| Recommendation | Compatibility fallback only | Acceptable | Preferred | Experimental |
Bottom line: prefer ECDSA P-256, and pair it with an RSA certificate as a dual setup if you carry compatibility baggage. In Nginx it is two extra lines, and modern clients negotiate ECDSA automatically:
ssl_certificate /etc/letsencrypt/live/oltool.net/fullchain.pem; # ECDSA
ssl_certificate_key /etc/letsencrypt/live/oltool.net/privkey.pem;
ssl_certificate /etc/letsencrypt/live/oltool.net-rsa/fullchain.pem; # RSA fallback
ssl_certificate_key /etc/letsencrypt/live/oltool.net-rsa/privkey.pem;
An Nginx Config You Can Paste
For more context (reverse proxying, static assets, location matching), see the Nginx config cheat sheet.
server {
listen 80;
listen [::]:80;
server_name oltool.net www.oltool.net;
# Keep ACME challenges plain HTTP; redirect everything else
location /.well-known/acme-challenge/ { root /var/www/html; }
location / { return 301 https://$host$request_uri; }
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name oltool.net www.oltool.net;
# fullchain includes the intermediate — never point this at the leaf alone
ssl_certificate /etc/letsencrypt/live/oltool.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/oltool.net/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3; # TLS 1.0/1.1 are fully deprecated
ssl_prefer_server_ciphers off; # Let the client's preference win
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off; # Avoid long-lived ticket key reuse
# OCSP stapling: the server asks the CA on the client's behalf, saving a request
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
# HSTS: force HTTPS for all subsequent visits. Start with a short max-age,
# then raise it to 31536000 once you are confident.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Three traps that are easy to miss:
add_headerdoes not inherit. The moment alocationblock declares anyadd_headerof its own, everyadd_headerfrom the enclosing levels — HSTS included — is discarded wholesale. You must restate them at each level that needs them. Addalwaysso 4xx/5xx responses carry them too.- HSTS is not reversible. Once you send
max-age=31536000, you cannot let users fall back to HTTP for a full year. Confirm every resource on the site works over HTTPS first; deploy withmax-age=300initially. includeSubDomainsis a trap. It applies to every subdomain, so if some internal subdomain lacks HTTPS you will lose access to it the moment this header ships. Add it only once all subdomains are ready.
Troubleshooting Table
| Symptom | Root cause | Diagnostic | Fix |
|---|---|---|---|
| Untrusted in browser; desktop fine, mobile fails | Incomplete chain | openssl s_client -connect host:443 -servername host, inspect chain depth |
Use fullchain.pem |
NET::ERR_CERT_COMMON_NAME_INVALID |
The hostname is not in the SAN | openssl x509 -in cert.pem -noout -text | grep -A1 "Subject Alternative Name" |
Reissue with complete SANs |
ERR_CERT_DATE_INVALID |
Expired, or the local clock is wrong | openssl x509 -in cert.pem -noout -dates |
Renew / fix the clock |
| Padlock shows a warning | Mixed content: an http:// resource on an HTTPS page | Browser console Mixed Content notice; DevTools Network filter | Switch to https or protocol-relative URLs |
| Only some clients fail the handshake | Algorithm or TLS version mismatch | openssl s_client -tls1_2 / curl --tlsv1.2 |
Relax protocols or add an RSA dual certificate |
| Multiple sites share an IP and the wrong cert is served | SNI not honored / no default_server | openssl s_client -connect ip:443 -servername a.com, compare two runs |
Add server_name and a default server block |
| OCSP lookups slow the first connection | The CA's OCSP endpoint is unreachable | openssl s_client -status, read OCSP Response Status |
Enable stapling or change CA |
| Revoked cert still accepted | No stapling, and the client skips live checks | openssl x509 -noout -text, look for CRL/OCSP URIs |
Enable stapling and shorten lifetimes |
Lifecycle and Automation
A certificate setup you can genuinely ignore rests on three things:
- Automated renewal. An ACME client plus a timer that checks daily, renews 30 days before expiry, and reloads:
# systemd timer (better than cron: inspectable last-run status and logs) systemctl enable --now certbot-renew.timer - Expiry monitoring. Renewal fails — DNS APIs change, ports get blocked, rate limits hit — so remaining lifetime must be monitored independently, alerting below 21 days. Automated renewal without monitoring is not automated renewal.
- Private-key discipline. Mode 600, owned by root; never in git (committing is permanent disclosure even if you delete it later); backups encrypted; revoke and reissue immediately on suspicion of compromise.
Pre-Launch Checklist
- [ ] Using
fullchain.pem(with intermediates), not the leaf - [ ] Only TLS 1.2 / 1.3 enabled; TLS 1.0 / 1.1 off
- [ ] SANs cover every hostname users reach (bare domain and www included)
- [ ] Port 80 does a 301 to HTTPS but allows
/.well-known/acme-challenge/ - [ ] No mixed content (every asset is https)
- [ ] HSTS validated starting from a short
max-age - [ ] OCSP stapling on and verifiable (
openssl s_client -status) - [ ] Automated renewal in place, with separate expiry monitoring
- [ ] Private key is mode 600 and not committed to version control