Skip to main content

SSL/TLS Best Practices 2026

A comprehensive guide to deploying and maintaining secure TLS configurations. Covers TLS 1.3 migration, cipher suite hardening, HSTS, automated certificate management, and production-ready server configs.

✓ TLS 1.2 vs 1.3 comparison ✓ HSTS & preload setup ✓ Let's Encrypt automation ✓ Nginx & Apache configs

TLS 1.2 vs TLS 1.3 Comparison

Feature TLS 1.2 TLS 1.3
Handshake RTT2-RTT1-RTT (0-RTT resumption)
Cipher suite count37+ (many weak)5 (all strong)
Forward SecrecyOptionalMandatory (ECDHE only)
CBC mode ciphersSupported (BEAST/POODLE)Removed
RSA key exchangeSupported (no FS)Removed
SHA-1 / MD5Allowed in some configsRemoved
Encrypted handshakePartialMost fields encrypted
Browser supportUniversal96%+ global (2026)

Nginx: Modern TLS Configuration

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name yourapp.com www.yourapp.com;

    # Certificate paths (Let's Encrypt)
    ssl_certificate     /etc/letsencrypt/live/yourapp.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourapp.com/privkey.pem;

    # Modern TLS protocols (disable 1.0, 1.1)
    ssl_protocols TLSv1.2 TLSv1.3;

    # TLS 1.3 ciphers (auto-selected, best practice to list explicitly)
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256;
    ssl_prefer_server_ciphers off;  # Let client choose (TLS 1.3)

    # Elliptic curves — prefer X25519, P-256
    ssl_ecdh_curve X25519:prime256v1:secp384r1;

    # Session resumption
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;  # Disable for forward secrecy

    # OCSP stapling (reduces handshake latency)
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/yourapp.com/chain.pem;
    resolver 1.1.1.1 8.8.8.8 valid=300s;

    # DH params (generate: openssl dhparam -out /etc/nginx/dhparam.pem 2048)
    ssl_dhparam /etc/nginx/dhparam.pem;

    # Security headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}

# HTTP → HTTPS redirect
server {
    listen 80;
    listen [::]:80;
    server_name yourapp.com www.yourapp.com;
    return 301 https://$host$request_uri;
}

Let's Encrypt: Automated Certificate Management

Initial Setup with Certbot

# Install certbot (Ubuntu/Debian)
sudo apt install certbot python3-certbot-nginx

# Obtain certificate + auto-configure nginx
sudo certbot --nginx -d yourapp.com -d www.yourapp.com

# Test renewal process
sudo certbot renew --dry-run

# Certbot auto-renewal is enabled via systemd timer:
sudo systemctl status certbot.timer

acme.sh Alternative (More Flexible)

# Install acme.sh
curl https://get.acme.sh | sh

# Issue certificate (standalone mode)
acme.sh --issue -d yourapp.com -d www.yourapp.com --standalone

# Install to nginx paths
acme.sh --install-cert -d yourapp.com \
  --cert-file      /etc/nginx/ssl/cert.pem \
  --key-file       /etc/nginx/ssl/key.pem \
  --fullchain-file /etc/nginx/ssl/fullchain.pem \
  --reloadcmd      "systemctl reload nginx"

# Renews automatically via cron (added by acme.sh installer)

Certificate Expiry Monitoring

#!/bin/bash
# check-cert-expiry.sh — run via cron, alert if < 30 days
DOMAIN="yourapp.com"
THRESHOLD=30  # days

EXPIRY=$(echo | openssl s_client -connect ${DOMAIN}:443 -servername ${DOMAIN} 2>/dev/null \
  | openssl x509 -noout -enddate 2>/dev/null \
  | cut -d= -f2)

EXPIRY_EPOCH=$(date -d "${EXPIRY}" +%s)
TODAY_EPOCH=$(date +%s)
DAYS_LEFT=$(( (EXPIRY_EPOCH - TODAY_EPOCH) / 86400 ))

echo "Certificate expires in ${DAYS_LEFT} days"

if [ ${DAYS_LEFT} -lt ${THRESHOLD} ]; then
    # Send alert (webhook, email, PagerDuty, etc.)
    curl -X POST ${SLACK_WEBHOOK} \
      -d "{\"text\":\"⚠️ TLS cert for ${DOMAIN} expires in ${DAYS_LEFT} days\"}"
fi

Apache TLS Configuration

<VirtualHost *:443>
    ServerName yourapp.com
    DocumentRoot /var/www/html

    SSLEngine on
    SSLCertificateFile      /etc/letsencrypt/live/yourapp.com/cert.pem
    SSLCertificateKeyFile   /etc/letsencrypt/live/yourapp.com/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/yourapp.com/chain.pem

    # Modern TLS only
    SSLProtocol -all +TLSv1.2 +TLSv1.3
    SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305
    SSLHonorCipherOrder off  # TLS 1.3 ignores this

    # OCSP Stapling
    SSLUseStapling on
    SSLStaplingCache "shmcb:/var/run/ocsp(128000)"

    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
