Skip to content

Instantly share code, notes, and snippets.

@amcchord
Created January 11, 2026 20:47
Show Gist options
  • Select an option

  • Save amcchord/38cd18ebfc0f9641541ec3489c8e8654 to your computer and use it in GitHub Desktop.

Select an option

Save amcchord/38cd18ebfc0f9641541ec3489c8e8654 to your computer and use it in GitHub Desktop.
Unified Apache Virtual Host Setup Script with SSL (Let's Encrypt)
#!/bin/bash
# Unified Apache Virtual Host Setup Script
# Usage: ./setupVHost.sh <domain> [subdomain]
#
# Smart detection:
# - domain.com -> sets up domain.com + www.domain.com
# - sub.domain.com -> sets up sub.domain.com only (detected as subdomain)
# - domain.com api -> sets up api.domain.com only
# - domain.com www -> sets up domain.com + www.domain.com
set -e # Exit on error
# Check if script is run as root
if [[ $EUID -ne 0 ]]; then
echo "This script must be run as root"
exit 1
fi
# Check if domain name is provided
if [ -z "$1" ]; then
echo "Please provide a domain name as an argument."
echo "Usage: $0 <domain> [subdomain]"
echo ""
echo "Examples:"
echo " $0 example.com # Sets up example.com + www.example.com"
echo " $0 example.com www # Same as above"
echo " $0 example.com api # Sets up api.example.com only"
echo " $0 sub.example.com # Sets up sub.example.com only (auto-detected)"
exit 1
fi
INPUT_DOMAIN=$1
SUBDOMAIN=${2:-""}
# Count dots in domain to detect if it's already a subdomain
DOT_COUNT=$(echo "$INPUT_DOMAIN" | tr -cd '.' | wc -c)
# Function to check if a valid SSL certificate exists for given domains
check_ssl_exists() {
local domains_to_check="$1"
if ! command -v certbot &> /dev/null; then
return 1
fi
# Get list of certificates
local cert_output
cert_output=$(certbot certificates 2>/dev/null)
# Check if all required domains are covered by existing certificates
local all_found=true
for domain in $domains_to_check; do
if ! echo "$cert_output" | grep -q "$domain"; then
all_found=false
break
fi
done
if [ "$all_found" = true ]; then
return 0
fi
return 1
}
# Function to ensure certbot is installed
ensure_certbot() {
if ! command -v certbot &> /dev/null; then
echo "Installing certbot..."
apt update
apt install -y certbot python3-certbot-apache
fi
}
# Function to create web root directory
create_web_root() {
local web_root="$1"
local display_name="$2"
if [ ! -d "$web_root" ]; then
echo "Creating web root directory: $web_root"
mkdir -p "$web_root"
chown -R www-data:www-data "$web_root"
chmod -R 755 "$web_root"
echo "<h1>Welcome to $display_name</h1>" > "$web_root/index.html"
chown www-data:www-data "$web_root/index.html"
else
echo "Web root already exists: $web_root"
fi
}
# Function to create Apache config and enable site
create_apache_config() {
local conf_file="$1"
local conf_content="$2"
local site_name="$3"
if [ ! -f "$conf_file" ]; then
echo "Creating Apache configuration: $conf_file"
echo "$conf_content" > "$conf_file"
a2ensite "$site_name"
echo "Restarting Apache..."
systemctl restart apache2
else
echo "Apache configuration already exists: $conf_file"
fi
}
# Function to clean up on failure
cleanup_on_failure() {
local conf_file="$1"
local site_name="$2"
local web_root="$3"
echo ""
echo "ERROR: Setup failed. Cleaning up..."
# Disable and remove site config if it exists
if [ -f "$conf_file" ]; then
echo "Removing Apache configuration: $conf_file"
a2dissite "$site_name" 2>/dev/null || true
rm -f "$conf_file"
fi
# Remove web root if it's empty (only has our default index.html)
if [ -d "$web_root" ]; then
local file_count
file_count=$(find "$web_root" -type f | wc -l)
if [ "$file_count" -le 1 ]; then
echo "Removing web root: $web_root"
rm -rf "$web_root"
else
echo "Web root has user content, leaving it: $web_root"
fi
fi
# Restart Apache to clean state
echo "Restarting Apache..."
systemctl restart apache2
echo "Cleanup complete."
}
# Function to run certbot with error handling
run_certbot() {
local certbot_args="$1"
local email="$2"
local conf_file="$3"
local site_name="$4"
local web_root="$5"
echo "Setting up SSL certificate..."
# Run certbot and capture exit code
set +e # Temporarily disable exit on error
certbot --apache $certbot_args --non-interactive --agree-tos --email "$email" --redirect
local certbot_exit=$?
set -e # Re-enable exit on error
if [ $certbot_exit -ne 0 ]; then
echo ""
echo "Certbot failed with exit code: $certbot_exit"
cleanup_on_failure "$conf_file" "$site_name" "$web_root"
exit 1
fi
}
# Determine setup mode based on domain structure and arguments
# If domain has 2+ dots (e.g., sub.domain.com), treat as standalone subdomain
# If domain has 1 dot (e.g., domain.com), check for subdomain argument
if [ "$DOT_COUNT" -ge 2 ]; then
# Input is already a subdomain (e.g., casana.mcchord.net)
# Set up as standalone, no www
SETUP_MODE="subdomain"
TARGET_DOMAIN="$INPUT_DOMAIN"
# Extract base domain for email (last two parts)
BASE_DOMAIN=$(echo "$INPUT_DOMAIN" | rev | cut -d'.' -f1-2 | rev)
elif [ -z "$SUBDOMAIN" ] || [ "$SUBDOMAIN" = "www" ]; then
# Standard domain with www
SETUP_MODE="www"
TARGET_DOMAIN="$INPUT_DOMAIN"
BASE_DOMAIN="$INPUT_DOMAIN"
else
# Explicit subdomain argument
SETUP_MODE="subdomain"
TARGET_DOMAIN="$SUBDOMAIN.$INPUT_DOMAIN"
BASE_DOMAIN="$INPUT_DOMAIN"
fi
# Execute setup based on mode
if [ "$SETUP_MODE" = "www" ]; then
# WWW + Bare Domain Setup
WEB_ROOT="/var/www/$TARGET_DOMAIN"
WWW_DOMAIN="www.$TARGET_DOMAIN"
CONF_FILE="/etc/apache2/sites-available/$TARGET_DOMAIN.conf"
SITE_NAME="$TARGET_DOMAIN.conf"
CERTBOT_DOMAINS="-d $TARGET_DOMAIN -d $WWW_DOMAIN"
echo "Setting up $TARGET_DOMAIN and $WWW_DOMAIN..."
# Create web root
create_web_root "$WEB_ROOT" "$TARGET_DOMAIN"
# Create Apache virtual host configuration
CONF_CONTENT="<VirtualHost *:80>
ServerName $TARGET_DOMAIN
ServerAlias $WWW_DOMAIN
DocumentRoot $WEB_ROOT
ErrorLog \${APACHE_LOG_DIR}/$TARGET_DOMAIN-error.log
CustomLog \${APACHE_LOG_DIR}/$TARGET_DOMAIN-access.log combined
</VirtualHost>"
create_apache_config "$CONF_FILE" "$CONF_CONTENT" "$SITE_NAME"
# Ensure certbot is installed
ensure_certbot
# Check if SSL certificate already exists
if check_ssl_exists "$TARGET_DOMAIN $WWW_DOMAIN"; then
echo "Valid SSL certificate already exists for $TARGET_DOMAIN and $WWW_DOMAIN. Skipping certbot."
else
run_certbot "$CERTBOT_DOMAINS" "webmaster@$BASE_DOMAIN" "$CONF_FILE" "$SITE_NAME" "$WEB_ROOT"
fi
echo ""
echo "Setup completed successfully for $TARGET_DOMAIN and $WWW_DOMAIN"
echo "Web root: $WEB_ROOT"
else
# Subdomain Setup (standalone, no www)
WEB_ROOT="/var/www/$TARGET_DOMAIN"
CONF_FILE="/etc/apache2/sites-available/$TARGET_DOMAIN.conf"
SITE_NAME="$TARGET_DOMAIN.conf"
CERTBOT_DOMAINS="-d $TARGET_DOMAIN"
echo "Setting up $TARGET_DOMAIN (standalone, no www)..."
# Create web root
create_web_root "$WEB_ROOT" "$TARGET_DOMAIN"
# Create Apache virtual host configuration
CONF_CONTENT="<VirtualHost *:80>
ServerName $TARGET_DOMAIN
DocumentRoot $WEB_ROOT
ErrorLog \${APACHE_LOG_DIR}/$TARGET_DOMAIN-error.log
CustomLog \${APACHE_LOG_DIR}/$TARGET_DOMAIN-access.log combined
</VirtualHost>"
create_apache_config "$CONF_FILE" "$CONF_CONTENT" "$SITE_NAME"
# Ensure certbot is installed
ensure_certbot
# Check if SSL certificate already exists
if check_ssl_exists "$TARGET_DOMAIN"; then
echo "Valid SSL certificate already exists for $TARGET_DOMAIN. Skipping certbot."
else
run_certbot "$CERTBOT_DOMAINS" "webmaster@$BASE_DOMAIN" "$CONF_FILE" "$SITE_NAME" "$WEB_ROOT"
fi
echo ""
echo "Setup completed successfully for $TARGET_DOMAIN"
echo "Web root: $WEB_ROOT"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment