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>