Transport Layer Security (TLS) is the cryptographic protocol that underpins every HTTPS connection on the internet. Whether you are running a personal blog or a large-scale API, getting your TLS configuration right is essential for protecting user data in transit, satisfying compliance requirements, and maintaining trust with browsers and search engines.
This guide walks you through TLS fundamentals, certificate selection, cipher suite hardening, HTTPS migration, and the modern protocols (HTTP/2, HTTP/3) that depend on a solid TLS foundation. Every recommendation includes concrete Nginx and Apache configuration examples you can apply immediately, plus links to the free TLS Scanner and related tools on GF.dev so you can verify each change.
If you are new to transport security, start here and work through the cluster articles linked in each section. If you already have HTTPS deployed, jump to the cipher suite hardening or SSL Labs scoring sections to tighten your existing setup.
When a browser connects to your server over HTTPS, a TLS handshake takes place before any application data is exchanged. Understanding this handshake is the key to understanding every configuration choice that follows.
In TLS 1.2, the handshake requires two full round trips between client and server:
This two-round-trip process adds measurable latency, especially on high-latency connections. It is one of the primary motivations for TLS 1.3.
TLS 1.3 collapses the handshake to a single round trip. The client sends its key share in the very first message, and the server responds with its own key share and the encrypted certificate in the same flight. The result is roughly half the handshake latency of TLS 1.2, plus a simpler state machine that eliminates entire classes of vulnerabilities (such as downgrade attacks and renegotiation exploits).
TLS 1.3 also supports 0-RTT (Zero Round Trip Time) resumption, where returning clients can send encrypted application data in their very first message. This is powerful but must be used carefully because 0-RTT data is susceptible to replay attacks. Most servers should leave 0-RTT disabled unless the application layer is explicitly designed to handle replayed requests idempotently.
The table below summarizes the key differences:
| Feature | TLS 1.2 | TLS 1.3 |
|---|---|---|
| Handshake Round Trips | 2 | 1 (0-RTT optional) |
| Cipher Suites Available | ~40+ (many insecure) | 5 (all AEAD) |
| Forward Secrecy | Optional (requires ECDHE/DHE) | Mandatory |
| RSA Key Exchange | Allowed | Removed |
| CBC Mode Ciphers | Allowed | Removed |
| Renegotiation | Supported | Removed |
| Browser Support | Universal | All modern browsers since 2018+ |
For a deep dive into which cipher suites to keep and which to remove, see TLS Cipher Suites Ranked: Which to Enable, Which to Disable.
All TLS certificates perform the same cryptographic function — binding a public key to a domain name. The differences lie in the level of identity validation the Certificate Authority (CA) performs before issuing the certificate.
DV certificates verify only that the requester controls the domain, typically through a DNS TXT record or an HTTP challenge file. They are issued in seconds, cost nothing (via Let's Encrypt or ZeroSSL), and are perfectly adequate for the vast majority of websites. From a cryptographic standpoint, a DV certificate provides identical encryption strength to an EV certificate.
OV certificates require the CA to verify the legal identity of the organization behind the domain. The organization name appears in the certificate's Subject field, but modern browsers no longer display it prominently. OV certificates typically cost $50–$200/year and are common in enterprise procurement workflows.
EV certificates require the most rigorous verification, including legal existence, physical address, and operational status. Historically, browsers displayed a green address bar with the company name for EV certificates. As of 2019, all major browsers have removed this UI distinction, making EV certificates visually indistinguishable from DV/OV. Unless a compliance framework mandates EV, there is little practical reason to purchase one.
Wildcard certificates cover *.example.com (one level of subdomains). Multi-domain SAN certificates list multiple distinct hostnames in the Subject Alternative Name extension. Let's Encrypt supports both types for free, making them accessible to everyone.
A cipher suite defines the algorithms used for key exchange, authentication, bulk encryption, and message authentication. Choosing the right set of cipher suites is one of the most impactful changes you can make to your TLS configuration.
TLS 1.3 only allows five cipher suites, all of which are considered secure. You rarely need to restrict them further:
# TLS 1.3 cipher suites (OpenSSL names)
TLS_AES_256_GCM_SHA384
TLS_CHACHA20_POLY1305_SHA256
TLS_AES_128_GCM_SHA256
TLS_AES_128_CCM_SHA256
TLS_AES_128_CCM_8_SHA256The first three are universally supported and performant. The CCM variants are niche and mainly relevant for IoT devices.
For TLS 1.2, you need to be far more selective. Disable all cipher suites that lack forward secrecy (RSA key exchange) or use weak algorithms (RC4, 3DES, CBC-mode without encrypt-then-MAC):
# Nginx — strong TLS 1.2 cipher suite list
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:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;# Apache — equivalent configuration
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
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 onRun these configurations through the GF.dev TLS Scanner to verify that only strong suites are negotiated. For a full ranking and explanation of each suite, read TLS Cipher Suites Ranked.
Moving an existing HTTP site to HTTPS involves more than installing a certificate. The most common pitfalls are mixed content, redirect chains, and hardcoded HTTP URLs in databases or templates.
Use Let's Encrypt with Certbot for automated issuance and renewal:
sudo certbot --nginx -d example.com -d www.example.comCertbot will modify your Nginx configuration, add the certificate paths, and set up a systemd timer for automatic renewal.
# Nginx
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$host$request_uri;
}# Apache (.htaccess)
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]Mixed content occurs when an HTTPS page loads sub-resources (images, scripts, stylesheets) over plain HTTP. Browsers block mixed active content (scripts, iframes) entirely and may warn on mixed passive content (images). Use the GF.dev Mixed Content Test to scan your pages, then update URLs to HTTPS or use protocol-relative paths. For a complete walkthrough, see How to Find and Fix Mixed Content on Your Website.
Once everything works over HTTPS, add the Strict-Transport-Security header to prevent any future HTTP connections. Start with a short max-age and increase it after confirming stability:
# Nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;For a full explanation of HSTS, preloading, and common pitfalls, see What is HSTS and How Does It Protect Your Site?
Modern HTTP protocols are inseparable from TLS. HTTP/2 requires TLS 1.2+ (in practice, all browsers require it). HTTP/3 replaces TCP+TLS with QUIC, which integrates TLS 1.3 directly into the transport layer, eliminating a round trip and solving head-of-line blocking at the transport level.
# Nginx
listen 443 ssl http2;
# Apache
Protocols h2 http/1.1Verify with the GF.dev HTTP/2 Test.
HTTP/3 support requires a QUIC-capable server (Nginx 1.25+, Caddy, LiteSpeed, or a CDN like Cloudflare). You also need to advertise HTTP/3 via the Alt-Svc header:
# Nginx 1.25+
listen 443 quic reuseport;
listen 443 ssl;
http2 on;
add_header Alt-Svc 'h3=":443"; ma=86400';Verify with the GF.dev HTTP/3 Test. For a full comparison, read HTTP/2 vs HTTP/3: Performance, Security, and How to Enable Both.
HTTP Public Key Pinning (HPKP) was a security header that let sites pin specific certificate public keys. It was removed from all browsers by 2020 because misconfiguration could permanently lock users out of a site. The replacement strategies include Certificate Transparency (CT) monitoring, CAA DNS records, and Expect-CT (itself now also deprecated as CT is universally enforced). Check your legacy headers with the GF.dev HPKP Test and read HPKP is Dead — What Replaced It and Why for the full story.
No TLS configuration is complete without testing. The most widely referenced benchmark is the Qualys SSL Labs Server Test, which grades your setup from A+ to F.
Common issues that prevent an A+ grade:
max-ageUse the GF.dev TLS Scanner for a quick check and the Secure Headers Test to verify HSTS and other security headers. For a step-by-step walkthrough, see How to Get an A+ on SSL Labs (Step-by-Step).
Online Certificate Status Protocol (OCSP) stapling lets your server attach a signed, timestamped OCSP response to the TLS handshake, so the client does not need to contact the CA separately to check if your certificate has been revoked. This improves handshake speed and user privacy.
# Nginx
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;# Apache
SSLUseStapling on
SSLStaplingCache shmcb:/tmp/stapling_cache(128000)Let's Encrypt certificates expire every 90 days. Certbot's systemd timer or cron job handles renewal automatically, but you should verify it is running:
sudo systemctl status certbot.timer
# or test renewal
sudo certbot renew --dry-runIf you use a reverse proxy or load balancer, ensure the renewal process can complete the HTTP-01 or DNS-01 challenge. For complex setups, DNS-01 challenges with a provider API (Cloudflare, Route53) are the most reliable.
Here is a complete, production-ready Nginx TLS configuration that scores A+ on SSL Labs:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example.com www.example.com;
# Certificate
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Protocols and ciphers
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:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
# Session settings
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
# Security headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
# ... your location blocks
}After applying this configuration, reload Nginx and verify with the TLS Scanner and HSTS Test tools on GF.dev.