</VirtualHost>

TLS Security Checklist

Check Requirement Verify With
Protocol versionTLS 1.2+ onlySSL Labs test
Forward secrecyECDHE or DHE key exchangessl-enum-ciphers
HSTS headermax-age ≥ 31536000curl -I yourhost
OCSP staplingEnabled and verifiedopenssl s_client -status
Certificate chainComplete chain, not expiredSSL Labs / certbot
Renewal automationCertbot/acme.sh timer activesystemctl status certbot.timer
HTTP redirect301 from HTTP to HTTPScurl -L http://yourhost

Frequently Asked Questions

What is the difference between TLS 1.2 and TLS 1.3?▼

TLS 1.3 (RFC 8446, 2018) is a major redesign with significant improvements: 1-RTT handshake (vs 2-RTT in TLS 1.2), 0-RTT resumption for repeat connections, removal of all weak cipher suites (RC4, DES, 3DES, MD5, SHA-1, CBC mode), perfect forward secrecy is now mandatory (ephemeral ECDH only), and simplified cipher suite negotiation. TLS 1.3 is 30-40% faster for new connections and should be used exclusively. TLS 1.0 and 1.1 are deprecated by RFC 8996 and disabled in all modern browsers.

What is HSTS and how do I enable it?▼

HTTP Strict Transport Security (HSTS) tells browsers to only connect to your domain over HTTPS, even if the user types http://. Once a browser sees the Strict-Transport-Security header, it will automatically upgrade all future requests to HTTPS for the specified max-age duration, preventing SSL stripping attacks. Set: Strict-Transport-Security: max-age=63072000; includeSubDomains; preload (2 years). To add your domain to the HSTS preload list (hardcoded in browsers), submit at hstspreload.org — this is permanent, so only do it when fully committed to HTTPS.

How often should I rotate TLS certificates?▼

Let's Encrypt certificates expire every 90 days and should be renewed at 60 days (30 days before expiry). Commercial certificates are typically issued for 1 year (maximum since 2020). Best practice: automate renewal with certbot or acme.sh — never rely on manual renewal. Set up monitoring to alert when certificates are within 30 days of expiry. For internal services, use automated certificate management like HashiCorp Vault PKI Secrets Engine or AWS Certificate Manager Private CA.

What cipher suites should I use in 2026?▼

For TLS 1.3, cipher suites are fixed and non-negotiable: TLS_AES_256_GCM_SHA384, TLS_CHACHA20_POLY1305_SHA256, TLS_AES_128_GCM_SHA256. For TLS 1.2 compatibility, use ECDHE+AESGCM+SHA256/384. Disable: CBC mode ciphers (BEAST/POODLE), RC4, 3DES, export ciphers, DH key exchange under 2048-bit. Mozilla's SSL Configuration Generator at ssl-config.mozilla.org is the gold standard reference and is updated as vulnerabilities are discovered.

What is certificate pinning and when should I use it?▼

Certificate pinning associates a specific TLS certificate (or public key hash) with an application, so the app refuses connections if the certificate doesn't match — even if it's signed by a trusted CA. It protects against compromised CAs or MITM with a fraudulently issued certificate. Use in: mobile apps with fixed server connections, high-security financial applications. Avoid for general web browsers (makes cert rotation difficult). Prefer public key pinning over full certificate pinning to allow cert renewal without app update. Google removed Chrome's HPKP support in 2018 due to site lockout risks.

How do I test my TLS configuration?▼

Use SSL Labs (ssllabs.com/ssltest) for a detailed A+ grade report including certificate chain, protocol versions, cipher suites, and known vulnerabilities (BEAST, POODLE, Heartbleed, ROBOT, etc.). Command-line testing: 'openssl s_client -connect yourhost:443 -tls1_3' to verify TLS 1.3 works. 'nmap --script ssl-enum-ciphers -p 443 yourhost' to enumerate all supported ciphers. 'testssl.sh' for comprehensive command-line testing. Run these tests after any server configuration change.

Share this tool: X / Twitter LinkedIn
✓ 60,000+ developers use DevPlaybook ★★★★★ 4.8 / 5 on Gumroad ✓ MIT licensed · Instant download ✓ No login required

Related Products

on Gumroad

You Might Also Need

More Free Tools

Enjoying this tool? Get more like it.

Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.

DevPlaybook Pro 7 days free

Unlock AI-Powered Dev Tools

  • ⚡ AI Code Review, Doc Generator & SQL Builder
  • ⚡ All premium templates & early access
  • ⚡ Member discounts on Gumroad products
Start Free Trial →

$9/mo after trial · Cancel anytime

Want the full toolkit?

Get DevPlaybook Pro

Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.

$79
$241 separately Save $162 (67% off)
Get DevPlaybook Pro — $79 →

MIT licensed · Instant download · No subscription

See what's included → Browse all deals →