When your web server sits behind a load balancer, the REMOTE_ADDR server variable becomes practically useless for identifying actual client IPs. This creates headaches for scenarios like maintenance mode whitelisting, where you need to exempt certain IPs from redirection.
Here's the typical .htaccess setup for maintenance mode redirection:
RewriteEngine on
RewriteCond %{REQUEST_URI} !/maintenance.php$ [NC]
RewriteCond %{REQUEST_URI} !\.(jpe?g?|png|gif) [NC]
RewriteRule .* /maintenance.php [R=302,L]
With a load balancer in place, REMOTE_ADDR only shows the LB's internal IP. Test this with:
RewriteEngine on
RewriteCond %{REMOTE_ADDR} !^11\.111\.111\.111
# This will never match because REMOTE_ADDR contains LB's IP
Most LBs pass the original client IP in the X-Forwarded-For header. The correct syntax to access it in .htaccess is:
RewriteCond %{HTTP:X-Forwarded-For} !^11\.111\.111\.111
Here's the full working version that:
- Exempts your IP
- Exempts maintenance.php itself
- Allows image requests
RewriteEngine on
RewriteCond %{HTTP:X-Forwarded-For} !^11\.111\.111\.111
RewriteCond %{REQUEST_URI} !/maintenance.php$ [NC]
RewriteCond %{REQUEST_URI} !\.(jpe?g?|png|gif) [NC]
RewriteRule .* /maintenance.php [R=302,L]
For teams needing multiple IP exemptions:
RewriteCond %{HTTP:X-Forwarded-For} !^(11\.111\.111\.111|22\.222\.222\.222|33\.333\.333\.333)
If using Cloudflare, you should check CF-Connecting-IP instead:
RewriteCond %{HTTP:CF-Connecting-IP} !^11\.111\.111\.111
If it's not working:
- Verify your LB actually sets X-Forwarded-For
- Check Apache logs for the actual header name
- Consider using SetEnvIf for more complex logic
When working with web servers behind load balancers, the standard REMOTE_ADDR
server variable in .htaccess files becomes problematic. Instead of showing the visitor's real IP address, it returns the load balancer's internal IP. This creates challenges when implementing IP-based access rules, particularly for maintenance mode scenarios.
Most load balancers pass the original client IP through specific HTTP headers. The most common ones are:
- X-Forwarded-For
- X-Real-IP
- Forwarded (RFC 7239 standard)
After testing various approaches, the correct syntax for accessing the forwarded IP in .htaccess is:
RewriteEngine on
RewriteCond %{HTTP:X-Forwarded-For} !^123\.456\.789\.012
RewriteCond %{REQUEST_URI} !/maintenance.php$ [NC]
RewriteCond %{REQUEST_URI} !\.(jpe?g?|png|gif) [NC]
RewriteRule .* /maintenance.php [R=302,L]
When dealing with multiple proxies, X-Forwarded-For contains a comma-separated list of IPs. Here's how to extract the original client IP:
RewriteCond %{HTTP:X-Forwarded-For} !^([^,]*,\s*)*123\.456\.789\.012
Some environments use different headers. Here are alternative conditions you might need:
# For X-Real-IP
RewriteCond %{HTTP:X-Real-IP} !^123\.456\.789\.012
# For CloudFront
RewriteCond %{HTTP:CloudFront-Viewer-IP} !^123\.456\.789\.012
Remember that HTTP headers can be spoofed. For critical security rules, consider:
- Validating IPs at the load balancer level
- Using additional authentication methods
- Implementing firewall rules instead of .htaccess when possible
To verify your setup is working, create a test rule:
RewriteCond %{HTTP:X-Forwarded-For} ^123\.456\.789\.012
RewriteRule ^test-ip$ /ip-test-success.php [L]
Then access yoursite.com/test-ip
from your whitelisted IP to confirm the rule triggers correctly.