When Apache runs behind a reverse proxy or load balancer, the default access log usually records the proxy connection instead of the original client path. Adding the X-Forwarded-For header to a dedicated access log makes request tracing, abuse review, and proxy troubleshooting much easier because the forwarded client chain is written alongside each request.

Apache access logging is handled by mod_log_config. A named LogFormat string decides which request fields are written, and Apache can attach more than one CustomLog directive to the same virtual host, so you can keep the standard combined log and add a second file for forwarded-client visibility. Current Apache 2.4 behavior also matters here: %a can be rewritten by mod_remoteip when trusted proxy handling is enabled, while %{c}a keeps the underlying TCP peer address for comparison.

The X-Forwarded-For header is only meaningful when your edge proxy strips client-supplied values and rewrites the header itself. The value can contain a comma-separated chain rather than one address, and changing the main access.log format can break parsers or dashboards, so the safer pattern is to add a separate access_xff.log file and validate the configuration before reloading Apache. Examples below use the Debian or Ubuntu layout with /etc/apache2/ and the apache2 service name.

Steps to log X-Forwarded-For IP in Apache:

  1. Find the active LogFormat and CustomLog directives before editing anything.
    $ sudo grep --recursive --line-number --extended-regexp '^[[:space:]]*(LogFormat|CustomLog)[[:space:]]' /etc/apache2
    /etc/apache2/sites-available/000-default.conf:21:	CustomLog ${APACHE_LOG_DIR}/access.log combined
    /etc/apache2/sites-available/default-ssl.conf:13:	CustomLog ${APACHE_LOG_DIR}/access.log combined
    /etc/apache2/conf-available/other-vhosts-access-log.conf:2:CustomLog ${APACHE_LOG_DIR}/other_vhosts_access.log vhost_combined
    /etc/apache2/apache2.conf:212:LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
    /etc/apache2/apache2.conf:213:LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
    /etc/apache2/apache2.conf:214:LogFormat "%h %l %u %t \"%r\" %>s %O" common
    ##### snipped #####

    On RHEL-style systems, search /etc/httpd/ instead and expect the httpd service name.

  2. Open the file that defines the current combined log format.
    $ sudoedit /etc/apache2/apache2.conf

    Some platforms define the default format in /etc/httpd/conf/httpd.conf or a distro-specific include file. Edit the file shown by the previous grep output instead of guessing.

  3. Add a new LogFormat nickname that starts with the forwarded chain and the underlying peer IP.
    LogFormat "\"%{X-Forwarded-For}i\" %{c}a %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined_xff

    Use %{c}a for the connected peer address. When mod_remoteip is enabled, %a becomes the trusted client IP, so it is no longer a reliable proxy-socket field for comparison.

    If your platform's current combined format uses %b instead of %O, keep the existing byte field and add only the quoted X-Forwarded-For value plus %{c}a.

    If mod_remoteip is already rewriting forwarded addresses from trusted proxies, Apache can shorten or remove the raw X-Forwarded-For header after trust evaluation. In that case, use this page to preserve the raw header early, or consider logging %{remoteip-proxy-ip-list}n when you need the trusted proxy chain instead.

  4. Open the virtual host or log destination file that already writes the access log.
    $ sudoedit /etc/apache2/sites-available/000-default.conf

    Use the file path from the initial grep output when the active CustomLog directive lives in a different virtual host or include file.

  5. Keep the existing access log and add a second CustomLog line for the forwarded-client log.
    CustomLog ${APACHE_LOG_DIR}/access.log combined
    CustomLog /var/log/apache2/access_xff.log combined_xff

    Put the new file in the same log directory used by the current access log so ownership, rotation, and backup rules stay aligned.

    Changing the original access.log format can break SIEM pipelines, dashboards, or parsing jobs. A dedicated access_xff.log file isolates the new format.

  6. Test the Apache configuration syntax before reloading it.
    $ sudo apache2ctl configtest
    Syntax OK

    Use sudo apachectl -t or sudo httpd -t on platforms that do not ship the apache2ctl wrapper.

  7. Reload Apache so the new log format and destination become active.
    $ sudo systemctl reload apache2

    Use sudo systemctl reload httpd on most RHEL-family systems, or sudo apachectl graceful when systemd is not managing the service.

  8. Send a test request with an X-Forwarded-For header.
    $ curl --silent --output /dev/null --write-out "%{http_code}\n" --header "X-Forwarded-For: 203.0.113.10, 198.51.100.7" http://127.0.0.1/
    200

    Use the site hostname or add a matching Host header when localhost does not map to the virtual host you changed.

  9. Confirm that the new forwarded-client log records the header chain and the proxy peer IP.
    $ sudo tail --lines 1 /var/log/apache2/access_xff.log
    "203.0.113.10, 198.51.100.7" 127.0.0.1 - - [09/Apr/2026:04:31:46 +0000] "GET / HTTP/1.1" 200 10926 "-" "curl/8.5.0"

    If the first field is empty or only shows direct-client traffic, check that requests are actually passing through the proxy and that the proxy is configured to set X-Forwarded-For.

    On Debian and Ubuntu, the default /etc/logrotate.d/apache2/ wildcard usually covers /var/log/apache2/*.log. On RHEL-style systems, confirm the new filename is included in the httpd logrotate rules.