After migrating from mod_extract_forwarded to mod_remoteip in Apache 2.4+, many administrators notice their access logs stubbornly show 127.0.0.1 instead of actual client IPs - despite PHP and other modules correctly displaying the forwarded addresses.
The key lies in proper module activation before the logging phase. Add this to your httpd.conf or vhost configuration:
LoadModule remoteip_module modules/mod_remoteip.so
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 127.0.0.1/32 ::1/128
Standard log formats won't reflect the modified IP. You must explicitly use:
LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
Key points:
%a
captures the remote IP after mod_remoteip processing- Old formats using
%h
will still show proxy IPs - For conditional logging:
%{c}a
records the peer address
When dealing with multiple proxies (e.g., Varnish → Apache):
RemoteIPInternalProxy 192.168.1.10/32 # Varnish IP
RemoteIPInternalProxy 10.0.0.5/32 # Load balancer
RemoteIPTrustedProxy 203.0.113.0/24 # Cloud provider range
- Verify module loading order in httpd.conf
- Check for conflicting directives in .htaccess files
- Test with
curl -H "X-Forwarded-For: 1.2.3.4" http://yourserver
- Inspect raw logs using
tail -f /var/log/httpd/access_log
To log differently for direct vs proxied traffic:
<If "%{REMOTE_ADDR} != '127.0.0.1'">
CustomLog /var/log/httpd/direct_access.log combined
</If>
<Else>
CustomLog /var/log/httpd/proxied_access.log "%{c}a %l %u %t \"%r\" %>s %b"
</Else>
After migrating from mod_extract_forwarded to mod_remoteip in Apache 2.4+, many administrators face a puzzling situation: while the module correctly processes X-Forwarded-For headers for application-level scripts (PHP, CGI, WSGI), the access logs (%a, %h, %{c}a) stubbornly show 127.0.0.1 or the load balancer's IP instead of the original client IP.
The root cause lies in the logging sequence - Apache evaluates the log format before mod_remoteip processes the headers. This means the %a, %h, and %{c}a placeholders capture the connection's immediate source rather than the modified remote IP.
Here's the complete configuration that solved this for me after extensive testing:
# Load the module
LoadModule remoteip_module modules/mod_remoteip.so
# Configure trusted proxies
RemoteIPHeader X-Forwarded-For
RemoteIPInternalProxy 127.0.0.1
RemoteIPInternalProxy 192.168.1.0/24
# CRUCIAL: Tell Apache to use the remote IP for logging
LogFormat "%a %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" forwarded
CustomLog logs/access_log combined
If you prefer different logging formats, consider these variations:
# Option 1: Using %{c}a for conditional logging
LogFormat "%{c}a %l %u %t \"%r\" %>s %b" proxy
# Option 2: Combined with XFF fallback
LogFormat "%a (%{X-Forwarded-For}i) %l %u %t \"%r\" %>s %b" debug
# Option 3: For cloud environments
RemoteIPTrustedProxy 10.0.0.0/8
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b" cloud
After implementing these changes:
- Restart Apache:
apachectl graceful
- Make test requests with X-Forwarded-For headers
- Check your access logs for the real client IPs
Remember that the exact configuration may vary based on your proxy setup and Apache version. Always test in a staging environment before applying to production.