Configuring Apache Virtual Hosts Based on Client IP Address for Selective Content Delivery


2 views

In web server administration, there are scenarios where you might want to serve different content based on the client's IP address while maintaining the same hostname and interface. This differs from traditional name-based or port-based virtual hosting and requires special Apache configuration.

The most effective approach combines mod_rewrite with Apache's environment variables:

<VirtualHost *:80>
    ServerName example.com

    # Set environment variable if client IP matches
    RewriteEngine On
    RewriteCond %{REMOTE_ADDR} ^123\.45\.67\.89$
    RewriteRule .* - [E=TRUSTED_IP:1]

    # Main configuration for trusted IP
    <IfDefine ENV:TRUSTED_IP>
        DocumentRoot /var/www/main_site
        ErrorLog ${APACHE_LOG_DIR}/main_error.log
        CustomLog ${APACHE_LOG_DIR}/main_access.log combined
    </IfDefine>

    # Fallback for all other IPs
    <IfDefine !ENV:TRUSTED_IP>
        DocumentRoot /var/www/holding_page
        ErrorLog ${APACHE_LOG_DIR}/holding_error.log
        CustomLog ${APACHE_LOG_DIR}/holding_access.log combined
    </IfDefine>
</VirtualHost>

For more complex scenarios with multiple IP ranges, consider this pattern:

# Primary VirtualHost for trusted IPs
<VirtualHost *:80>
    ServerName example.com
    
    # IP range check
    <If "%{REMOTE_ADDR} -ipmatch '192.168.1.0/24'">
        DocumentRoot /var/www/main_site
        # Other trusted configuration...
    </If>
</VirtualHost>

# Catch-all VirtualHost for others
<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/holding_page
    
    # Exclude trusted IPs
    <If "%{REMOTE_ADDR} -ipmatch '192.168.1.0/24'">
        # Skip this VirtualHost for trusted IPs
        Redirect 403 /
    </If>
</VirtualHost>

When implementing IP-based virtual hosting:

  • Place IP checks early in the configuration
  • Consider using mod_geoip for geographic-based rules
  • Test with various client IPs using curl: curl --interface eth0 http://example.com

IP-based filtering has limitations:

  • Client IPs can be spoofed or change frequently
  • Consider combining with other authentication methods
  • Regularly update your IP whitelist

When managing a website during development or maintenance, you might want to show different content based on the visitor's IP address. A common scenario is displaying a maintenance page for public visitors while allowing internal IPs to access the full site. While mod_rewrite solutions exist, using separate virtual hosts with different document roots provides cleaner separation.

Apache's <VirtualHost> directive combined with mod_setenvif or mod_rewrite can achieve this. Here's the most straightforward method using SetEnvIf:

<VirtualHost *:80>
    ServerName example.com
    
    SetEnvIf Remote_Addr "^192\.168\.1\.100$" INTERNAL_ACCESS
    
    <If "env('INTERNAL_ACCESS')">
        DocumentRoot /var/www/main_site
    </If>
    <Else>
        DocumentRoot /var/www/maintenance
    </Else>
    
    <Directory "/var/www">
        Require all granted
    </Directory>
</VirtualHost>

For more complex scenarios with multiple IP ranges, consider separate virtual hosts:

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/maintenance
    
    <Directory "/var/www/maintenance">
        Require all granted
    </Directory>
</VirtualHost>

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/main_site
    
    <Directory "/var/www/main_site">
        Require ip 192.168.1.100
        Require ip 10.0.0.0/24
    </Directory>
</VirtualHost>

The first method using environment variables is generally more performant for simple cases, while the multiple VirtualHost approach provides better isolation and more flexible access control. Remember to test your configuration with:

sudo apachectl configtest
sudo systemctl reload apache2

For matching multiple IPs or ranges, you can use regular expressions or CIDR notation:

SetEnvIf Remote_Addr "^(192\.168\.1\.100|10\.0\.0\.[0-9]+)$" INTERNAL_ACCESS

Or in the directory-based approach:

<Directory "/var/www/main_site">
    Require ip 192.168.1.100
    Require ip 10.0.0.0/24
    Require ip 2001:db8::/32
</Directory>