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

  • Apache installed and running (Debian/Ubuntu or CentOS/RHEL)
  • Root or sudo access
  • Comfort editing config files and reloading services
  • mod_log_config enabled (this is on by default in nearly every Apache install)

Default Access Log Locations

  • Debian/Ubuntu: /var/log/apache2/access.log
  • CentOS/RHEL/Fedora: /var/log/httpd/access_log

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

  • Traffic analysis: Seeing which pages get the most visits and when.
  • Bot and scraper detection: Unusual user agents hitting hundreds of URLs in seconds are easy to spot.
  • SEO auditing: Watching how search engine crawlers interact with your site.
  • Debugging broken links: A spike in 404s for a specific URL usually means something changed unexpectedly.
  • Feeding analytics pipelines: Many self-hosted analytics tools (Matomo, AWStats, GoAccess) parse raw access logs directly.

Troubleshooting Tips

  • If the access log isn’t being written at all, double-check that mod_log_config is enabled: apachectl -M | grep log_config.
  • If you’re missing referrer or user-agent data, confirm you’re using combined and not common format.
  • If logs show the load balancer’s IP instead of the real client IP, you need mod_remoteip or to log %{X-Forwarded-For}i instead of %h.
  • If log entries seem duplicated, check you don’t have multiple CustomLog directives pointing at the same file from both the global config and a virtual host.

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

  • Access logs often contain personal data (IP addresses, sometimes query strings with sensitive parameters) — treat them as sensitive data under privacy regulations like GDPR.
  • Set restrictive file permissions (640 is a solid default) so only authorized users/groups can read them.
  • Avoid logging full query strings if they might contain tokens or personal data — consider stripping sensitive parameters with a custom LogFormat.
  • If shipping logs off-server, always use encrypted transport.

Performance Optimization

  • Use BufferedLogs On for high-traffic servers to batch writes and reduce disk I/O.
  • Avoid excessive custom fields in your log format — every additional field adds a small amount of overhead per request.
  • Rotate and compress logs aggressively on high-traffic servers to prevent I/O contention with the actual application.

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:

  • CustomLog and LogFormat control what gets logged and how it’s structured.
  • Use combined format for referrer and user-agent data.
  • Set up per-virtual-host logs on multi-site servers.
  • Rotate logs regularly, and be mindful of the sensitive data they contain.
  • Tools like GoAccess turn raw logs into genuinely useful insights fast.

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

References

Total
1
Shares

Leave a Reply

Previous Post
How to install and configure third-party Apache modules

How to Install and Configure Third-Party Apache Modules

Next Post
How to set up Apache error logs

How to Set Up Apache Error Logs

Related Posts