Skip to main content

Apache SSL/TLS Configuration: Complete Guide

Apache HTTPD uses the mod_ssl module for HTTPS. This page covers the core directives — SSLEngine, SSLProtocol, SSLCipherSuite — and builds a complete, secure HTTPS environment.


Install and Enable mod_ssl

# Ubuntu/Debian
sudo apt install libapache2-mod-ssl
sudo a2enmod ssl
sudo a2enmod headers # for security headers
sudo a2enmod rewrite # for HTTP→HTTPS redirect
sudo systemctl restart apache2

# CentOS/RHEL (usually pre-installed)
sudo dnf install mod_ssl
sudo systemctl restart httpd

# Verify activation
apache2ctl -M | grep ssl
# ssl_module (shared)

Basic HTTPS VirtualHost

# /etc/apache2/sites-available/example.com-ssl.conf

<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com

DocumentRoot /var/www/html
DirectoryIndex index.html index.php

# Enable SSL
SSLEngine On

# Certificate files
SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem

<Directory /var/www/html>
AllowOverride All
Require all granted
</Directory>

CustomLog /var/log/apache2/example.com-ssl-access.log combined
ErrorLog /var/log/apache2/example.com-ssl-error.log
</VirtualHost>
# Enable site
sudo a2ensite example.com-ssl.conf
sudo systemctl reload apache2

Global TLS Version and Cipher Suite

Global SSL settings are managed in ssl.conf or apache2.conf.

# /etc/apache2/mods-available/ssl.conf or conf-available/ssl-security.conf

# TLS versions: allow 1.2 and 1.3 only
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1

# Cipher suites (TLS 1.2)
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:\
DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384

# TLS 1.3 cipher suites
SSLCipherSuite TLSv1.3 TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256

# Server cipher order (off = client preference; recommended for TLS 1.3)
SSLHonorCipherOrder off

# ECDH curves
SSLOpenSSLConfCmd Curves X25519:prime256v1:secp384r1

# Session cache
SSLSessionCache shmcb:/run/apache2/ssl_scache(512000)
SSLSessionCacheTimeout 300

# OCSP Stapling
SSLUseStapling On
SSLStaplingCache shmcb:/run/apache2/ssl_stapling(32768)
SSLStaplingReturnResponderErrors off

HTTP → HTTPS Redirect

# /etc/apache2/sites-available/example.com.conf

<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com

# Exclude Let's Encrypt challenge path
Alias /.well-known/acme-challenge/ /var/www/html/.well-known/acme-challenge/
<Directory /var/www/html/.well-known/acme-challenge/>
Require all granted
</Directory>

# Redirect everything else to HTTPS
RewriteEngine On
RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge/
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>

Or more simply:

<VirtualHost *:80>
ServerName example.com
Redirect permanent / https://example.com/
</VirtualHost>

HSTS and Security Headers

<VirtualHost *:443>
ServerName example.com
SSLEngine On
# ... certificate config ...

# Security headers (requires mod_headers)
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"

# Hide server info
ServerTokens Prod
ServerSignature Off
</VirtualHost>

Complete Production Apache HTTPS Configuration

# /etc/apache2/sites-available/production-ssl.conf

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

# SSL settings
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem

# TLS version
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1

# Cipher suites
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

# OCSP Stapling
SSLUseStapling On

# Security headers
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self'"

# Reverse proxy to Tomcat
ProxyRequests Off
ProxyPreserveHost On
ProxyPass "/" "http://localhost:8080/"
ProxyPassReverse "/" "http://localhost:8080/"

# Forward HTTPS info to backend
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"

CustomLog /var/log/apache2/ssl-access.log combined
ErrorLog /var/log/apache2/ssl-error.log
</VirtualHost>

Validation

# Check config syntax
sudo apachectl configtest

# Apply changes
sudo systemctl reload apache2

# Verify SSL config
openssl s_client -connect example.com:443 -tls1_3 2>/dev/null | head -20

# Check Apache SSL status
sudo apachectl -D DUMP_RUN_CFG | grep SSL

# Check ports
sudo ss -tlnp | grep -E '80|443'

Multi-Domain HTTPS (SNI)

Run HTTPS for multiple domains on a single server.

# Domain A
<VirtualHost *:443>
ServerName example.com
SSLEngine On
SSLCertificateFile /etc/ssl/example.com/fullchain.pem
SSLCertificateKeyFile /etc/ssl/example.com/privkey.pem
DocumentRoot /var/www/example.com
</VirtualHost>

# Domain B
<VirtualHost *:443>
ServerName another.com
SSLEngine On
SSLCertificateFile /etc/ssl/another.com/fullchain.pem
SSLCertificateKeyFile /etc/ssl/another.com/privkey.pem
DocumentRoot /var/www/another.com
</VirtualHost>

SNI (Server Name Indication) allows multiple certificates on a single port 443. All modern browsers support SNI.

The next page covers HTTPS offloading (SSL Termination) — terminating TLS at the load balancer and forwarding plain HTTP to the backend.