Apache · macOS M4

Apache Access
Logs

Everything you need to find, read, filter, and understand who is accessing your Apache web server — covering both the built-in macOS Apache and Homebrew Apache.

Built-in Apache Homebrew Apache MacBook Air M4
// A single access log entry — decoded
192.168.1.42 - frank [07/May/2026:14:23:01 -0500] "GET /index.html HTTP/1.1" 200 4823 "https://google.com" "Mozilla/5.0 (Macintosh; ...)"
Client IP
Who made the request
Auth User
Username if HTTP auth used (- = none)
Timestamp
Date, time & timezone
Request
Method, path, HTTP version
Status Code
HTTP response code
Bytes Sent
Response size in bytes
Referrer
Where visitor came from
User Agent
Browser / device info
01

Log File Locations

The location of the access log depends on whether you are using the macOS built-in Apache or Homebrew's Apache.

Built-in macOS Apache

Access log:

/private/var/log/apache2/access_log

Error log:

/private/var/log/apache2/error_log

Symlink also works:

/var/log/apache2/access_log

Homebrew Apache

Access log:

/opt/homebrew/var/log/httpd/access_log

Error log:

/opt/homebrew/var/log/httpd/error_log

Virtual host logs are defined per-vhost in config.

Confirm the log path from your config

Terminal
# Built-in Apache — grep the config for CustomLog
grep -i "CustomLog" /etc/apache2/httpd.conf

# Homebrew Apache
grep -i "CustomLog" /opt/homebrew/etc/httpd/httpd.conf
02

Reading the Access Log

These are the essential Terminal commands for viewing log content in real time or reviewing history.

Watch live traffic as it happens

Terminal — live tailing
# Built-in Apache — watch every hit as it comes in
tail -f /private/var/log/apache2/access_log

# Homebrew Apache
tail -f /opt/homebrew/var/log/httpd/access_log

# Show last 100 lines then follow
tail -n 100 -f /private/var/log/apache2/access_log

View recent entries without following

Terminal
# Last 50 lines
tail -n 50 /private/var/log/apache2/access_log

# First 20 lines (oldest entries)
head -n 20 /private/var/log/apache2/access_log

# Page through the whole file
less /private/var/log/apache2/access_log
# In less: press G to jump to end, g to go to start, q to quit

Count total number of requests

Terminal
wc -l /private/var/log/apache2/access_log
# Each line = one request
03

Filtering & Searching the Log

Use grep and awk to zero in on specific visitors, pages, errors, or time ranges without any extra tools.

Find requests from a specific IP address

Terminal — filter by IP
grep "192.168.1.42" /private/var/log/apache2/access_log

# Count how many times that IP visited
grep -c "192.168.1.42" /private/var/log/apache2/access_log

Find all requests to a specific page or path

Terminal — filter by URL path
grep "GET /contact" /private/var/log/apache2/access_log

# Any request containing /admin
grep "/admin" /private/var/log/apache2/access_log

Find all 404 errors (pages not found)

Terminal — filter by status code
grep " 404 " /private/var/log/apache2/access_log

# All 5xx server errors
grep -E " 5[0-9]{2} " /private/var/log/apache2/access_log

# All 4xx client errors
grep -E " 4[0-9]{2} " /private/var/log/apache2/access_log

Filter by date or time window

Terminal — filter by date
# All entries on May 7th 2026
grep "07/May/2026" /private/var/log/apache2/access_log

# Between 2pm and 3pm
grep "07/May/2026:14:" /private/var/log/apache2/access_log

# This month
grep "May/2026" /private/var/log/apache2/access_log

Filter by browser or device (User Agent)

Terminal — filter by User Agent
# All requests from iPhones
grep -i "iphone" /private/var/log/apache2/access_log

# All requests from bots/crawlers
grep -iE "bot|crawler|spider" /private/var/log/apache2/access_log

# Exclude bots — show only real visitors
grep -ivE "bot|crawler|spider" /private/var/log/apache2/access_log

Combine filters (IP + status code)

Terminal — chaining greps
# All 404 errors from a specific IP
grep "192.168.1.42" /private/var/log/apache2/access_log | grep " 404 "

# Errors on a specific date
grep "07/May/2026" /private/var/log/apache2/access_log | grep -E " [45][0-9]{2} "
04

Traffic Analysis with awk

These one-liners extract useful intelligence directly from the log file — no extra software needed.

Top 10 visitor IP addresses

Terminal — most active IPs
awk '{print $1}' /private/var/log/apache2/access_log \
  | sort | uniq -c | sort -rn | head -10

Top 10 most-requested pages

Terminal — most visited URLs
awk '{print $7}' /private/var/log/apache2/access_log \
  | sort | uniq -c | sort -rn | head -10

Count requests by HTTP status code

Terminal — breakdown by status
awk '{print $9}' /private/var/log/apache2/access_log \
  | sort | uniq -c | sort -rn

Top referrers (where visitors came from)

Terminal — top referrers
awk '{print $11}' /private/var/log/apache2/access_log \
  | sort | uniq -c | sort -rn | head -10

Unique visitor IPs (total distinct visitors)

Terminal
awk '{print $1}' /private/var/log/apache2/access_log \
  | sort -u | wc -l

Total bandwidth served (bytes)

Terminal
awk '{sum += $10} END {print sum " bytes / " sum/1024/1024 " MB"}' \
  /private/var/log/apache2/access_log

Requests per hour (traffic pattern)

Terminal
awk '{print substr($4,14,2)":00"}' /private/var/log/apache2/access_log \
  | sort | uniq -c | sort -k2
05

HTTP Status Codes in Logs

The status code (field 9 in the log) tells you what happened with each request. Here are the ones you'll see most often:

200
OK
Request succeeded. Page delivered normally.
201
Created
Resource created (POST/PUT success).
204
No Content
Success but no body returned.
301
Moved Permanently
URL has permanently moved. Browser updates bookmark.
302
Found / Redirect
Temporary redirect to another URL.
304
Not Modified
Browser used its cached version.
400
Bad Request
Malformed request from client.
401
Unauthorized
Authentication required.
403
Forbidden
Server understood but refused access.
404
Not Found
Page or file does not exist.
500
Server Error
Apache/script crashed. Check error_log.
503
Unavailable
Server overloaded or in maintenance.
06

Configuring Log Format & Location

You can control exactly what gets logged, in what format, and where, by editing your Apache config file.

Make sure logging is enabled (httpd.conf)

Open your config and confirm these two module lines are uncommented:

httpd.conf — required modules
# Built-in Apache paths:
LoadModule log_config_module  libexec/apache2/mod_log_config.so
LoadModule logio_module       libexec/apache2/mod_logio.so

# Homebrew Apache paths:
LoadModule log_config_module  lib/httpd/modules/mod_log_config.so
LoadModule logio_module       lib/httpd/modules/mod_logio.so

Log format definitions

The combined format includes IP, timestamp, request, status, size, referrer, and user agent — this is the default and the most useful.

httpd.conf — built-in log formats
# "combined" — most informative (recommended)
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined

# "common" — minimal (no referrer or user agent)
LogFormat "%h %l %u %t \"%r\" %>s %b" common

# Set which format to use and where to write:
CustomLog "/private/var/log/apache2/access_log" combined

Custom log format with response time

Add %D (microseconds) to log how long each request took — useful for performance debugging:

httpd.conf — add to your config
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %D" combined_time
CustomLog "/private/var/log/apache2/access_log" combined_time

Separate log per Virtual Host

If you have virtual hosts, give each one its own log file inside the <VirtualHost> block:

httpd-vhosts.conf
<VirtualHost *:80>
    ServerName  myapp.test
    DocumentRoot "/Users/YOUR_USERNAME/Sites/myapp"

    # Separate access and error log per site
    CustomLog  "/private/var/log/apache2/myapp-access.log"  combined
    ErrorLog   "/private/var/log/apache2/myapp-error.log"
</VirtualHost>

LogFormat field reference

TokenWhat it logs
%hClient IP address (or hostname if HostnameLookups On)
%lRFC 1413 identity (almost always -)
%uAuthenticated username (- if no auth)
%tTime the request was received
%rFirst line of request (method + path + protocol)
%>sFinal HTTP status code sent to client
%bBytes sent (excluding headers), - if zero
%{Referer}iReferer header — where the visitor came from
%{User-Agent}iUser-Agent header — browser and OS string
%DRequest time in microseconds (performance)
%TRequest time in seconds
%vServerName of virtual host serving the request

Apply changes

Terminal
# Test config first
sudo apachectl configtest

# Then graceful restart
sudo apachectl graceful
💡

After changing the log format, only new requests will use the new format. Existing log entries keep the old format. Consider rotating the log after a format change to keep things consistent.

07

Log Rotation & Management

Access logs grow without limit unless you rotate them. macOS uses newsyslog to rotate system logs automatically — Apache's built-in logs are included by default.

Check current log size

Terminal
# Check size of Apache log files
ls -lh /private/var/log/apache2/

# Homebrew
ls -lh /opt/homebrew/var/log/httpd/

Manually rotate (truncate) the log

Terminal — manual rotation
# Archive the current log with a timestamp
sudo mv /private/var/log/apache2/access_log \
        /private/var/log/apache2/access_log.$(date +%Y%m%d)

# Graceful restart so Apache creates a fresh log file
sudo apachectl graceful
⚠️

Never just delete an active log file while Apache is running — it will keep writing to the deleted file descriptor. Always move or rename it, then do a graceful restart so Apache opens a new file.

Disable logging entirely (not recommended)

If you want to turn off access logging completely, comment out the CustomLog line in httpd.conf:

httpd.conf
# Add a # to disable:
#CustomLog "/private/var/log/apache2/access_log" combined