How to Log Real Client IP in Apache Access Logs When Using mod_remoteip


19 views

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:

  1. Restart Apache: apachectl graceful
  2. Make test requests with X-Forwarded-For headers
  3. 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.