A properly hardened TLS configuration protects against protocol downgrade attacks, weak cipher exploitation, and man-in-the-middle attacks. This guide covers protocol selection, cipher suites, and security headers for production deployments.
TLS Configuration Overview
┌─────────────────────────────────────────────────────────────────────────┐
│ TLS SECURITY LAYERS │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ Layer 4: Security Headers │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ HSTS │ CSP │ X-Frame-Options │ Referrer-Policy │ Permissions │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ Layer 3: Certificate Configuration │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ ECDSA/RSA │ Key Size │ Chain │ OCSP Stapling │ CT Compliance │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ Layer 2: Cipher Suites │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Key Exchange │ Authentication │ Encryption │ MAC/Hash │ │
│ │ ECDHE │ ECDSA/RSA │ AES-GCM │ SHA-384 │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ Layer 1: Protocol Version │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ TLS 1.3 (preferred) │ TLS 1.2 (fallback) │ ❌ TLS 1.1/1.0/SSL │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Protocol Version Selection
| Protocol | Status | Support |
|---|---|---|
| TLS 1.3 | ✅ Recommended | Required for A+ grade |
| TLS 1.2 | ✅ Acceptable | Compatibility fallback |
| TLS 1.1 | ❌ Deprecated | Disabled by browsers |
| TLS 1.0 | ❌ Deprecated | PCI DSS prohibited |
| SSL 3.0 | ❌ Insecure | POODLE vulnerable |
| SSL 2.0 | ❌ Insecure | Multiple vulnerabilities |
Cipher Suite Selection
TLS 1.3 Cipher Suites (All Secure)
TLS_AES_256_GCM_SHA384 # Strongest, slightly slower
TLS_CHACHA20_POLY1305_SHA256 # Fastest on mobile (no AES-NI)
TLS_AES_128_GCM_SHA256 # Good balance of security/performance
TLS 1.2 Cipher Suites (Curated)
# ECDSA Certificate Ciphers (preferred)
ECDHE-ECDSA-AES256-GCM-SHA384
ECDHE-ECDSA-CHACHA20-POLY1305
ECDHE-ECDSA-AES128-GCM-SHA256
# RSA Certificate Ciphers (fallback)
ECDHE-RSA-AES256-GCM-SHA384
ECDHE-RSA-CHACHA20-POLY1305
ECDHE-RSA-AES128-GCM-SHA256
Cipher Suite Components
┌─────────────────────────────────────────────────────────────────────────┐
│ ECDHE - ECDSA - AES256 - GCM - SHA384 │
│ │ │ │ │ │ │
│ │ │ │ │ └─ Hash: SHA-384 │
│ │ │ │ └──────── Mode: Galois/Counter │
│ │ │ └──────────────── Cipher: AES 256-bit │
│ │ └──────────────────────── Auth: ECDSA signature │
│ └──────────────────────────────── Key Exchange: Ephemeral EC│
│ │
│ SECURE CHOICES: │
│ Key Exchange: ECDHE (forward secrecy) │
│ Authentication: ECDSA (fast), RSA (compatible) │
│ Encryption: AES-GCM, ChaCha20-Poly1305 (AEAD modes) │
│ Hash: SHA-256, SHA-384 (SHA-2 family) │
│ │
│ AVOID: │
│ Key Exchange: RSA (no forward secrecy), DHE (slow) │
│ Encryption: CBC mode (BEAST, POODLE), 3DES (weak), RC4 (broken) │
│ Hash: MD5 (broken), SHA-1 (deprecated) │
└─────────────────────────────────────────────────────────────────────────┘
Nginx Configuration
Modern Configuration (A+ Grade)
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com;
# Certificates
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# Protocol versions - TLS 1.2 and 1.3 only
ssl_protocols TLSv1.2 TLSv1.3;
# Cipher suites - TLS 1.3 ciphers are configured automatically
# TLS 1.2 ciphers ordered by preference
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers on;
# ECDH curve
ssl_ecdh_curve X25519:secp384r1;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/nginx/ssl/fullchain.pem;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# Session configuration
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off; # Disable for better forward secrecy
# Security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;
# ... rest of configuration
}
# Redirect HTTP to HTTPS
server {
listen 80;
listen [::]:80;
server_name example.com;
return 301 https://$server_name$request_uri;
}
Dual Certificate Configuration (ECDSA + RSA)
server {
listen 443 ssl http2;
server_name example.com;
# ECDSA certificate (preferred)
ssl_certificate /etc/nginx/ssl/ecdsa-fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/ecdsa-privkey.pem;
# RSA certificate (fallback for older clients)
ssl_certificate /etc/nginx/ssl/rsa-fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/rsa-privkey.pem;
# Nginx automatically selects based on client capability
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305';
ssl_prefer_server_ciphers on;
}
Apache Configuration
Modern Configuration (Apache 2.4+)
# Global SSL configuration
SSLProtocol -all +TLSv1.2 +TLSv1.3
SSLCipherSuite TLSv1.3 TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
SSLCipherSuite ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
SSLHonorCipherOrder on
SSLCompression off
SSLSessionTickets off
# OCSP Stapling
SSLStaplingCache shmcb:/var/run/apache2/ocsp(128000)
SSLUseStapling On
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors Off
<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile /etc/apache2/ssl/server.crt
SSLCertificateKeyFile /etc/apache2/ssl/server.key
SSLCertificateChainFile /etc/apache2/ssl/chain.crt
# Security headers
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
# ... rest of configuration
</VirtualHost>
# Redirect HTTP to HTTPS
<VirtualHost *:80>
ServerName example.com
Redirect permanent / https://example.com/
</VirtualHost>
HAProxy Configuration
global
# SSL/TLS defaults
ssl-default-bind-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
ssl-default-bind-ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256
ssl-default-bind-options ssl-min-ver TLSv1.2 no-tls-tickets
ssl-default-server-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
ssl-default-server-options ssl-min-ver TLSv1.2 no-tls-tickets
tune.ssl.default-dh-param 2048
frontend https
bind *:443 ssl crt /etc/haproxy/certs/combined.pem alpn h2,http/1.1
# HSTS header
http-response set-header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# Security headers
http-response set-header X-Content-Type-Options "nosniff"
http-response set-header X-Frame-Options "DENY"
http-response set-header Referrer-Policy "strict-origin-when-cross-origin"
default_backend webservers
frontend http
bind *:80
redirect scheme https code 301
backend webservers
server web1 10.0.0.1:8080 check
Security Headers Deep Dive
HSTS (HTTP Strict Transport Security)
# Start with short max-age for testing
add_header Strict-Transport-Security "max-age=300" always;
# After testing, increase to 1 year with subdomains
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# For HSTS preload list submission (permanent)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
HSTS Preload Requirements:
- Valid HTTPS certificate
- Redirect HTTP to HTTPS
- All subdomains served over HTTPS
- HSTS header with max-age >= 1 year, includeSubDomains, preload
- Submit at hstspreload.org
Content Security Policy
# Basic CSP (adjust for your application)
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.example.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://api.example.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self';" always;
# Report-only mode for testing
add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report" always;
Complete Header Set
# Essential security headers
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# Privacy and feature control
add_header Permissions-Policy "accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=()" always;
# Legacy XSS protection (mostly obsolete with CSP)
add_header X-XSS-Protection "1; mode=block" always;
# Content Security Policy (customize for your app)
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; frame-ancestors 'none';" always;
Testing Your Configuration
SSL Labs Test
# Online test (comprehensive, public)
# Visit: https://www.ssllabs.com/ssltest/analyze.html?d=example.com
# Expected grade: A+ with proper configuration
testssl.sh (Command Line)
# Install
git clone --depth 1 https://github.com/drwetter/testssl.sh.git
cd testssl.sh
# Basic test
./testssl.sh example.com
# Full test with all checks
./testssl.sh --full example.com
# Check specific vulnerabilities
./testssl.sh --vulnerable example.com
# Check cipher suites only
./testssl.sh --cipher-per-proto example.com
OpenSSL Manual Tests
# Test TLS 1.3 support
openssl s_client -connect example.com:443 -tls1_3
# Test TLS 1.2 support
openssl s_client -connect example.com:443 -tls1_2
# Verify TLS 1.1 is disabled (should fail)
openssl s_client -connect example.com:443 -tls1_1
# Expected: "no protocols available" or handshake failure
# List supported cipher suites
openssl s_client -connect example.com:443 -cipher 'ALL' 2>/dev/null | grep -E "Cipher|Protocol"
# Check certificate chain
openssl s_client -connect example.com:443 -showcerts </dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates
# Test OCSP stapling
openssl s_client -connect example.com:443 -status </dev/null 2>/dev/null | grep -A 20 "OCSP Response"
Mozilla Observatory
# Online test for security headers
# Visit: https://observatory.mozilla.org/
# Or use CLI
pip install httpobs-cli
httpobs example.com
Security Headers Test
# Quick header check
curl -I https://example.com 2>/dev/null | grep -iE "strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy"
Common Vulnerabilities to Check
| Vulnerability | Risk | Mitigation |
|---|---|---|
| BEAST | Medium | Use TLS 1.2+ with AEAD ciphers |
| POODLE | High | Disable SSL 3.0 and TLS 1.0 |
| FREAK | High | Disable export ciphers |
| Logjam | Medium | Use 2048-bit DH params or ECDHE |
| ROBOT | High | Disable RSA key exchange |
| Heartbleed | Critical | Update OpenSSL, check with testssl.sh |
| DROWN | High | Disable SSL 2.0 on all servers |
| CRIME/BREACH | Medium | Disable TLS compression |
| Lucky13 | Low | Use AEAD ciphers (GCM, ChaCha20) |
Performance Optimization
Session Resumption
# TLS 1.2 session caching
ssl_session_cache shared:SSL:50m;
ssl_session_timeout 1d;
# Disable session tickets for better forward secrecy
# (trade-off: slightly more CPU usage)
ssl_session_tickets off;
# Or enable with key rotation for performance
# ssl_session_tickets on;
# ssl_session_ticket_key /etc/nginx/ssl/ticket.key;
# Rotate key every 24-48 hours
TLS 1.3 0-RTT (Use Carefully)
# Enable 0-RTT for improved latency (TLS 1.3)
# WARNING: Vulnerable to replay attacks
ssl_early_data on;
# In application, check for replays
proxy_set_header Early-Data $ssl_early_data;
# Application must reject sensitive requests with Early-Data: 1
ECDSA for Performance
# Generate ECDSA key (faster than RSA)
openssl ecparam -genkey -name prime256v1 -out ecdsa-key.pem
# Generate CSR
openssl req -new -key ecdsa-key.pem -out ecdsa.csr
# ECDSA handshakes are ~3x faster than RSA 2048
Automation and Monitoring
Configuration Checker Script
#!/bin/bash
# tls-check.sh - Quick TLS configuration verification
DOMAIN=${1:-"example.com"}
echo "=== TLS Configuration Check for $DOMAIN ==="
# Check TLS versions
echo -e "\n[Protocol Support]"
for proto in tls1 tls1_1 tls1_2 tls1_3; do
result=$(echo | timeout 5 openssl s_client -connect $DOMAIN:443 -$proto 2>&1)
if echo "$result" | grep -q "Protocol.*TLSv"; then
version=$(echo "$result" | grep "Protocol" | head -1)
echo " $proto: ENABLED ($version)"
else
echo " $proto: disabled"
fi
done
# Check certificate
echo -e "\n[Certificate]"
cert_info=$(echo | openssl s_client -connect $DOMAIN:443 2>/dev/null | openssl x509 -noout -subject -dates -issuer)
echo "$cert_info" | sed 's/^/ /'
# Check OCSP stapling
echo -e "\n[OCSP Stapling]"
ocsp=$(echo | openssl s_client -connect $DOMAIN:443 -status 2>/dev/null | grep -A 1 "OCSP Response Status")
if [ -n "$ocsp" ]; then
echo " Enabled"
echo "$ocsp" | sed 's/^/ /'
else
echo " Not enabled or no response"
fi
# Check security headers
echo -e "\n[Security Headers]"
headers=$(curl -sI https://$DOMAIN | grep -iE "strict-transport|x-frame|x-content-type|referrer-policy")
if [ -n "$headers" ]; then
echo "$headers" | sed 's/^/ /'
else
echo " No security headers found"
fi
echo -e "\n=== Run SSL Labs test for comprehensive analysis ==="
echo "https://www.ssllabs.com/ssltest/analyze.html?d=$DOMAIN"
Best Practices Summary
- Use TLS 1.3 + TLS 1.2 only - Disable all older versions
- ECDHE key exchange - Ensures forward secrecy
- AEAD ciphers only - AES-GCM or ChaCha20-Poly1305
- Enable HSTS - With preload after testing
- Enable OCSP stapling - Improves performance and privacy
- Set security headers - CSP, X-Frame-Options, etc.
- Use ECDSA certificates - Faster than RSA
- Test regularly - SSL Labs, testssl.sh, Mozilla Observatory
- Monitor for vulnerabilities - Subscribe to security advisories
- Automate certificate renewal - Let's Encrypt/ACME
Next Steps
- OCSP Stapling Implementation - Performance optimization
- Certificate Pinning - Mobile app protection
- mTLS Authentication - Client certificate auth
- TLS Certificate Complete Guide - Comprehensive certificate management