← Back to Blog

The Complete HTTPS Certificate Guide: From TLS Handshake to Automated Renewal

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_share goes 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.
  • CertificateVerify is 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:

  1. 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.
  2. 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 at http://<domain>/.well-known/acme-challenge/. Simple, but requires port 80 to be reachable and cannot issue wildcard certificates.
  • DNS-01: you add a _acme-challenge TXT 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:

  1. add_header does not inherit. The moment a location block declares any add_header of its own, every add_header from the enclosing levels — HSTS included — is discarded wholesale. You must restate them at each level that needs them. Add always so 4xx/5xx responses carry them too.
  2. 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 with max-age=300 initially.
  3. includeSubDomains is 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:

  1. 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
    
  2. 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.
  3. 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
Advertisement

Frequently Asked Questions

Is there any difference in encryption strength between free and paid certificates?

No. Both use identical algorithms and key lengths, the browser padlock looks the same, and an observer cannot tell from the traffic whether your certificate was free or paid. The real differences are: validation level (free CAs usually offer only DV; OV/EV are paid), validity period (free certificates are typically 90 days, paid ones can run a year), warranty coverage, and availability of wildcard or IP certificates. For the vast majority of sites, a free DV certificate provides all the encryption strength you need.

The browser says the certificate is untrusted, but it has not expired. What is the most likely cause?

The most common cause is an **incomplete certificate chain**: the server sends only the leaf certificate and omits the intermediate CA certificate. Browsers ship root certificates locally and cannot derive a missing intermediate out of thin air, so they report the chain as untrusted. The fix is to deploy the intermediate and leaf certificates concatenated into one file (the fullchain). Beware that this failure is deceptive: many desktop browsers will fetch the missing intermediate via the AIA extension or a cache and display the site fine, while mobile browsers, curl, and API clients fail outright — so never validate with a browser alone.

Should I choose an RSA or an ECDSA certificate?

Prefer **ECDSA (P-256)** unless you have a concrete legacy-compatibility burden. P-256 delivers security roughly equivalent to RSA 3072 with a far shorter key, a faster handshake, and lower CPU and bandwidth cost — a clear win for mobile clients and high-traffic sites. The tradeoff is that very old clients (pre-Android 4.x, Windows XP, some Java 6/7 runtimes) lack ECDSA support. The safe approach is dual certificates: configure ECDSA and RSA side by side so modern clients negotiate ECDSA and legacy clients fall back to RSA automatically. Nginx 1.11+ supports this natively.

How often should certificates be renewed? Is a 90-day lifetime too short?

Ninety days is the direction the industry is moving, not a flaw — the CA/Browser Forum has already voted to phase the maximum lifetime down to 47 days. Short lifetimes limit the exposure window after a private-key leak and force automation. The right response is not to buy a longer certificate but to automate renewal completely: use an ACME client (certbot or acme.sh) driven by a systemd timer or cron that checks daily and renews 30 days before expiry, then reloads the service. Add expiry monitoring that alerts below 21 days remaining and humans never have to think about it again.

← Back to Blog