Installing and Configuring SSL in Apache: Virtual Hosts, Modules, and Best Practices

Apache’s approach to SSL is fundamentally different from Nginx. Where Nginx has SSL baked in, Apache uses a module system — mod_ssl — that needs to be explicitly enabled. The configuration syntax is also more verbose. That’s not necessarily bad; Apache’s granular configuration options can be very useful. But it means more places where things can go wrong.

Enabling mod_ssl on Apache

On Ubuntu/Debian:

sudo a2enmod ssl
sudo a2enmod headers
sudo systemctl restart apache2

On RHEL/CentOS/AlmaLinux, mod_ssl is usually installed as a separate package:

sudo dnf install mod_ssl
sudo systemctl restart httpd

The headers module is needed for HSTS and other security headers later. It’s a common oversight to enable mod_ssl but forget headers, then wonder why HSTS isn’t working.

Basic SSL Virtual Host Configuration

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

    SSLEngine on
    SSLCertificateFile /etc/apache2/ssl/certificate.crt
    SSLCertificateKeyFile /etc/apache2/ssl/private.key
    SSLCertificateChainFile /etc/apache2/ssl/ca-bundle.crt

    <Directory /var/www/example.com>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

Notice that Apache uses three separate directives for the certificate components: SSLCertificateFile for your domain cert, SSLCertificateKeyFile for the private key, and SSLCertificateChainFile for the CA chain. This is different from Nginx, which wants them combined into a single fullchain file.

In Apache 2.4.8+, you can also use a combined chain file format (domain cert + intermediates in one file) directly in SSLCertificateFile and skip SSLCertificateChainFile entirely. But specifying them separately is clearer and works across all Apache versions.

HTTP to HTTPS Redirect

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

    RewriteEngine on
    RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>

You’ll need mod_rewrite for this: sudo a2enmod rewrite.

Alternatively, the simpler approach using a redirect directive:

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

The Redirect approach is simpler but strips the request path — http://example.com/page becomes https://example.com/ and not https://example.com/page. The RewriteRule version preserves the full path, which is almost always what you want.

TLS Protocol Versions and Cipher Suites

Apache configures protocols and ciphers in ssl.conf (usually at /etc/apache2/mods-enabled/ssl.conf or /etc/httpd/conf.d/ssl.conf):

SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
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
SSLCompression off
SSLSessionTickets off

The SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1 syntax starts with “all” (enable everything) and then subtracts the old protocols. The result is TLS 1.2 and 1.3 only, which is what you want.

SSLCompression off prevents CRIME attack. SSLHonorCipherOrder off with TLS 1.3 lets the client pick the preferred cipher — similar reasoning to Nginx’s ssl_prefer_server_ciphers off.

OCSP Stapling in Apache

SSLUseStapling on
SSLStaplingResponderTimeout 5
SSLStaplingReturnResponderErrors off
SSLStaplingCache shmcb:/var/run/ocsp(128000)

These directives go in the global server config (outside any VirtualHost), not inside the virtual host block. The SSLStaplingCache location may need to be adjusted based on your system — /var/run/ocsp needs to be writable by the Apache process.

HSTS and Security Headers

<VirtualHost *:443>
    # ... SSL config above ...

    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
    Header always set X-Frame-Options "DENY"
    Header always set X-Content-Type-Options "nosniff"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
</VirtualHost>

The always keyword ensures these headers are sent on all responses, including error responses. Without it, headers might not appear on 4xx or 5xx responses, which is a subtle but exploitable gap.

Performance: SSL Session Cache

SSLSessionCache shmcb:/var/run/ssl_scache(512000)
SSLSessionCacheTimeout 300

This creates a 512KB shared memory cache for TLS sessions. The 300 second timeout is conservative — you can increase it to 86400 (24 hours) if you want. Returning visitors will benefit from session resumption, avoiding a full TLS handshake on subsequent requests.

Virtual Host Order and the Default SSL Config

Apache ships with a default SSL virtual host in 000-default.conf or default-ssl.conf. If this default is enabled and you add your own SSL virtual host, Apache will respond with the default site’s certificate for any SNI request that doesn’t match your virtual host exactly.

Check what’s enabled:

ls -la /etc/apache2/sites-enabled/

Disable the default SSL site if you’re setting up your own:

sudo a2dissite default-ssl

I’ve seen this catch people out when they set up a new virtual host correctly but still get a certificate error in the browser — the default site is intercepting requests because it was loaded first.

Managing Multiple SSL Sites on Apache

When you have multiple HTTPS virtual hosts, they each need their own SSLCertificateFile and SSLCertificateKeyFile. SNI (Server Name Indication) handles routing different certificates to different domains on the same IP address.

# /etc/apache2/sites-available/site1.conf
<VirtualHost *:443>
    ServerName site1.com
    SSLCertificateFile /etc/apache2/ssl/site1.crt
    SSLCertificateKeyFile /etc/apache2/ssl/site1.key
    # ...
</VirtualHost>

# /etc/apache2/sites-available/site2.conf
<VirtualHost *:443>
    ServerName site2.com
    SSLCertificateFile /etc/apache2/ssl/site2.crt
    SSLCertificateKeyFile /etc/apache2/ssl/site2.key
    # ...
</VirtualHost>

Each site has its own configuration file. Enable both:

sudo a2ensite site1
sudo a2ensite site2
sudo apachectl configtest
sudo systemctl reload apache2

SNI is supported in all modern browsers. The only scenarios where it causes problems are very old clients (IE 6 on Windows XP, some ancient Android versions) — which you shouldn’t be worrying about in 2024.

Testing and Debugging Apache SSL

Configuration test before applying changes:

apachectl configtest

If you get an error, the output usually tells you exactly which file and line is problematic. Read it carefully before Googling.

View the active SSL configuration for a virtual host:

apachectl -D DUMP_VHOSTS

Test the actual TLS connection:

openssl s_client -connect example.com:443 -servername example.com

The -servername flag is important — without it, you’re testing without SNI, which might return a different certificate than what the browser sees.

Check which certificate Apache is actually serving:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject -dates

Apache vs Nginx: Which Is Better for SSL?

Neither, really. Apache’s configuration is more verbose but offers per-directory and per-location SSL settings that Nginx doesn’t support. Nginx handles high-concurrency SSL workloads more efficiently. For most sites that don’t need per-directory SSL config, Nginx performs marginally better. But if you’re already running Apache and it’s working, there’s no compelling reason to switch just for SSL.

The configuration differences are largely syntactic. The underlying SSL behavior — cipher negotiation, certificate verification, OCSP stapling — works the same way regardless of which server you’re using.

Scroll to Top