Virtual Hosts Configuration in Apache: Complete Guide

Introduction

Virtual hosting is a powerful Apache feature that enables a single physical server to host multiple websites, each with its own domain name, configuration, and content. This capability has revolutionized web hosting, making it cost-effective to run numerous websites on a single server while maintaining complete isolation between sites.

Apache supports two types of virtual hosting: name-based and IP-based. Name-based virtual hosting, the most common approach, uses the HTTP Host header to determine which website to serve, allowing unlimited websites on a single IP address. IP-based virtual hosting assigns each website a unique IP address, which is necessary for certain SSL/TLS configurations or when name-based hosting isn't suitable.

This comprehensive guide covers everything you need to know about configuring virtual hosts in Apache. You'll learn how to set up both name-based and IP-based virtual hosts, configure SSL/TLS certificates, implement security best practices, troubleshoot common issues, and optimize performance. Whether you're hosting a small blog or managing a complex multi-site environment, mastering virtual hosts is essential for effective Apache administration.

Prerequisites

Before configuring virtual hosts, ensure you have:

  • Apache installed and running on Ubuntu/Debian or CentOS/Rocky Linux
  • Root or sudo access to your server
  • Basic understanding of Apache configuration
  • Domain names configured with DNS pointing to your server's IP
  • Familiarity with Linux file permissions and directory structure
  • Text editor knowledge (nano, vim, or other)
  • Understanding of basic networking concepts
  • SSL/TLS certificates (if configuring HTTPS virtual hosts)

Understanding Virtual Hosts

Name-Based Virtual Hosts

Name-based virtual hosting allows multiple domains on one IP address:

Client Request Flow:
1. Client requests http://example.com/page.html
2. Browser sends HTTP request with "Host: example.com" header
3. Apache receives request on IP address 192.168.1.100:80
4. Apache checks Host header against configured virtual hosts
5. Apache serves content from matching virtual host
6. If no match found, serves from default virtual host

Example configuration:

# All virtual hosts share the same IP
<VirtualHost 192.168.1.100:80>
    ServerName example.com
    DocumentRoot /var/www/example.com
</VirtualHost>

<VirtualHost 192.168.1.100:80>
    ServerName another.com
    DocumentRoot /var/www/another.com
</VirtualHost>

IP-Based Virtual Hosts

IP-based virtual hosting assigns each site a unique IP:

# Each virtual host has different IP
<VirtualHost 192.168.1.100:80>
    ServerName example.com
    DocumentRoot /var/www/example.com
</VirtualHost>

<VirtualHost 192.168.1.101:80>
    ServerName another.com
    DocumentRoot /var/www/another.com
</VirtualHost>

Use cases:

  • SSL/TLS with Server Name Indication (SNI) not available
  • Clients that don't send Host headers
  • Requirement for separate IP addresses per site

Virtual Host Directory Structure

Ubuntu and Debian

Apache on Ubuntu/Debian uses a sites-available/sites-enabled structure:

/etc/apache2/
├── sites-available/          # All virtual host configs
│   ├── 000-default.conf     # Default HTTP virtual host
│   ├── default-ssl.conf     # Default HTTPS virtual host
│   ├── example.com.conf     # Custom virtual host
│   └── another.com.conf     # Another virtual host
└── sites-enabled/            # Enabled virtual hosts (symlinks)
    ├── 000-default.conf -> ../sites-available/000-default.conf
    └── example.com.conf -> ../sites-available/example.com.conf

Workflow:

  1. Create configuration in sites-available/
  2. Enable with a2ensite command (creates symlink)
  3. Reload Apache to apply changes

CentOS, Rocky Linux, and AlmaLinux

Red Hat-based systems use a simpler approach:

/etc/httpd/
└── conf.d/                   # All *.conf files loaded
    ├── example.com.conf
    ├── another.com.conf
    └── ssl.conf

Workflow:

  1. Create .conf file in conf.d/
  2. Reload Apache to apply changes

