7 min read
On this page

TLS & Certificates

TLS (Transport Layer Security) encrypts data in transit between clients and servers. TLS 1.3 is the current standard, adopted by all major browsers and most server software. As a developer, you do not need to understand the mathematics behind Diffie-Hellman or elliptic curves. You need to understand how to configure TLS correctly, manage certificates, and avoid the operational mistakes that lead to downtime and security incidents.

TLS 1.3

TLS 1.3, finalized in 2018 (RFC 8446), is a significant improvement over TLS 1.2. It removed insecure cipher suites, simplified the handshake, and improved performance.

What Changed from TLS 1.2

TLS 1.2                              TLS 1.3
──────────────────────────────────────────────────────
2-RTT handshake                      1-RTT handshake
Supports RSA key exchange            Forward secrecy only (ECDHE)
Many cipher suites (some weak)       5 cipher suites (all strong)
Vulnerable to downgrade attacks      Encrypted handshake
Optional 0-RTT                       Supports 0-RTT resumption

TLS 1.3 mandates forward secrecy. Even if a server's private key is compromised in the future, past recorded traffic cannot be decrypted. TLS 1.2 allowed RSA key exchange, which does not provide forward secrecy.

Disable Older Versions

TLS 1.0 and 1.1 are deprecated. PCI DSS has required their removal since 2018. TLS 1.2 is still acceptable but TLS 1.3 should be preferred.

# Nginx configuration
ssl_protocols TLSv1.2 TLSv1.3;

# Apache configuration
SSLProtocol -all +TLSv1.2 +TLSv1.3

0-RTT Resumption

TLS 1.3 supports 0-RTT (zero round-trip time) resumption, where the client sends encrypted data in the first message of a resumed connection. This improves latency but introduces a replay risk -- an attacker can capture and resend the 0-RTT data. Only use 0-RTT for idempotent requests (GET, not POST).

Certificate Authorities & Certificate Chains

A TLS certificate binds a domain name to a public key. Certificate Authorities (CAs) verify domain ownership and issue certificates. Browsers and operating systems maintain a list of trusted root CAs.

The Certificate Chain

Root CA (trusted, pre-installed in OS/browser)
  └── Intermediate CA (signed by Root CA)
        └── Server Certificate (signed by Intermediate CA)

The server sends its certificate and the intermediate certificate during the TLS handshake. The client verifies the chain up to a trusted root. If any link is missing or invalid, the connection fails.

A common misconfiguration is not including the intermediate certificate in the server configuration. The server certificate alone cannot be verified. Some browsers cache intermediate certificates, so it works in Chrome but fails in curl or mobile apps.

# Check certificate chain
openssl s_client -connect example.com:443 -showcerts

# Verify chain is complete
curl -vI https://example.com 2>&1 | grep "SSL certificate"

Certificate Fields That Matter

Subject:           CN=example.com (the domain)
Issuer:            CN=Let's Encrypt Authority X3
Validity:          Not Before / Not After (expiration)
Subject Alt Names: example.com, www.example.com, *.example.com
Key Usage:         Digital Signature, Key Encipherment

Subject Alternative Names (SANs) list all domains a certificate covers. Wildcard certificates (*.example.com) cover one level of subdomain but not the apex domain (example.com) unless explicitly listed.

Let's Encrypt

Let's Encrypt provides free, automated certificates using the ACME protocol. It issues Domain Validated (DV) certificates, which verify you control the domain but do not verify organizational identity. For most applications, DV certificates are sufficient.

# Certbot: the standard ACME client
# Install and obtain a certificate
sudo certbot --nginx -d example.com -d www.example.com

# Auto-renewal (certbot installs a cron job or systemd timer)
sudo certbot renew --dry-run

Let's Encrypt certificates expire every 90 days, by design. Short-lived certificates reduce the window of exposure if a key is compromised. Automate renewal and never manage Let's Encrypt certificates manually.

ACME Challenges

Challenge Type       How It Works
─────────────────────────────────────────────────────
HTTP-01              Place a file at /.well-known/acme-challenge/
DNS-01               Create a TXT record in DNS
TLS-ALPN-01          Respond on port 443 with a special certificate

DNS-01 is the only challenge that supports wildcard certificates. It is also the only option when your server is not publicly accessible on ports 80 or 443.

Certificate Pinning

Certificate pinning restricts which certificates a client accepts, beyond the normal CA verification. The client is configured to trust only specific certificates or public keys for a domain.

Why It Is Usually More Trouble Than It Is Worth

Certificate pinning seems like a strong security measure, but it creates significant operational problems:

  • Certificate rotation becomes dangerous. If you pin a certificate and it expires or is revoked, your app stops connecting. Users must update the app to recover.
  • CA migration breaks everything. If your CA changes its intermediate certificate, pinned clients reject the new chain.
  • Incident response is harder. If your key is compromised and you need to rotate immediately, pinned clients cannot connect until they receive an update.

Google removed certificate pinning from Chrome in 2019 (Chrome 72). The HPKP (HTTP Public Key Pinning) standard was deprecated because it caused more outages than it prevented attacks. Apple and Google have removed pinning-related APIs or discouraged their use on mobile platforms.

Use Certificate Transparency logs and CAA DNS records instead. These achieve similar goals without the operational risk.

