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 gracefulHTTPS 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.timerKey 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 gracefulmod_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