Creating Your First Virtual Host

Step 1: Create Document Root Directory

Create the directory structure for your website:

# Ubuntu/Debian
sudo mkdir -p /var/www/example.com/public_html
sudo mkdir -p /var/www/example.com/logs

# CentOS/Rocky/AlmaLinux
sudo mkdir -p /var/www/example.com/public_html
sudo mkdir -p /var/www/example.com/logs

Step 2: Set Proper Permissions

Configure ownership and permissions:

# Ubuntu/Debian
sudo chown -R www-data:www-data /var/www/example.com
sudo chmod -R 755 /var/www/example.com

# CentOS/Rocky/AlmaLinux
sudo chown -R apache:apache /var/www/example.com
sudo chmod -R 755 /var/www/example.com

On SELinux-enabled systems (CentOS/Rocky/AlmaLinux):

# Set correct SELinux context
sudo chcon -R -t httpd_sys_content_t /var/www/example.com/public_html
sudo chcon -R -t httpd_log_t /var/www/example.com/logs

# Make it permanent
sudo semanage fcontext -a -t httpd_sys_content_t "/var/www/example.com/public_html(/.*)?"
sudo semanage fcontext -a -t httpd_log_t "/var/www/example.com/logs(/.*)?"
sudo restorecon -Rv /var/www/example.com

Step 3: Create Test Content

Create a simple index.html file:

sudo nano /var/www/example.com/public_html/index.html