# CAA DNS record: restrict which CAs can issue certificates for your domain
example.com.  IN  CAA  0  issue  "letsencrypt.org"
example.com.  IN  CAA  0  iodef "mailto:security@example.com"

HSTS: HTTP Strict Transport Security

HSTS tells browsers to always use HTTPS for your domain. Once a browser receives an HSTS header, it will refuse to connect over HTTP for the specified duration.

# HSTS header
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age=31536000: Enforce HTTPS for one year (in seconds).
  • includeSubDomains: Apply to all subdomains. Be careful -- every subdomain must support HTTPS before enabling this.
  • preload: Submit your domain to the HSTS preload list (hstspreload.org). Browsers ship with this list, so HTTPS is enforced from the very first visit.

Deploying HSTS Safely

Start with a short max-age and no includeSubDomains. Verify everything works. Gradually increase the duration.

# Step 1: Test with short duration
Strict-Transport-Security: max-age=300

# Step 2: Extend after confirming no issues
Strict-Transport-Security: max-age=86400

# Step 3: Add subdomains when ready
Strict-Transport-Security: max-age=31536000; includeSubDomains

# Step 4: Preload when confident
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

HSTS preload is difficult to undo. Once your domain is in the preload list, removing it takes months and requires waiting for browser updates to propagate. Make sure every subdomain, including internal tools and staging environments, supports HTTPS before preloading.

Mixed Content

Mixed content occurs when an HTTPS page loads resources (scripts, images, stylesheets, iframes) over HTTP. This undermines the security of the entire page.

# Active mixed content (blocked by browsers)
<script src="http://cdn.example.com/app.js"></script>
<iframe src="http://ads.example.com/banner"></iframe>

# Passive mixed content (may show warning)
<img src="http://images.example.com/logo.png" />

Modern browsers block active mixed content (scripts, stylesheets, iframes) entirely. Passive mixed content (images, video, audio) may load with a warning but Chrome has moved toward blocking these as well.

Fixing Mixed Content

  • Use protocol-relative URLs (//cdn.example.com/file.js) or, better, explicit HTTPS (https://cdn.example.com/file.js).
  • Add the upgrade-insecure-requests CSP directive to automatically upgrade HTTP requests to HTTPS.
  • Audit third-party resources. If a third party does not support HTTPS, find an alternative.
# CSP header to upgrade insecure requests
Content-Security-Policy: upgrade-insecure-requests

Real-World TLS Failures

Equifax (2017)

Equifax failed to renew an internal SSL inspection certificate for 19 months. The expired certificate meant their intrusion detection system could not inspect encrypted traffic, so the massive data exfiltration of 147 million records went undetected for 76 days. Automated certificate monitoring would have flagged the expiration immediately.

DigiNotar (2011)

A Dutch Certificate Authority was compromised, and attackers issued fraudulent certificates for google.com, microsoft.com, and other major domains. These certificates were used to intercept Iranian users' Gmail traffic. DigiNotar was removed from all browser trust stores and went bankrupt within months. This incident accelerated the development of Certificate Transparency.

Heartbleed Aftermath (2014)

After the Heartbleed vulnerability was disclosed, every affected server needed to revoke and reissue its certificate because private keys may have been exposed. The scale of revocation overwhelmed the CRL (Certificate Revocation List) and OCSP infrastructure. Many organizations did not rotate their certificates at all. This exposed the fragility of the certificate revocation ecosystem.

Common Pitfalls

  • Not including intermediate certificates. The server must send the full chain (server cert + intermediate). Missing intermediates cause failures in some clients but not others, making debugging difficult.
  • Letting certificates expire. Automate renewal. Monitor expiration dates. Services like Certbot handle renewal automatically, but only if the renewal process is not broken by infrastructure changes.
  • Using self-signed certificates in production. Self-signed certs provide encryption but no identity verification. They train users to ignore certificate warnings, which is far more dangerous than the problem they solve.
  • Not setting HSTS. Without HSTS, the first request to your site may be over HTTP, allowing a man-in-the-middle to intercept it before the redirect to HTTPS.
  • Forgetting about certificate transparency. Monitor CT logs for unauthorized certificates issued for your domains. Tools like crt.sh provide free monitoring.
  • Ignoring CAA records. Without CAA records, any CA can issue a certificate for your domain. Set CAA records to restrict issuance to your chosen CA.
  • Pinning certificates without a rotation plan. If you must pin (you probably should not), pin the public key of your CA's intermediate certificate, not your leaf certificate. Always include a backup pin.

Key Takeaways

  • TLS 1.3 is the current standard. Disable TLS 1.0 and 1.1. Prefer TLS 1.3 over 1.2.
  • Use Let's Encrypt for free, automated certificates. Automate renewal and never manage certificates manually.
  • Always send the full certificate chain (server certificate plus intermediates) from the server.
  • Enable HSTS to force HTTPS. Start with a short max-age and gradually increase it. Be careful with preloading -- it is hard to undo.
  • Certificate pinning is generally not worth the operational risk. Use Certificate Transparency and CAA records instead.
  • Mixed content (loading HTTP resources on HTTPS pages) undermines security. Use upgrade-insecure-requests as a safety net.
  • Monitor certificate expiration and CT logs. Expired or fraudulent certificates cause real outages and breaches.