Apache SSL Configuration: Modern Settings for a Secure Setup

Apache’s SSL configuration has improved a lot over the years. Getting it right involves enabling the right modules, configuring ciphers, setting up HSTS, and making sure the certificate chain is complete. Here’s the configuration I use.

Required Modules

Make sure these modules are enabled:

a2enmod ssl
a2enmod rewrite
a2enmod headers    # Required for security headers including HSTS
a2enmod http2      # Optional but recommended
systemctl restart apache2

Verify:

apache2ctl -M | grep -E "ssl|rewrite|headers|http2"

Virtual Host Configuration

A complete HTTPS virtual host:

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

    # Certificate files
    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

    # Modern TLS protocols
    SSLProtocol -all +TLSv1.2 +TLSv1.3

    # 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:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384

    SSLHonorCipherOrder off
    SSLSessionTickets off

    # OCSP Stapling
    SSLUseStapling on
    SSLStaplingResponderTimeout 5
    SSLStaplingReturnResponderErrors off

    # Security headers
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
    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"

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

SSLProtocol -all +TLSv1.2 +TLSv1.3 — disables all protocols first, then enables TLS 1.2 and 1.3 explicitly. Cleaner than listing what to disable.

SSLHonorCipherOrder off — let the client choose the cipher. Same reasoning as Nginx’s ssl_prefer_server_ciphers off.

SSLSessionTickets off — disable session tickets for better forward secrecy. A small performance cost.

HTTP Redirect

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

    RewriteEngine On
    RewriteRule ^(.*)$ https://example.com$1 [R=301,L]
</VirtualHost>

Or simpler with Redirect:

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

I prefer the Redirect approach for simple redirects — RewriteRule is more powerful but the extra complexity isn’t needed for a basic HTTP→HTTPS redirect.

OCSP Stapling Configuration

OCSP stapling needs to be configured at the server level (not just in the virtual host):

In /etc/apache2/mods-enabled/ssl.conf or in your apache2.conf:

SSLUseStapling on
SSLStaplingCache "shmcb:${APACHE_RUN_DIR}/ssl_stapling(512000)"

Restart Apache and verify:

echo | openssl s_client -connect example.com:443 -servername example.com -status 2>/dev/null | grep "OCSP response"

If you see OCSP Response Status: successful (0x0), stapling is working.

DH Parameters

Generate if you haven’t:

openssl dhparam -out /etc/ssl/certs/dhparam.pem 2048

Configure in Apache:

SSLOpenSSLConfCmd DHParameters "/etc/ssl/certs/dhparam.pem"

HTTP/2 Configuration

Protocols h2 http/1.1

<VirtualHost *:443>
    # HTTP/2 is negotiated via ALPN during TLS handshake
    # Nothing else needed if Protocols is set globally
</VirtualHost>

Set Protocols h2 http/1.1 globally or per virtual host. Apache handles the HTTP/2 negotiation during TLS.

Check HTTP/2 is working:

curl -I --http2 https://example.com | grep "HTTP/2"

SSL Session Cache

# In ssl.conf or httpd.conf
SSLSessionCache "shmcb:/var/run/apache2/ssl_scache(512000)"
SSLSessionCacheTimeout 300

The shared memory cache stores SSL session data, reducing handshake overhead for returning clients.

Virtual Host for Multiple Domains on One IP (SNI)

Apache uses SNI (Server Name Indication) to serve multiple SSL sites from one IP:

# First site
<VirtualHost *:443>
    ServerName site1.example.com
    SSLCertificateFile /etc/ssl/site1.crt
    SSLCertificateKeyFile /etc/ssl/site1.key
    SSLCertificateChainFile /etc/ssl/site1-chain.pem
    # ...
</VirtualHost>

# Second site
<VirtualHost *:443>
    ServerName site2.example.com
    SSLCertificateFile /etc/ssl/site2.crt
    SSLCertificateKeyFile /etc/ssl/site2.key
    SSLCertificateChainFile /etc/ssl/site2-chain.pem
    # ...
</VirtualHost>

Each virtual host has its own certificate. SNI is supported by all modern browsers and clients.

Configuration Testing

Always test before restarting:

apachectl configtest

If it says “Syntax OK”, safe to restart:

systemctl restart apache2

For config changes that don’t require a full restart (like security headers), use graceful restart:

apachectl graceful

Graceful restart lets existing connections finish before applying the new config — no dropped connections.

Common Issues

Certificate chain incomplete — if you get SSLCertificateChainFile errors or browsers complain about the chain, verify the chain file contains the intermediate certificates. For Let’s Encrypt, use chain.pem, not cert.pem.

Protocol mismatch — if clients report TLS errors, check SSLProtocol includes what they support. Very old clients need TLS 1.2 minimum.

HSTS cached in browser — if you misconfigure HSTS (wrong max-age, or set it on an HTTP host by mistake), it caches in the browser. Clear HSTS in browser settings (usually under security/HSTS settings in chrome://net-internals/#hsts).

OpenSSL version matters — some cipher suites and TLS features require a specific minimum OpenSSL version. openssl version and apache2 -v show what you have. If they’re outdated (Ubuntu 18.04 ships older OpenSSL), some modern cipher configurations won’t work.

For generating and managing the certificates themselves, the Let’s Encrypt with Certbot guide covers the full certificate workflow, and the SSL certificate formats guide explains the PEM, chain, and combined certificate files Apache uses.

Scroll to Top