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:
- 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.
Related: Location for Apache configuration
- 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.
Related: Location for Apache configuration
- 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_xffUse %{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.
- 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.
Related: Location for Apache configuration
- 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_xffPut 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.
- 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.
Related: How to test Apache configuration
- 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.
- 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/ 200Use the site hostname or add a matching Host header when localhost does not map to the virtual host you changed.
- 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.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