Add the following content:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Welcome to Example.com</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 800px;
            margin: 50px auto;
            padding: 20px;
            background-color: #f5f5f5;
        }
        .container {
            background: white;
            padding: 40px;
            border-radius: 8px;
            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        h1 {
            color: #d14836;
            border-bottom: 3px solid #d14836;
            padding-bottom: 10px;
        }
        .info {
            background: #e8f4f8;
            padding: 15px;
            border-left: 4px solid #0088cc;
            margin: 20px 0;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Welcome to Example.com</h1>
        <p>Your Apache virtual host is configured correctly!</p>
        <div class="info">
            <strong>Server Information:</strong><br>
            Virtual Host: example.com<br>
            Document Root: /var/www/example.com/public_html<br>
            Status: Active and Running
        </div>
        <p>You can now upload your website content to this directory.</p>
    </div>
</body>
</html>

Step 4: Create Virtual Host Configuration

Ubuntu/Debian Configuration

Create the virtual host configuration file:

sudo nano /etc/apache2/sites-available/example.com.conf

Add the basic configuration:

<VirtualHost *:80>
    # Basic settings
    ServerName example.com
    ServerAlias www.example.com
    ServerAdmin [email protected]

    # Document root
    DocumentRoot /var/www/example.com/public_html

    # Directory permissions
    <Directory /var/www/example.com/public_html>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    # Logging
    ErrorLog /var/www/example.com/logs/error.log
    CustomLog /var/www/example.com/logs/access.log combined

    # Optional: Set environment variable
    SetEnv ENVIRONMENT production
</VirtualHost>

Enable the virtual host:

# Enable the site
sudo a2ensite example.com.conf

# Test configuration
sudo apache2ctl configtest

# Reload Apache
sudo systemctl reload apache2

CentOS/Rocky/AlmaLinux Configuration

Create the configuration file:

sudo nano /etc/httpd/conf.d/example.com.conf

Add the configuration:

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    ServerAdmin [email protected]

    DocumentRoot /var/www/example.com/public_html

    <Directory /var/www/example.com/public_html>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog /var/www/example.com/logs/error.log
    CustomLog /var/www/example.com/logs/access.log combined
</VirtualHost>

Test and reload:

# Test configuration
sudo httpd -t

# Reload Apache
sudo systemctl reload httpd

Advanced Virtual Host Configuration

Multiple Domain Names (ServerAlias)

Host multiple domains pointing to the same content:

<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com example.net www.example.net example.org
    DocumentRoot /var/www/example.com/public_html

    <Directory /var/www/example.com/public_html>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/example-error.log
    CustomLog ${APACHE_LOG_DIR}/example-access.log combined
</VirtualHost>

Wildcard ServerAlias

Match all subdomains:

<VirtualHost *:80>
    ServerName example.com
    ServerAlias *.example.com
    DocumentRoot /var/www/example.com/public_html

    # Log subdomain in access logs
    LogFormat "%v %h %l %u %t \"%r\" %>s %b" vhost_combined
    CustomLog ${APACHE_LOG_DIR}/example-access.log vhost_combined
</VirtualHost>

Subdomain Virtual Hosts

Create separate virtual hosts for subdomains:

# Main domain
<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com/public_html
</VirtualHost>

# Blog subdomain
<VirtualHost *:80>
    ServerName blog.example.com
    DocumentRoot /var/www/example.com/blog

    <Directory /var/www/example.com/blog>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/blog.example-error.log
    CustomLog ${APACHE_LOG_DIR}/blog.example-access.log combined
</VirtualHost>

# API subdomain
<VirtualHost *:80>
    ServerName api.example.com
    DocumentRoot /var/www/example.com/api

    <Directory /var/www/example.com/api>
        Options -Indexes +FollowSymLinks
        AllowOverride None
        Require all granted
    </Directory>

    # API-specific logging format
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %D" api_log
    CustomLog ${APACHE_LOG_DIR}/api.example-access.log api_log
</VirtualHost>

Directory-Specific Options

Configure different options for different directories:

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/example.com/public_html

    # Main website directory
    <Directory /var/www/example.com/public_html>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    # Downloads directory - enable directory listing
    <Directory /var/www/example.com/public_html/downloads>
        Options +Indexes +FollowSymLinks
        AllowOverride None
        Require all granted
        IndexOptions FancyIndexing NameWidth=* DescriptionWidth=*
    </Directory>

    # Admin area - restrict access
    <Directory /var/www/example.com/public_html/admin>
        Options -Indexes +FollowSymLinks
        AllowOverride None
        Require ip 192.168.1.0/24
        Require ip 10.0.0.100

        # Additional authentication
        AuthType Basic
        AuthName "Admin Area"
        AuthUserFile /etc/apache2/.htpasswd
        Require valid-user
    </Directory>

    # Static assets - long cache times
    <Directory /var/www/example.com/public_html/assets>
        Options -Indexes +FollowSymLinks
        AllowOverride None
        Require all granted

        <IfModule mod_expires.c>
            ExpiresActive On
            ExpiresByType image/jpg "access plus 1 year"
            ExpiresByType image/jpeg "access plus 1 year"
            ExpiresByType image/png "access plus 1 year"
            ExpiresByType image/gif "access plus 1 year"
            ExpiresByType text/css "access plus 1 month"
            ExpiresByType application/javascript "access plus 1 month"
        </IfModule>
    </Directory>
</VirtualHost>

URL Redirection

Redirect www to non-www (or vice versa):

# Redirect www to non-www
<VirtualHost *:80>
    ServerName www.example.com
    Redirect permanent / http://example.com/
</VirtualHost>

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/example.com/public_html
    # ... rest of configuration
</VirtualHost>

# Alternative using mod_rewrite
<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com/public_html

    <IfModule mod_rewrite.c>
        RewriteEngine On
        RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
        RewriteRule ^(.*)$ http://%1$1 [R=301,L]
    </IfModule>
</VirtualHost>

Custom Error Pages

Configure custom error pages per virtual host:

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/example.com/public_html

    # Custom error documents
    ErrorDocument 400 /errors/400.html
    ErrorDocument 401 /errors/401.html
    ErrorDocument 403 /errors/403.html
    ErrorDocument 404 /errors/404.html
    ErrorDocument 500 /errors/500.html
    ErrorDocument 502 /errors/502.html
    ErrorDocument 503 /errors/503.html

    <Directory /var/www/example.com/public_html>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

Create custom error pages:

sudo mkdir -p /var/www/example.com/public_html/errors
sudo nano /var/www/example.com/public_html/errors/404.html

Example 404 page:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>404 - Page Not Found</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            text-align: center;
            padding: 50px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
        }
        h1 { font-size: 72px; margin: 0; }
        p { font-size: 24px; }
        a { color: #ffd700; text-decoration: none; }
    </style>
</head>
<body>
    <h1>404</h1>
    <p>Oops! The page you're looking for doesn't exist.</p>
    <p><a href="/">Return to homepage</a></p>
</body>
</html>

SSL/TLS Virtual Hosts

Configure HTTPS virtual hosts:

Enable SSL Module

# Ubuntu/Debian
sudo a2enmod ssl
sudo systemctl restart apache2

# CentOS/Rocky/AlmaLinux
sudo yum install mod_ssl -y  # or dnf
sudo systemctl restart httpd

Basic HTTPS Virtual Host

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com/public_html

    # SSL Engine
    SSLEngine on

    # SSL Certificates
    SSLCertificateFile /etc/ssl/certs/example.com.crt
    SSLCertificateKeyFile /etc/ssl/private/example.com.key
    SSLCertificateChainFile /etc/ssl/certs/chain.crt

    # SSL Protocols and Ciphers
    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
    SSLCipherSuite HIGH:!aNULL:!MD5:!3DES
    SSLHonorCipherOrder on

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

    <Directory /var/www/example.com/public_html>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/example-ssl-error.log
    CustomLog ${APACHE_LOG_DIR}/example-ssl-access.log combined
</VirtualHost>

# HTTP to HTTPS redirect
<VirtualHost *:80>
    ServerName example.com
    ServerAlias www.example.com
    Redirect permanent / https://example.com/
</VirtualHost>

Let's Encrypt Configuration

After obtaining Let's Encrypt certificates with Certbot:

<VirtualHost *:443>
    ServerName example.com
    ServerAlias www.example.com
    DocumentRoot /var/www/example.com/public_html

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

    # Strong SSL configuration
    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

    # OCSP Stapling
    SSLUseStapling On
    SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"

    <Directory /var/www/example.com/public_html>
        Options -Indexes +FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>

Performance Optimization

Enable Compression

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/example.com/public_html

    # Enable compression
    <IfModule mod_deflate.c>
        AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css
        AddOutputFilterByType DEFLATE application/javascript application/json
        AddOutputFilterByType DEFLATE application/xml application/rss+xml
        AddOutputFilterByType DEFLATE image/svg+xml

        # Don't compress images
        SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png|ico)$ no-gzip dont-vary

        # Handle browser quirks
        BrowserMatch ^Mozilla/4 gzip-only-text/html
        BrowserMatch ^Mozilla/4\.0[678] no-gzip
        BrowserMatch \bMSIE !no-gzip !gzip-only-text/html

        # Add Vary header
        Header append Vary User-Agent env=!dont-vary
    </IfModule>
</VirtualHost>

Browser Caching

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/example.com/public_html

    # Enable expires module
    <IfModule mod_expires.c>
        ExpiresActive On
        ExpiresDefault "access plus 1 month"

        # HTML
        ExpiresByType text/html "access plus 1 hour"

        # Images
        ExpiresByType image/jpg "access plus 1 year"
        ExpiresByType image/jpeg "access plus 1 year"
        ExpiresByType image/png "access plus 1 year"
        ExpiresByType image/gif "access plus 1 year"
        ExpiresByType image/svg+xml "access plus 1 year"
        ExpiresByType image/x-icon "access plus 1 year"

        # CSS and JavaScript
        ExpiresByType text/css "access plus 1 month"
        ExpiresByType application/javascript "access plus 1 month"

        # Fonts
        ExpiresByType font/woff "access plus 1 year"
        ExpiresByType font/woff2 "access plus 1 year"
        ExpiresByType application/font-woff "access plus 1 year"
        ExpiresByType application/font-woff2 "access plus 1 year"
    </IfModule>

    # Cache-Control headers
    <IfModule mod_headers.c>
        <FilesMatch "\.(jpg|jpeg|png|gif|ico|svg)$">
            Header set Cache-Control "public, max-age=31536000, immutable"
        </FilesMatch>

        <FilesMatch "\.(css|js)$">
            Header set Cache-Control "public, max-age=2592000"
        </FilesMatch>
    </IfModule>
</VirtualHost>

PHP-FPM Integration

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/example.com/public_html

    # PHP-FPM via proxy
    <FilesMatch \.php$>
        SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost"
    </FilesMatch>

    # Or via mod_fastcgi
    <Directory /var/www/example.com/public_html>
        Options -Indexes +FollowSymLinks +ExecCGI
        AllowOverride All
        Require all granted
    </Directory>

    # Proxy timeout settings
    ProxyTimeout 300
</VirtualHost>

Security Configuration

IP-Based Access Control

<VirtualHost *:80>
    ServerName admin.example.com
    DocumentRoot /var/www/example.com/admin

    <Directory /var/www/example.com/admin>
        Options -Indexes +FollowSymLinks
        AllowOverride None

        # Allow specific IPs only
        Require ip 192.168.1.0/24
        Require ip 10.0.0.100
        Require ip 203.0.113.50

        # Or deny specific IPs
        <RequireAll>
            Require all granted
            Require not ip 192.0.2.100
        </RequireAll>
    </Directory>
</VirtualHost>

Basic Authentication

<VirtualHost *:80>
    ServerName secure.example.com
    DocumentRoot /var/www/example.com/secure

    <Directory /var/www/example.com/secure>
        AuthType Basic
        AuthName "Restricted Area"
        AuthUserFile /etc/apache2/.htpasswd
        Require valid-user

        Options -Indexes +FollowSymLinks
        AllowOverride None
    </Directory>
</VirtualHost>

Create password file:

# Ubuntu/Debian
sudo htpasswd -c /etc/apache2/.htpasswd username

# CentOS/Rocky/AlmaLinux
sudo htpasswd -c /etc/httpd/.htpasswd username

Security Headers

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/example.com/public_html

    # Security headers
    <IfModule mod_headers.c>
        # Prevent clickjacking
        Header always set X-Frame-Options "SAMEORIGIN"

        # Prevent MIME sniffing
        Header always set X-Content-Type-Options "nosniff"

        # XSS Protection
        Header always set X-XSS-Protection "1; mode=block"

        # Referrer Policy
        Header always set Referrer-Policy "strict-origin-when-cross-origin"

        # Content Security Policy
        Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"

        # Remove server information
        Header always unset X-Powered-By
        Header always unset Server
    </IfModule>
</VirtualHost>

Logging Configuration

Custom Log Formats

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/example.com/public_html

    # Define custom log format
    LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
    LogFormat "%h %l %u %t \"%r\" %>s %b %D" performance

    # Use custom formats
    CustomLog ${APACHE_LOG_DIR}/example-access.log vhost_combined
    CustomLog ${APACHE_LOG_DIR}/example-performance.log performance
    ErrorLog ${APACHE_LOG_DIR}/example-error.log

    # Set error log level
    LogLevel warn
</VirtualHost>

Separate Logs Per Subdomain

<VirtualHost *:80>
    ServerName blog.example.com
    DocumentRoot /var/www/example.com/blog

    ErrorLog /var/log/apache2/blog.example-error.log
    CustomLog /var/log/apache2/blog.example-access.log combined
</VirtualHost>

<VirtualHost *:80>
    ServerName api.example.com
    DocumentRoot /var/www/example.com/api

    ErrorLog /var/log/apache2/api.example-error.log
    CustomLog /var/log/apache2/api.example-access.log combined
</VirtualHost>

Conditional Logging

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/example.com/public_html

    # Don't log static files
    SetEnvIf Request_URI "\.(gif|jpg|jpeg|png|css|js|ico)$" dontlog

    # Don't log health checks
    SetEnvIf Request_URI "^/health$" dontlog

    CustomLog ${APACHE_LOG_DIR}/example-access.log combined env=!dontlog
</VirtualHost>

Troubleshooting

Test Configuration

Always test configuration before reloading:

# Ubuntu/Debian
sudo apache2ctl configtest

# CentOS/Rocky/AlmaLinux
sudo httpd -t

Check Virtual Host Configuration

# Ubuntu/Debian
sudo apache2ctl -S

# CentOS/Rocky/AlmaLinux
sudo httpd -S

Output shows:

  • All configured virtual hosts
  • Port assignments
  • ServerName and ServerAlias values
  • Default virtual host

Common Issues

Issue: "Name or service not known"

# Ensure ServerName is resolvable
# Add to /etc/hosts for testing:
sudo nano /etc/hosts
# Add: 127.0.0.1 example.com www.example.com

Issue: Wrong virtual host served

# Check virtual host order
sudo apache2ctl -S

# Ensure ServerName matches exactly
# Check for default catch-all virtual host

Issue: Permission denied errors

# Check file permissions
ls -la /var/www/example.com/

# Fix permissions
sudo chown -R www-data:www-data /var/www/example.com  # Ubuntu/Debian
sudo chown -R apache:apache /var/www/example.com      # CentOS/Rocky

# Check SELinux (CentOS/Rocky/AlmaLinux)
sudo getenforce
sudo chcon -R -t httpd_sys_content_t /var/www/example.com/public_html

Best Practices

Organized Configuration

Keep virtual host files organized:

# Ubuntu/Debian naming convention
/etc/apache2/sites-available/
├── 000-default.conf        # Default/catch-all
├── example.com.conf        # Main domain
├── blog.example.com.conf   # Subdomain
└── api.example.com.conf    # API subdomain

Documentation

Document each virtual host:

#
# Virtual Host Configuration: example.com
# Created: 2024-01-15
# Modified: 2024-01-20
# Purpose: Main company website
# Contacts: [email protected]
#

<VirtualHost *:80>
    ServerName example.com
    # ...
</VirtualHost>

Regular Backups

Backup virtual host configurations:

# Ubuntu/Debian
sudo tar -czf apache-vhosts-backup-$(date +%F).tar.gz /etc/apache2/sites-available/

# CentOS/Rocky/AlmaLinux
sudo tar -czf httpd-vhosts-backup-$(date +%F).tar.gz /etc/httpd/conf.d/

Monitoring

Monitor virtual host access and errors:

# Real-time access log
sudo tail -f /var/log/apache2/example-access.log

# Real-time error log
sudo tail -f /var/log/apache2/example-error.log

# Count requests per virtual host
sudo awk '{print $1}' /var/log/apache2/*-access.log | sort | uniq -c | sort -nr

Conclusion

Virtual hosts are a fundamental Apache feature that enables efficient hosting of multiple websites on a single server. This guide has covered everything from basic name-based virtual hosts to advanced SSL/TLS configurations, security hardening, and performance optimization.

Key takeaways:

  • Name-based virtual hosting allows unlimited websites on one IP address
  • Proper directory structure and permissions are essential for security
  • SSL/TLS virtual hosts require additional configuration but are critical for modern web
  • Performance optimization through caching and compression improves user experience
  • Security headers and access controls protect your websites
  • Regular testing and monitoring ensure reliable operation

Master virtual host configuration to efficiently manage multiple websites, reduce hosting costs, and maintain secure, performant web environments. Continue exploring advanced topics like dynamic virtual hosts with mod_vhost_alias, integration with configuration management tools, and automated SSL certificate management with Let's Encrypt.