How to Configure Apache to Use a Different Log File Format

How to configure Apache to use a different log file format

I used to stare at Apache’s default access log trying to figure out why a specific endpoint was slow, and every time I’d realize the default format just wasn’t giving me the field I needed. Once I started customizing LogFormat, debugging performance issues and feeding logs into tools like ELK or Grafana Loki got a lot easier. Apache’s mod_log_config module makes this fully customizable, and it’s one of the first things I tune on a new server.

Here’s how I approach Apache logging and how I build custom formats.

How Apache Logging Works

Apache writes two main log streams by default:

Log formats are defined with LogFormat directives using format specifiers (like %h for client IP), then applied to a log file with CustomLog.

Prerequisites

I check the module is active:

apache2ctl -M | grep log_config     # Debian/Ubuntu
httpd -M | grep log_config           # RHEL/CentOS

Default Log Format Reference

Apache ships with predefined formats, usually near the top of httpd.conf or apache2.conf:

LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common

combined is the one I see most often in the wild and includes referer and user-agent data.

Key Format Specifiers I Reach For

SpecifierMeaning
%hRemote host (client IP)
%lRemote logname (rarely used)
%uAuthenticated username, if any
%tTime the request was received
%rFirst line of the request
%>sFinal HTTP status code
%OBytes sent, including headers
%bBytes sent, excluding headers
%{Referer}iReferer header
%{User-Agent}iUser-Agent header
%DRequest duration in microseconds
%TRequest duration in seconds
%pPort the request was served on
%{X-Forwarded-For}iClient IP as seen by an upstream proxy
%XConnection status when the response completed

The full reference lives in the official mod_log_config docs.

Step 1: Define a Custom Log Format

I add a new named format to my main config or virtual host:

LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %D" custom_timed

This extends combined with %D, the request duration — I use it constantly to spot slow endpoints.

Step 2: Apply the Format to a Log File

CustomLog ${APACHE_LOG_DIR}/access.log custom_timed

Or scoped to a specific virtual host:

<VirtualHost *:80>
    ServerName example.com
    CustomLog /var/log/apache2/example.com-access.log custom_timed
    ErrorLog /var/log/apache2/example.com-error.log
</VirtualHost>

Step 3: Test and Reload

sudo apachectl configtest
sudo systemctl reload apache2

Then I generate a request and check the new log line:

curl -s http://example.com/ > /dev/null
tail -n 1 /var/log/apache2/example.com-access.log

Building a JSON Log Format

I lean on structured JSON logs when I need them to slot cleanly into ELK, Loki, or Datadog:

LogFormat "{\"time\":\"%{%Y-%m-%dT%H:%M:%S%z}t\",\"remote_ip\":\"%a\",\"method\":\"%m\",\"uri\":\"%U%q\",\"status\":%>s,\"bytes\":%B,\"referer\":\"%{Referer}i\",\"user_agent\":\"%{User-Agent}i\",\"response_time_us\":%D}" json_log

CustomLog ${APACHE_LOG_DIR}/access.json json_log

One caveat I’ve hit: this simple approach doesn’t escape embedded quotes in user-agent or referer values, which can occasionally break a strict JSON parser. For production-grade JSON logging at scale, I’d rather use mod_log_json or run logs through Fluentd/Logstash, which handle the escaping properly.

Logging Behind a Reverse Proxy or Load Balancer

If Apache sits behind Nginx, HAProxy, or a cloud load balancer, %h logs the proxy’s IP, not the real visitor. I fix that with mod_remoteip:

sudo a2enmod remoteip
RemoteIPHeader X-Forwarded-For
RemoteIPTrustedProxy 10.0.0.0/8

LogFormat "%a %l %u %t \"%r\" %>s %b" proxy_combined
CustomLog ${APACHE_LOG_DIR}/access.log proxy_combined

With mod_remoteip active, %a reflects the real client IP once the trusted proxy header gets parsed correctly.

Conditional Logging

I skip logging for specific requests, like health checks, using environment variables:

SetEnvIf Request_URI "^/healthz$" dontlog
CustomLog ${APACHE_LOG_DIR}/access.log combined env=!dontlog

Real-World Use Cases

Mistakes I’ve Made

Security Best Practices

Performance Optimization

/var/log/apache2/*.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    create 640 www-data adm
    sharedscripts
    postrotate
        systemctl reload apache2 > /dev/null
    endscript
}

Troubleshooting

Log file empty or not updating I check that CustomLog sits inside the correct <VirtualHost> block and that Apache reloaded successfully.

Real client IPs show as the load balancer’s IP mod_remoteip needs configuring as above — raw %h always shows the immediate connecting peer, not the original client, behind a proxy.

JSON logs fail to parse I look for unescaped quotes in User-Agent or Referer; a dedicated JSON logging module or log-shipping pipeline handles escaping properly.

FAQs

Can I have multiple log formats active at once? Yes — multiple CustomLog directives with different formats, each writing to its own file.

Does changing the log format affect log rotation? No, logrotate operates on the file itself regardless of internal format.

Is JSON logging slower than plain text? Negligible difference for typical traffic in my experience; the format string is evaluated the same way either way.

Summary and Key Takeaways

References

Exit mobile version