📚 Part of the MTA-STS and TLS-RPT Guide: Enforcing Email Encryption in Transit series.
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.
SSL Checker
Run quick or deep SSL scans to verify certificate chains, protocol support, and common configuration issues
Open the full SSL Checker tool →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