Websites & apps

How to find and read Apache access and error logs

Find Apache's access and error logs on Ubuntu, Debian, AlmaLinux and Rocky Linux, set up per-site logs, and pull out errors and top visitors with a few shell commands.

BeginnerLinux server5 min read

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, grep and tail.

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.log for requests.
  • /var/log/apache2/error.log for errors.
  • /var/log/apache2/other_vhosts_access.log for virtual hosts that have no CustomLog of their own.

AlmaLinux and Rocky Linux

The package is httpd, and logs go to /var/log/httpd/:

  • /var/log/httpd/access_log for requests.
  • /var/log/httpd/error_log for errors.
  • ssl_access_log and ssl_error_log for the default HTTPS site from mod_ssl.

If you are not sure where a server writes its logs, search the configuration:

Bash
sudo grep -RiE "^\s*(ErrorLog|CustomLog)" /etc/apache2 /etc/httpd 2>/dev/null

Startup failures that happen before Apache opens its log files appear in the systemd journal instead:

Bash
sudo journalctl -u apache2 --since "1 hour ago"   # Ubuntu/Debian
sudo journalctl -u httpd --since "1 hour ago"     # AlmaLinux/Rocky

Understand 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:

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

A 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:

Apache
LogFormat "%h %t \"%r\" %>s %O %D \"%{User-Agent}i\"" timed

Tip: 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:

Apache
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:

Bash
sudo apachectl configtest
sudo systemctl reload apache2   # or: sudo systemctl reload httpd

Find errors fast

Watch the error log live while you reload the broken page:

Bash
sudo tail -f /var/log/apache2/error.log

Show only the most recent problems:

Bash
sudo grep -E "\[(error|crit|alert|emerg)\]|:error\]" /var/log/apache2/error.log | tail -n 30

Apache 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:

Bash
sudo awk '{print $9}' /var/log/apache2/access.log | sort | uniq -c | sort -rn

List the URLs that returned 404 most often:

Bash
sudo awk '$9 == 404 {print $7}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head

Find top visitors and busy pages

The ten IP addresses with the most requests:

Bash
sudo awk '{print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head

The most requested paths:

Bash
sudo awk '{print $7}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head

Requests to the WordPress login page or XML-RPC, a common sign of brute-force attempts:

Bash
sudo grep -E "wp-login.php|xmlrpc.php" /var/log/apache2/access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head

Older 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:

Bash
sudo logrotate -d /etc/logrotate.d/apache2

Warning: 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

  1. Load a page on your site in a browser.
  2. Run sudo tail -n 5 on the site's access log. You should see your own request with status 200.
  3. Request a page that does not exist and confirm a 404 line appears.

Troubleshooting

  • The per-site log file is empty: the request matched a different virtual host. Check ServerName and ServerAlias, and run sudo apachectl -S to see which vhost handles each name.
  • Permission denied when reading logs: logs are owned by root and the adm group on Ubuntu. Use sudo, or add your user to adm.
  • All visitors show the same IP: a proxy or CDN is in front. Enable mod_remoteip and trust the proxy's ranges.
  • The disk is filling with logs: check that logrotate runs (systemctl list-timers | grep logrotate) and lower the rotate count.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.