How do I configure TLS properly?

Direct answer

TLS 1.2 as a minimum with 1.3 preferred, modern cipher suites only, automated certificate renewal, and HSTS once you're confident. The most damaging TLS bug isn't the configuration though, it's application code with certificate verification disabled, which turns encryption into decoration.

Muhammad HasanUpdated

The configuration

Disable TLS 1.0 and 1.1. Support 1.2 and 1.3.

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;

Rather than assembling a cipher list by hand, use Mozilla's SSL Configuration Generator. Pick the "intermediate" profile unless you have a specific reason not to, and re-generate occasionally as recommendations change.

Prefer ECDSA certificates where your clients support them; they're smaller and faster than RSA at equivalent strength.

Automate renewal

Expired certificates cause more outages than weak ciphers cause breaches.

Let's Encrypt with certbot or an ACME client, or your platform's managed certificates. Ninety-day certificates are the norm now and manual renewal doesn't survive contact with a busy quarter.

Monitor expiry independently of the renewal process. Automation fails silently more often than it fails loudly.

Redirect and then commit

Redirect HTTP to HTTPS, then add HSTS:

Strict-Transport-Security: max-age=31536000; includeSubDomains

HSTS is sticky. A browser that's seen it will refuse to connect over HTTP for the duration, and a broken certificate becomes an outage rather than a warning. Start with a short max-age, confirm everything on the domain and its subdomains works over HTTPS, then raise it.

preload is a further commitment and hard to reverse. Worth it, but not on day one.

The bug that undoes it all

const agent = new https.Agent({ rejectUnauthorized: false });

# Python
requests.get(url, verify=False)

# curl
curl -k https://internal.service

Each of these disables certificate verification. Encryption still happens; authentication doesn't. Anyone positioned between the two ends can present their own certificate and read everything.

This gets added to work around a self-signed certificate in development and ships. It's one of the higher-impact one-line bugs there is, and it's easy to find, grep for rejectUnauthorized, verify=False, InsecureRequestWarning, --insecure and -k.

For genuinely self-signed internal certificates, add the CA to the trust store rather than disabling verification.

Internal traffic

Traffic between your own services is often plaintext on the assumption the network is trusted. That assumption is what lets one compromised service read everything.

mTLS between services if your platform supports it. Service meshes make this considerably less painful than it used to be.

Checking

SSL Labs will grade a public endpoint and explain what's weak. testssl.sh does the same for internal hosts. Both belong in a periodic check rather than a one-off, since a proxy or load balancer change can quietly alter the configuration.

Go deeper

Kolega for DevOps

Related answers

See what your own repository returns

Connect a repo and run a scan. No credit card, no pipeline changes.

Get started for free