Apache writes two kinds of log: an access log with one line per request, and an error log with warnings, PHP errors and startup problems. When a site breaks or traffic spikes, these files tell you what happened. This guide shows where the logs live, how to give each site its own logs, and how to pull useful answers out of them from the command line.
Before you start
- A Linux VPS running Apache on Ubuntu 22.04/24.04, Debian 12, or AlmaLinux/Rocky Linux 8 or 9.
- SSH access as a user with
sudo. See Connect to your Linux VPS with SSH if you need help. - Basic comfort with
less,grepandtail.
Where Apache logs are stored
The location depends on the distribution, because the package names differ.
Ubuntu and Debian
The package is apache2, and logs go to /var/log/apache2/:
/var/log/apache2/access.logfor requests./var/log/apache2/error.logfor errors./var/log/apache2/other_vhosts_access.logfor virtual hosts that have noCustomLogof their own.
AlmaLinux and Rocky Linux
The package is httpd, and logs go to /var/log/httpd/:
/var/log/httpd/access_logfor requests./var/log/httpd/error_logfor errors.ssl_access_logandssl_error_logfor the default HTTPS site frommod_ssl.
If you are not sure where a server writes its logs, search the configuration:
sudo grep -RiE "^\s*(ErrorLog|CustomLog)" /etc/apache2 /etc/httpd 2>/dev/nullStartup failures that happen before Apache opens its log files appear in the systemd journal instead:
sudo journalctl -u apache2 --since "1 hour ago" # Ubuntu/Debian
sudo journalctl -u httpd --since "1 hour ago" # AlmaLinux/RockyUnderstand LogFormat and CustomLog
LogFormat defines what a line contains and gives it a nickname. CustomLog writes requests to a file using one of those nicknames. The stock combined format looks like this:
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combinedA matching log line reads:
198.51.100.20 - - [09/Nov/2026:10:14:02 +0000] "GET /blog/ HTTP/1.1" 200 5123 "https://example.com/" "Mozilla/5.0 ..."The fields are client IP, identity, user, time, request line, status code, bytes sent, referrer and user agent.
To record how long each request took, add %D (microseconds) to a custom format:
LogFormat "%h %t \"%r\" %>s %O %D \"%{User-Agent}i\"" timedTip: If your site sits behind Cloudflare or another proxy, %h shows the proxy's IP. Use mod_remoteip with the proxy's IP ranges so the real visitor IP is logged.
Give each site its own logs
Separate logs per site make troubleshooting much faster. Inside each <VirtualHost> block, add:
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined${APACHE_LOG_DIR} is defined on Ubuntu and Debian. On AlmaLinux and Rocky, use a full path such as /var/log/httpd/example.com-access.log. Test and reload:
sudo apachectl configtest
sudo systemctl reload apache2 # or: sudo systemctl reload httpdFind errors fast
Watch the error log live while you reload the broken page:
sudo tail -f /var/log/apache2/error.logShow only the most recent problems:
sudo grep -E "\[(error|crit|alert|emerg)\]|:error\]" /var/log/apache2/error.log | tail -n 30Apache 2.4 tags each line with a module and level, such as [php:error] or [core:error], plus an AH code. Search the AH code in Apache's documentation when the message is unclear.
Count error responses in the access log by status code:
sudo awk '{print $9}' /var/log/apache2/access.log | sort | uniq -c | sort -rnList the URLs that returned 404 most often:
sudo awk '$9 == 404 {print $7}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | headFind top visitors and busy pages
The ten IP addresses with the most requests:
sudo awk '{print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | headThe most requested paths:
sudo awk '{print $7}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | headRequests to the WordPress login page or XML-RPC, a common sign of brute-force attempts:
sudo grep -E "wp-login.php|xmlrpc.php" /var/log/apache2/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | headOlder logs are compressed. Read them with zcat or zgrep, for example sudo zgrep " 500 " /var/log/apache2/access.log.2.gz.
Log rotation
Logrotate rotates Apache logs so they do not fill the disk. The rules live in /etc/logrotate.d/apache2 on Ubuntu and Debian, and /etc/logrotate.d/httpd on AlmaLinux and Rocky. Per-site logs in the same directory are covered automatically because the rules use a wildcard.
To keep logs longer, raise the rotate value in that file. Check the rules without rotating anything:
sudo logrotate -d /etc/logrotate.d/apache2Warning: Never delete a log file Apache has open. The disk space is not freed until Apache reloads. Truncate it with sudo truncate -s 0 file instead.
Check it works
- Load a page on your site in a browser.
- Run
sudo tail -n 5on the site's access log. You should see your own request with status200. - Request a page that does not exist and confirm a
404line appears.
Troubleshooting
- The per-site log file is empty: the request matched a different virtual host. Check
ServerNameandServerAlias, and runsudo apachectl -Sto see which vhost handles each name. Permission deniedwhen reading logs: logs are owned by root and theadmgroup on Ubuntu. Usesudo, or add your user toadm.- All visitors show the same IP: a proxy or CDN is in front. Enable
mod_remoteipand trust the proxy's ranges. - The disk is filling with logs: check that logrotate runs (
systemctl list-timers | grep logrotate) and lower therotatecount.
Next steps
- Fix the errors you found: Fix 500 Internal Server Error on Apache.
- Learn where other services log: Linux log files and where to find them.
- Tune rotation further: Logrotate guide.
