How to Configure Apache Access Logs

How to configure Apache access logs

How to configure Apache access logs

Every time someone visits a site I host, Apache quietly writes down who they are, what they asked for, and how the server responded. That record is the access log, and honestly, it’s one of the most underused tools in a lot of admins’ workflows. I use mine constantly — to spot traffic spikes, catch bots scraping my content, debug broken links, and feed data into analytics tools.

What Is the Apache Access Log

The access log records every single HTTP request that hits your Apache server: the client’s IP address, the timestamp, the requested URL, the HTTP method, the response status code, the number of bytes sent, the referrer, and the user agent. It’s essentially a running history of everyone who’s touched your site.

This is different from the error log (which I cover in a separate post) — the access log doesn’t care whether a request succeeded or failed, it just records that it happened.

Prerequisites

Default Access Log Locations

You can confirm this with:

grep -r "CustomLog" /etc/apache2/   # Debian/Ubuntu
grep -r "CustomLog" /etc/httpd/     # CentOS/RHEL

The Core Directive: CustomLog

Access logging is controlled by the CustomLog directive, which takes a log file path and a format:

CustomLog ${APACHE_LOG_DIR}/access.log combined

combined here refers to a predefined LogFormat — Apache ships with a few standard ones out of the box.

Understanding Log Formats

Apache defines log formats using the LogFormat directive, usually found near the top of apache2.conf or httpd.conf:

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

Here’s what those format strings actually mean, broken down:

DirectiveMeaning
%hRemote host (client IP)
%lRemote logname (usually -)
%uRemote user (if authenticated)
%tTime the request was received
%rFirst line of the request (method, URL, protocol)
%>sFinal HTTP status code
%bResponse size in bytes
%{Referer}iReferrer header
%{User-Agent}iClient’s user agent string

I almost always use combined because the referrer and user agent fields are genuinely useful for spotting bad bots, broken referral links, and unusual client behavior.

Creating a Custom Log Format

Sometimes the built-in formats don’t give me everything I want. For example, I like tracking response time:

LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %D" combined_with_time
CustomLog ${APACHE_LOG_DIR}/access.log combined_with_time

%D logs the time taken to serve the request in microseconds — incredibly useful when I’m chasing down slow endpoints.

Per-Virtual-Host Access Logs

Just like error logs, I set up a dedicated access log per virtual host:

<VirtualHost *:80>
    ServerName example.com
    DocumentRoot /var/www/example.com/public_html

    CustomLog /var/log/apache2/example.com-access.log combined
    ErrorLog /var/log/apache2/example.com-error.log
</VirtualHost>

This keeps traffic data isolated per domain, which matters a lot once you’re running more than one or two sites off the same box.

Conditional Logging

Sometimes I don’t want to log everything — health check pings from a load balancer, for instance, are just noise. Apache lets you set environment variables and skip logging based on them:

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

This keeps my logs cleaner and focused on actual user traffic.

Applying Changes

Always test before reloading:

sudo apachectl configtest
sudo systemctl reload apache2   # Debian/Ubuntu
sudo systemctl reload httpd     # CentOS/RHEL

Reading and Analyzing Access Logs

Tailing Live Traffic

sudo tail -f /var/log/apache2/access.log

Top Requested URLs

awk '{print $7}' /var/log/apache2/access.log | sort | uniq -c | sort -nr | head -20

Top Client IPs

awk '{print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -nr | head -20

Filtering by Status Code

grep " 404 " /var/log/apache2/access.log
grep " 500 " /var/log/apache2/access.log

Using GoAccess for Visual Analysis

I frequently pull logs into GoAccess, a real-time terminal-based log analyzer:

sudo apt install goaccess
goaccess /var/log/apache2/access.log --log-format=COMBINED

It gives you an instant breakdown of top pages, referrers, browsers, and operating systems — way faster than writing your own awk scripts every time.

Log Rotation for Access Logs

Access logs grow far faster than error logs on any moderately busy site, so rotation matters even more here. The default logrotate config usually handles both:

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

For high-traffic sites, I switch from weekly to daily rotation and keep 30 days of compressed history.

Piping Logs to an External Program

Instead of writing directly to a file, Apache can pipe access logs to another program in real time. I use this when I want rotation handled by the logging pipe itself, using rotatelogs:

CustomLog "|/usr/bin/rotatelogs /var/log/apache2/access.%Y-%m-%d.log 86400" combined

This creates a new log file every 86,400 seconds (24 hours) automatically, without relying on a separate logrotate cron job. It’s a pattern I reach for on high-traffic servers where I want date-stamped files without extra tooling.

Logging in JSON Format

For sites feeding logs into tools like the ELK stack (Elasticsearch, Logstash, Kibana) or other structured log pipelines, I define a JSON-formatted LogFormat instead of the default space-delimited one:

LogFormat "{\"time\":\"%t\",\"remoteIP\":\"%a\",\"host\":\"%V\",\"request\":\"%U\",\"query\":\"%q\",\"method\":\"%m\",\"status\":\"%>s\",\"userAgent\":\"%{User-Agent}i\",\"referer\":\"%{Referer}i\"}" json
CustomLog ${APACHE_LOG_DIR}/access.json.log json

This makes downstream parsing far more reliable than trying to regex-parse the traditional combined format, especially once you’re aggregating logs from multiple servers.

Real-World Use Cases

Troubleshooting Tips

Common Mistakes to Avoid

  1. Not separating logs per virtual host — makes traffic analysis a nightmare on multi-site servers.
  2. Logging real client IPs incorrectly behind a proxy or load balancer.
  3. Forgetting log rotation, which leads to massive files that are painfully slow to grep through.
  4. Using common format when you actually need referrer/user-agent data.
  5. Not excluding health checks and monitoring traffic, which clutters analysis.

Security Best Practices

Performance Optimization

Frequently Asked Questions

Q: What’s the default Apache access log format? A: Most distros default to combined, which includes IP, timestamp, request line, status code, size, referrer, and user agent.

Q: How do I log the real visitor IP behind a load balancer? A: Use mod_remoteip or log %{X-Forwarded-For}i in your custom log format.

Q: Can I exclude certain requests from logging? A: Yes, use SetEnvIf combined with the env= condition on your CustomLog directive.

Q: How long should I keep access logs? A: It depends on your compliance requirements, but 30–90 days of rotated, compressed logs is a common baseline.

Q: What tool do you recommend for analyzing Apache access logs? A: I personally like GoAccess for quick real-time analysis, and AWStats or Matomo for longer-term historical reporting.

Summary and Key Takeaways

Configuring Apache access logs well takes maybe fifteen minutes, but it’s the difference between having real visibility into your traffic and flying blind. Key points to remember:

Once this is set up properly, you’ll actually want to check your logs instead of dreading them.

References

Exit mobile version