Apache
06 / 07

Virtual Hosts, Modules & SSL/TLS

Apache: Virtual Hosts, Modules & SSL/TLS

Name-Based Virtual Hosts

Virtual hosts let one server handle multiple domains. Apache uses the Host header to route requests to the correct VirtualHost block.

# /etc/apache2/sites-available/myapp.conf

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

    DocumentRoot /var/www/myapp
    ErrorLog ${APACHE_LOG_DIR}/myapp-error.log
    CustomLog ${APACHE_LOG_DIR}/myapp-access.log combined

    <Directory /var/www/myapp>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    # Redirect all HTTP to HTTPS
    RewriteEngine On
    RewriteCond %{HTTPS} off
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>
# Enable site, disable default
sudo a2ensite myapp.conf
sudo a2dissite 000-default.conf
sudo apachectl graceful

HTTPS Virtual Host with SSL

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

    DocumentRoot /var/www/myapp

    SSLEngine on
    SSLCertificateFile      /etc/letsencrypt/live/myapp.com/fullchain.pem
    SSLCertificateKeyFile   /etc/letsencrypt/live/myapp.com/privkey.pem

    # Modern TLS config (Mozilla Intermediate)
    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
    SSLHonorCipherOrder     off
    SSLSessionTickets       off

    # HSTS
    Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"

    # Security headers
    Header always set X-Content-Type-Options "nosniff"
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set Referrer-Policy "strict-origin-when-cross-origin"
</VirtualHost>

Let's Encrypt with Certbot

# Install Certbot
sudo apt install certbot python3-certbot-apache

# Obtain and auto-configure SSL
sudo certbot --apache -d myapp.com -d www.myapp.com

# Standalone mode (if Apache is not running)
sudo certbot certonly --standalone -d myapp.com

# Test renewal
sudo certbot renew --dry-run

# Certbot creates systemd timer for auto-renewal
systemctl status certbot.timer

Key Modules

# Enable / disable modules
sudo a2enmod rewrite     # URL rewriting (mod_rewrite)
sudo a2enmod ssl         # HTTPS
sudo a2enmod headers     # Response headers (mod_headers)
sudo a2enmod deflate     # Gzip compression
sudo a2enmod expires     # Cache-Control headers
sudo a2enmod proxy       # Reverse proxy
sudo a2enmod proxy_http  # HTTP proxy
sudo a2dismod autoindex  # Disable directory listing
sudo apachectl graceful

mod_rewrite — URL Rewriting

<VirtualHost *:443>
    RewriteEngine On

    # HTTPS redirect
    RewriteCond %{HTTPS} off
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]

    # www → non-www redirect
    RewriteCond %{HTTP_HOST} ^www.(.+)$ [NC]
    RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]

    # SPA — serve index.html for all non-file routes
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^ /index.html [L]

    # Old URL → new URL (301 permanent)
    RewriteRule ^/old-blog/(.*)$ /blog/$1 [R=301,L]

    # Block access to hidden files (.git, .env)
    RewriteRule (?:^|/). - [F,L]
</VirtualHost>

Reverse Proxy

# Proxy to Node.js app on port 3000
<VirtualHost *:443>
    ServerName api.myapp.com

    SSLEngine on
    SSLCertificateFile /etc/letsencrypt/live/api.myapp.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/api.myapp.com/privkey.pem

    ProxyPreserveHost On
    ProxyPass / http://localhost:3000/
    ProxyPassReverse / http://localhost:3000/

    # Forward real client IP
    RequestHeader set X-Forwarded-Proto "https"
    RequestHeader set X-Forwarded-For %{REMOTE_ADDR}s
</VirtualHost>

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free