Recommended Apache Config Templates
1. Catch-all vhost (000-catchall.conf)
Must load first — the 000- prefix guarantees this alphabetically.
# Catches any Host/SNI that doesn't match a real vhost — must load FIRST
<VirtualHost *:80>
ServerName catchall.invalid
<Location />
Require all denied
</Location>
ErrorDocument 403 "Not found"
</VirtualHost>
<VirtualHost *:443>
ServerName catchall.invalid
SSLEngine on
SSLStrictSNIVHostCheck On
SSLCertificateFile /etc/ssl/certs/ssl-cert-snakeoil.pem
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
<Location />
Require all denied
</Location>
ErrorDocument 403 "Not found"
</VirtualHost>
sudo a2ensite 000-catchall.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
2. New-domain template (_template.conf) — HTTP only, no cert paths
<VirtualHost *:80>
ServerName __DOMAIN__
ServerAlias www.__DOMAIN__
RedirectMatch 301 ^/(.*)$ https://www.__DOMAIN__/$1
RewriteEngine on
RewriteCond %{SERVER_NAME} =www.__DOMAIN__ [OR]
RewriteCond %{SERVER_NAME} =__DOMAIN__
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
Deliberately has no :443 block — that gets generated by certbot in step
4 of the workflow below, removing cert-path copy/paste as a failure mode.
3. Scaffolding script (/opt/scripts/new-apex-vhost.sh)
#!/bin/bash
set -e
DOMAIN="$1"
if [ -z "$DOMAIN" ]; then
echo "Usage: $0 domain.com"; exit 1
fi
CONF="/etc/apache2/sites-available/${DOMAIN}.conf"
sed "s/__DOMAIN__/${DOMAIN}/g" /etc/apache2/sites-available/_template.conf > "$CONF"
a2ensite "${DOMAIN}.conf"
apache2ctl configtest && systemctl reload apache2
echo "HTTP vhost created and enabled for $DOMAIN."
echo "Next: confirm DNS A record points here, then run:"
echo " sudo certbot --apache -d ${DOMAIN} -d www.${DOMAIN}"
4. Onboarding workflow (see also: Onboarding Checklist)
sudo /opt/scripts/new-apex-vhost.sh <domain>— HTTP-only vhost from the clean template.- Client adds apex A record → this server's static IP.
- Confirm DNS propagation:
dig +short <domain> - Confirm port-80 vhost is matching (not falling to catchall):
curl -s -o /dev/null -w "%{http_code}\n" --resolve <domain>:80:127.0.0.1 http://<domain>/ - Issue cert — certbot writes the
:443block with correct paths:sudo certbot --apache -d <domain> -d www.<domain> - Run the full
--resolveverification loop from Apache Best Practices to confirm the redirect chain end-to-end over HTTPS.
Until step 5, HTTPS requests to the new domain hit 000-catchall and get a
safe 403 — not another client's site.