Set up Apache HTTP server with multiple virtual hosts, secure SSL certificates from Let's Encrypt, and implement security hardening for production workloads.
Prerequisites
- Root or sudo access
- Domain names pointed to your server
- Basic command line knowledge
What this solves
Apache web server is one of the most widely used HTTP servers, powering millions of websites worldwide. This tutorial shows you how to configure Apache with multiple virtual hosts to serve different domains from a single server, secure them with Let's Encrypt SSL certificates, and implement security hardening measures for production environments.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest versions of all packages.
sudo apt update && sudo apt upgrade -y
Install Apache web server
Install the Apache HTTP server package along with essential SSL modules for HTTPS support.
sudo apt install -y apache2 apache2-utils ssl-cert
Enable and start Apache service
Enable Apache to start automatically on boot and start the service immediately.
sudo systemctl enable --now apache2
sudo systemctl status apache2
Configure firewall rules
Open HTTP (port 80) and HTTPS (port 443) in your firewall to allow web traffic.
sudo ufw allow 'Apache Full'
sudo ufw status
Configure virtual hosts
Create directory structure for websites
Create document root directories for each domain you want to host. This example uses example.com and blog.example.com.
sudo mkdir -p /var/www/example.com/html
sudo mkdir -p /var/www/blog.example.com/html
sudo mkdir -p /var/www/example.com/logs
sudo mkdir -p /var/www/blog.example.com/logs
Set correct ownership and permissions
Set proper ownership for web directories so Apache can read the files. Never use chmod 777 as it creates security vulnerabilities.
sudo chown -R www-data:www-data /var/www/
sudo chmod -R 755 /var/www/
Create sample HTML files
Create simple test pages for each domain to verify virtual host configuration.
<!DOCTYPE html>
<html>
<head>
<title>Welcome to example.com</title>
</head>
<body>
<h1>Success! example.com virtual host is working</h1>
<p>This is the main website.</p>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Welcome to blog.example.com</title>
</head>
<body>
<h1>Success! blog.example.com virtual host is working</h1>
<p>This is the blog subdomain.</p>
</body>
</html>
Create virtual host configuration files
Create Apache virtual host configuration files for each domain. These define how Apache handles requests for different domains.
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/html
ErrorLog /var/www/example.com/logs/error.log
CustomLog /var/www/example.com/logs/access.log combined
<Directory /var/www/example.com/html>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
<VirtualHost *:80>
ServerName blog.example.com
DocumentRoot /var/www/blog.example.com/html
ErrorLog /var/www/blog.example.com/logs/error.log
CustomLog /var/www/blog.example.com/logs/access.log combined
<Directory /var/www/blog.example.com/html>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Enable virtual hosts
Enable the virtual host configurations and reload Apache to apply changes.
sudo a2ensite example.com.conf
sudo a2ensite blog.example.com.conf
sudo a2dissite 000-default.conf
sudo systemctl reload apache2
Configure SSL certificates with Let's Encrypt
Install Certbot
Install Certbot, the official Let's Encrypt client, to automatically obtain and manage SSL certificates.
sudo apt install -y certbot python3-certbot-apache
Obtain SSL certificates
Use Certbot to automatically obtain SSL certificates for your domains. This will also automatically configure Apache SSL virtual hosts.
sudo certbot --apache -d example.com -d www.example.com
sudo certbot --apache -d blog.example.com
Test automatic renewal
Let's Encrypt certificates expire after 90 days. Test the automatic renewal process to ensure your certificates stay valid.
sudo certbot renew --dry-run
Set up automatic renewal cron job
Create a cron job to automatically renew certificates before they expire.
sudo crontab -e
Add this line to run renewal checks twice daily:
0 12 * /usr/bin/certbot renew --quiet
Security hardening and performance optimization
Enable essential Apache modules
Enable security and performance modules for better protection and speed.
sudo a2enmod ssl
sudo a2enmod headers
sudo a2enmod rewrite
sudo a2enmod deflate
sudo a2enmod expires
Configure security headers
Add security headers to protect against common web vulnerabilities. Create a security configuration file.
# Security Headers
Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options DENY
Header always set X-XSS-Protection "1; mode=block"
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
Hide Apache version
ServerTokens Prod
ServerSignature Off
sudo a2enconf security-headers
Configure compression and caching
Enable gzip compression and browser caching to improve website performance.
# Enable compression
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript application/javascript application/json
</IfModule>
Browser caching
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType image/png "access plus 1 year"
ExpiresByType image/jpg "access plus 1 year"
ExpiresByType image/jpeg "access plus 1 year"
ExpiresByType image/gif "access plus 1 year"
ExpiresByType image/svg+xml "access plus 1 year"
</IfModule>
sudo a2enconf performance
Configure rate limiting
Install and configure mod_evasive to protect against DDoS attacks and excessive requests.
sudo apt install -y libapache2-mod-evasive
sudo a2enmod evasive
<IfModule mod_evasive24.c>
DOSHashTableSize 10000
DOSPageCount 2
DOSSiteCount 50
DOSPageInterval 1
DOSSiteInterval 1
DOSBlockingPeriod 3600
DOSLogDir "/var/log/apache2"
DOSEmailNotify admin@example.com
</IfModule>
Restart Apache to apply all changes
Restart Apache to activate all security and performance configurations.
sudo systemctl restart apache2
Verify your setup
Test your Apache configuration and SSL certificates to ensure everything is working correctly.
# Check Apache configuration syntax
sudo apache2ctl configtest
Check service status
sudo systemctl status apache2
Test HTTP to HTTPS redirect
curl -I http://example.com
Test SSL certificate
ssl-labs-ssl-test --verbosity=info example.com
Check virtual host configuration
sudo apache2ctl -S
You can also test in your browser by visiting your domains. HTTP requests should automatically redirect to HTTPS, and you should see valid SSL certificates.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| 403 Forbidden error | Incorrect file permissions or ownership | Run sudo chown -R www-data:www-data /var/www/ and sudo chmod -R 755 /var/www/ |
| Virtual host shows default page | Virtual host not enabled or DNS not configured | Run sudo a2ensite sitename.conf and check DNS settings |
| SSL certificate errors | Domain not pointing to server or firewall blocking port 80 | Verify DNS and ensure port 80 is open for Let's Encrypt validation |
| Apache won't start | Configuration syntax error | Run sudo apache2ctl configtest to identify errors |
| Performance issues | Default Apache configuration not optimized | Tune MaxRequestWorkers and enable caching modules |
Next steps
- Setup nginx reverse proxy with SSL certificates and security hardening - Compare Apache with Nginx reverse proxy setup
- Configure automatic security updates with unattended-upgrades and email notifications - Keep your server secure with automated updates
- Configure Apache reverse proxy and load balancing for high availability - Scale your Apache setup with load balancing
- Implement Apache Web Application Firewall with ModSecurity - Add advanced security protection
- Configure Apache HTTP/2 and performance optimization - Further optimize your Apache server
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Default values
DOMAIN1="${1:-example.com}"
DOMAIN2="${2:-blog.example.com}"
EMAIL="${3:-admin@${DOMAIN1}}"
# Usage function
usage() {
echo "Usage: $0 [domain1] [domain2] [email]"
echo "Example: $0 mysite.com blog.mysite.com admin@mysite.com"
exit 1
}
# Cleanup function for rollback
cleanup() {
echo -e "${RED}[ERROR]${NC} Installation failed. Cleaning up..."
systemctl stop ${SERVICE_NAME} 2>/dev/null || true
rm -f ${VHOST_DIR}/${DOMAIN1}.conf 2>/dev/null || true
rm -f ${VHOST_DIR}/${DOMAIN2}.conf 2>/dev/null || true
rm -rf /var/www/${DOMAIN1} 2>/dev/null || true
rm -rf /var/www/${DOMAIN2} 2>/dev/null || true
}
trap cleanup ERR
# Check if running as root or with sudo
if [[ $EUID -eq 0 ]]; then
SUDO=""
elif command -v sudo &> /dev/null && sudo -n true 2>/dev/null; then
SUDO="sudo"
else
echo -e "${RED}Error:${NC} This script requires root privileges or sudo access"
exit 1
fi
# Detect distribution and set variables
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
SERVICE_NAME="apache2"
APACHE_PACKAGES="apache2 apache2-utils ssl-cert"
APACHE_USER="www-data"
VHOST_DIR="/etc/apache2/sites-available"
ENABLE_SITE="a2ensite"
FIREWALL_CMD="ufw allow 'Apache Full'"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
SERVICE_NAME="httpd"
APACHE_PACKAGES="httpd mod_ssl openssl"
APACHE_USER="apache"
VHOST_DIR="/etc/httpd/conf.d"
ENABLE_SITE=""
FIREWALL_CMD="firewall-cmd --permanent --add-service=http && firewall-cmd --permanent --add-service=https && firewall-cmd --reload"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
SERVICE_NAME="httpd"
APACHE_PACKAGES="httpd mod_ssl openssl"
APACHE_USER="apache"
VHOST_DIR="/etc/httpd/conf.d"
ENABLE_SITE=""
FIREWALL_CMD="firewall-cmd --permanent --add-service=http && firewall-cmd --permanent --add-service=https && firewall-cmd --reload"
;;
*)
echo -e "${RED}Error:${NC} Unsupported distribution: $ID"
exit 1
;;
esac
else
echo -e "${RED}Error:${NC} Cannot detect operating system"
exit 1
fi
echo -e "${GREEN}Apache Virtual Host Setup Script${NC}"
echo "Setting up virtual hosts for: $DOMAIN1, $DOMAIN2"
echo ""
# Step 1: Update system packages
echo -e "${YELLOW}[1/8]${NC} Updating system packages..."
$SUDO $PKG_UPDATE
# Step 2: Install Apache web server
echo -e "${YELLOW}[2/8]${NC} Installing Apache web server..."
$SUDO $PKG_INSTALL $APACHE_PACKAGES
# Step 3: Enable and start Apache service
echo -e "${YELLOW}[3/8]${NC} Enabling and starting Apache service..."
$SUDO systemctl enable --now $SERVICE_NAME
# Step 4: Configure firewall rules
echo -e "${YELLOW}[4/8]${NC} Configuring firewall rules..."
if command -v ufw &> /dev/null && [[ "$ID" =~ ^(ubuntu|debian)$ ]]; then
$SUDO ufw --force enable 2>/dev/null || true
$SUDO $FIREWALL_CMD
elif command -v firewall-cmd &> /dev/null; then
$SUDO bash -c "$FIREWALL_CMD"
fi
# Step 5: Create directory structure for websites
echo -e "${YELLOW}[5/8]${NC} Creating directory structure..."
$SUDO mkdir -p /var/www/${DOMAIN1}/{html,logs}
$SUDO mkdir -p /var/www/${DOMAIN2}/{html,logs}
# Set correct ownership and permissions
$SUDO chown -R ${APACHE_USER}:${APACHE_USER} /var/www/
$SUDO chmod -R 755 /var/www/
# Step 6: Create sample HTML files
echo -e "${YELLOW}[6/8]${NC} Creating sample HTML files..."
$SUDO tee /var/www/${DOMAIN1}/html/index.html > /dev/null << EOF
<!DOCTYPE html>
<html>
<head>
<title>Welcome to ${DOMAIN1}</title>
</head>
<body>
<h1>Success! ${DOMAIN1} virtual host is working</h1>
<p>This is the main website.</p>
</body>
</html>
EOF
$SUDO tee /var/www/${DOMAIN2}/html/index.html > /dev/null << EOF
<!DOCTYPE html>
<html>
<head>
<title>Welcome to ${DOMAIN2}</title>
</head>
<body>
<h1>Success! ${DOMAIN2} virtual host is working</h1>
<p>This is the blog subdomain.</p>
</body>
</html>
EOF
# Set proper permissions for HTML files
$SUDO chmod 644 /var/www/${DOMAIN1}/html/index.html
$SUDO chmod 644 /var/www/${DOMAIN2}/html/index.html
# Step 7: Create virtual host configuration files
echo -e "${YELLOW}[7/8]${NC} Creating virtual host configuration files..."
# Virtual host for domain1
if [[ "$ID" =~ ^(ubuntu|debian)$ ]]; then
VHOST_FILE="${VHOST_DIR}/${DOMAIN1}.conf"
else
VHOST_FILE="${VHOST_DIR}/${DOMAIN1}.conf"
fi
$SUDO tee $VHOST_FILE > /dev/null << EOF
<VirtualHost *:80>
ServerName ${DOMAIN1}
ServerAlias www.${DOMAIN1}
DocumentRoot /var/www/${DOMAIN1}/html
ErrorLog /var/www/${DOMAIN1}/logs/error.log
CustomLog /var/www/${DOMAIN1}/logs/access.log combined
<Directory /var/www/${DOMAIN1}/html>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
EOF
# Virtual host for domain2
if [[ "$ID" =~ ^(ubuntu|debian)$ ]]; then
VHOST_FILE2="${VHOST_DIR}/${DOMAIN2}.conf"
else
VHOST_FILE2="${VHOST_DIR}/${DOMAIN2}.conf"
fi
$SUDO tee $VHOST_FILE2 > /dev/null << EOF
<VirtualHost *:80>
ServerName ${DOMAIN2}
DocumentRoot /var/www/${DOMAIN2}/html
ErrorLog /var/www/${DOMAIN2}/logs/error.log
CustomLog /var/www/${DOMAIN2}/logs/access.log combined
<Directory /var/www/${DOMAIN2}/html>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
EOF
# Enable sites on Debian/Ubuntu systems
if [[ "$ID" =~ ^(ubuntu|debian)$ ]] && [[ -n "$ENABLE_SITE" ]]; then
$SUDO $ENABLE_SITE ${DOMAIN1}.conf
$SUDO $ENABLE_SITE ${DOMAIN2}.conf
$SUDO a2dissite 000-default 2>/dev/null || true
fi
# Step 8: Restart Apache and verify
echo -e "${YELLOW}[8/8]${NC} Restarting Apache and verifying configuration..."
$SUDO systemctl restart $SERVICE_NAME
# Verify Apache configuration
if $SUDO ${SERVICE_NAME%d} -t 2>/dev/null || $SUDO httpd -t 2>/dev/null; then
echo -e "${GREEN}✓${NC} Apache configuration is valid"
else
echo -e "${RED}✗${NC} Apache configuration has errors"
exit 1
fi
# Verify service status
if systemctl is-active --quiet $SERVICE_NAME; then
echo -e "${GREEN}✓${NC} Apache service is running"
else
echo -e "${RED}✗${NC} Apache service is not running"
exit 1
fi
echo ""
echo -e "${GREEN}Installation completed successfully!${NC}"
echo ""
echo "Next steps:"
echo "1. Point your domains (${DOMAIN1}, ${DOMAIN2}) to this server's IP address"
echo "2. Install Certbot for SSL certificates:"
if [[ "$ID" =~ ^(ubuntu|debian)$ ]]; then
echo " sudo apt install -y certbot python3-certbot-apache"
else
echo " sudo dnf install -y certbot python3-certbot-apache"
fi
echo "3. Obtain SSL certificates:"
echo " sudo certbot --apache -d ${DOMAIN1} -d www.${DOMAIN1} -d ${DOMAIN2}"
echo ""
echo "Your virtual hosts are now configured and ready to serve content!"
Review the script before running. Execute with: bash install.sh