[ GF.dev ] All Tools →

SSL/TLS Configuration Guide: Securing Your Web Server from Scratch

Published 2026-03-29 · Last modified 2026-03-29

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.

TLS Fundamentals: What Happens During a Handshake

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.

The TLS 1.2 Handshake (Two Round Trips)

In TLS 1.2, the handshake requires two full round trips between client and server:

  1. ClientHello — The client sends its supported TLS versions, cipher suites, and a random value.
  2. ServerHello + Certificate — The server selects a cipher suite, sends its certificate chain, and its own random value.
  3. Key Exchange — Depending on the cipher suite, the client and server exchange Diffie-Hellman parameters or RSA-encrypted pre-master secrets.
  4. Finished — Both sides derive session keys and confirm with Finished messages.

This two-round-trip process adds measurable latency, especially on high-latency connections. It is one of the primary motivations for TLS 1.3.

The TLS 1.3 Handshake (One Round Trip)

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.

TLS 1.2 vs TLS 1.3: Configuration Comparison

The table below summarizes the key differences:

FeatureTLS 1.2TLS 1.3
Handshake Round Trips21 (0-RTT optional)
Cipher Suites Available~40+ (many insecure)5 (all AEAD)
Forward SecrecyOptional (requires ECDHE/DHE)Mandatory
RSA Key ExchangeAllowedRemoved
CBC Mode CiphersAllowedRemoved
RenegotiationSupportedRemoved
Browser SupportUniversalAll 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.

Certificate Types: DV, OV, EV, and Wildcard

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.

Domain Validated (DV)

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.

Organization Validated (OV)

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.

Extended Validation (EV)

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 and Multi-Domain (SAN) Certificates

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.

Cipher Suite Hardening

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.

Recommended TLS 1.3 Cipher Suites

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_SHA256

The first three are universally supported and performant. The CCM variants are niche and mainly relevant for IoT devices.

Recommended TLS 1.2 Cipher Suites

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 on

Run 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.

Migrating to HTTPS Without Breaking Your Site

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.

Step 1: Obtain and Install a Certificate

Use Let's Encrypt with Certbot for automated issuance and renewal:

sudo certbot --nginx -d example.com -d www.example.com

Certbot will modify your Nginx configuration, add the certificate paths, and set up a systemd timer for automatic renewal.

Step 2: Redirect HTTP to HTTPS

# 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]

Step 3: Fix Mixed Content

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.

Step 4: Enable HSTS

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?

HTTP/2 and HTTP/3: Protocols Built on TLS

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.

Enabling HTTP/2

# Nginx
listen 443 ssl http2;

# Apache
Protocols h2 http/1.1

Verify with the GF.dev HTTP/2 Test.

Enabling HTTP/3

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.

HPKP: Deprecated and Replaced

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.

Testing Your Configuration

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:

Use 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).

OCSP Stapling

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)

Automating Certificate Renewal

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-run

If 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.

Putting It All Together: A Hardened Nginx TLS Block

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.