Configuring the Apache Error and Access Logs

By 

•

Updated on

•

13 min read

Configuring Apache Logging

When a page on your site returns a 500 error or a visitor reports that something is broken, the first place to look is the web server logs. When managing Apache web servers, checking the log files is one of the most frequent tasks you will perform.

Knowing how to configure and read the logs is very useful when troubleshooting server or application issues, as they provide detailed debugging information.

Apache writes records of its events in two types of logs: access logs and error logs. Access logs include information about client requests, and error logs include information about server and application issues.

This article describes how to configure and read the Apache access and error logs.

Configuring the Access Log

Apache web server generates a new event in the access log for all processed requests. Each event record contains a timestamp and includes various information about the client and the requested resource. Access logs show the visitor IP address, the requested resource, the response status, and much more.

The CustomLog directive defines the location of the log file and the format of the logged messages.

The most basic syntax of the CustomLog directive is as follows:

apache
CustomLog log_file format [condition]

The log_file can be either relative to the ServerRoot or a full path to the log file. The log messages can also be piped to another program using the pipe symbol |.

The second argument, format specifies the format of the log messages. It can be either a nickname defined by the LogFormat directive or an explicit format definition.

Here is a format defined with LogFormat and used by its nickname:

apache
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
CustomLog logs/access.log combined

The same format can also be written directly in the CustomLog directive:

apache
CustomLog logs/access.log "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\""

To avoid repeating the same code multiple times, prefer defining the LogFormat directive and using it as a nickname in the CustomLog directive.

The third argument [condition] is optional and allows you to write log messages only when a specific condition is met. Usually, this is done using environment variables. The condition can be negated with the ! symbol.

For example, if you want to exclude requests to CSS files from being written to the log file, you would use the following:

apache
SetEnvIf Request_URI \.css$ css-file
CustomLog logs/access.log combined env=!css-file

While the access log provides very useful information, it takes disk space and may affect the server performance. If your server is low on resources and you have a busy website, you might want to disable the access log.

To do that, simply comment out or remove the CustomLog directive from the main server configuration and virtual host sections.

If you want to turn off the access log only for one virtual host, set the first argument of the CustomLog directive to /dev/null:

apache
CustomLog /dev/null combined

Log Format Strings

Ubuntu and Debian define several formats in /etc/apache2/apache2.conf. The default virtual host uses combined:

apache
LogFormat "%v:%p %h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" vhost_combined
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%h %l %u %t \"%r\" %>s %O" common

The common format follows the Common Log Format field order, but uses %O instead of %b, so its byte count includes response headers. The combined format adds the referrer and the user agent. The vhost_combined format prefixes each line with the virtual host name and canonical port, which is handy when several sites write to the same file.

The fields of the combined format are explained in the reading section below. Other commonly used format strings are:

  • %b - Size of the response in bytes, excluding headers. Shows - when no bytes were sent.
  • %D - Time taken to serve the request, in microseconds.
  • %T - Time taken to serve the request, in seconds.
  • %v - The ServerName of the virtual host that served the request.
  • %p - The canonical port of the server that handled the request. Use %{local}p for the port the request was received on.
  • %U - The URL path requested, without the query string.
  • %q - The query string, prefixed with ?. Empty when there is no query string.
  • %{X-Forwarded-For}i - The client IP address set by a reverse proxy or load balancer.

For a complete list of all format strings and modifiers, check the “mod_log_config” module documentation.

To add the request time (%D) to the combined format on Ubuntu or Debian, define a new format in /etc/apache2/apache2.conf:

apache
LogFormat "%h %l %u %t \"%r\" %>s %O %D \"%{Referer}i\" \"%{User-Agent}i\"" combined_time

Then replace the existing CustomLog line in /etc/apache2/sites-available/000-default.conf with this one:

apache
CustomLog ${APACHE_LOG_DIR}/access.log combined_time

Replace the directive rather than adding a second one, which would write duplicate entries to the same file. After testing the configuration and reloading Apache, each record includes the number of microseconds it took to serve the request:

output
::1 - - [01/Oct/2026:19:24:19 +0000] "GET /about HTTP/1.1" 404 472 37 "-" "curl/8.18.0"

In the output above, 37 is the %D value. Slow requests stand out once you sort the log by this field.

Configuring the Error Log

Apache writes messages about the application and general server errors in the error log file. If you are experiencing errors in your web application, the error log is the first place to start for troubleshooting issues.

The ErrorLog directive defines the location of the error log. It takes the following form:

apache
ErrorLog log_file

If the path to the log_file is not absolute, then it is set as relative to the ServerRoot. The error messages can also be piped to another program using the pipe symbol |.

The LogLevel directive sets the level of logging. Below are levels listed by their severity (from low to high):

  • trace1 - trace8 - Trace messages.
  • debug - Debugging messages.
  • info - Informational messages.
  • notice - Notices.
  • warn - Warnings.
  • error - Errors while processing a request.
  • crit - Critical issues. Requires a prompt action.
  • alert - Alerts. Action must be taken immediately.
  • emerg - Emergency situation. The system is in an unusable state.

Each log level includes the higher levels. For example, if you set the log level to warn, Apache also writes the error, crit, alert, and emerg messages.

When the LogLevel directive is not specified, it defaults to warn. This is a good value for production servers. Setting it higher, to crit for example, hides regular request errors that you will want to see.

When you are troubleshooting a problem, raise the level to info or debug. Set it back to warn once you are done, as the lower levels write a lot of messages and the error log grows quickly.

The ErrorLogFormat directive specifies the format of the error log. On most Linux distributions, the Apache server is using the default format, which is sufficient for most cases.

Per-Module Log Level

LogLevel also accepts a level for individual modules. This gives you detailed output from one module without flooding the log with messages from the rest of the server.

For example, to debug mod_rewrite rules and keep everything else at warn, you would use the following:

apache
LogLevel warn rewrite:trace3

Each rewrite step is now written to the error log, tagged with the module name and level:

output
[Thu Oct 01 19:24:28.255311 2026] [rewrite:trace3] [pid 11009:tid 11016] mod_rewrite.c(4401): [client ::1:51252] ::1 - - [localhost/sid#eec6572d8418][rid#eec6573760a0/initial] applying pattern '^/old$' to uri '/old'

The record shows the rule pattern Apache tried and the URI it was applied to. Remove the module level when you are done. Trace output adds several lines to every request.

Virtual Hosts and Global Logging

The logging behavior and the location of the files can be set either globally or on a per-virtual-host basis.

When the CustomLog or ErrorLog directives are set in the main server context, the server writes all log messages to the same access and error log files. Otherwise, if the directives are placed inside a <VirtualHost> block, only the log messages for that virtual host are written to the specified file.

The log directive set in the <VirtualHost> block overrides the one set in the server context.

Virtual hosts without CustomLog or ErrorLog directives will have their log messages written to the global server logs.

For better readability, it is recommended to set separate access and error log files for each virtual host. Here is an example:

apache
<VirtualHost *:80>
     ServerName example.com
     ServerAlias www.example.com
     ServerAdmin webmaster@example.com
     DocumentRoot /var/www/example.com/public
     LogLevel warn
     ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
     CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>

On Ubuntu and Debian, ${APACHE_LOG_DIR} expands to /var/log/apache2. On other distributions, use a full path such as /var/log/httpd/example.com-error.log. If you use a custom directory, create it first. Apache does not create missing log directories and will fail to start without them.

After changing a logging directive, test the configuration and reload Apache for the change to take effect.

Location of the Log Files

The default log file location depends on the Linux distribution:

DistributionAccess logError log
Ubuntu, Debian/var/log/apache2/access.log/var/log/apache2/error.log
AlmaLinux, Rocky Linux, Fedora/var/log/httpd/access_log/var/log/httpd/error_log
Apache compiled from source/usr/local/apache2/logs/access_log/usr/local/apache2/logs/error_log

The log files are readable only by privileged users, so use sudo to open them or add your user to the adm group on Ubuntu and Debian.

Finding a Custom Log Location

If the logs are not in the default location, the configuration points them somewhere else. On Ubuntu and Debian, apache2ctl -S prints the path of the main error log:

Terminal
sudo apache2ctl -S

The relevant lines look like this:

output
ServerRoot: "/etc/apache2"
Main DocumentRoot: "/var/www/html"
Main ErrorLog: "/var/log/apache2/error.log"

On RHEL-based systems, run sudo apachectl -S instead.

The command does not list access logs. To find every CustomLog and ErrorLog directive, search the configuration directory with grep :

Terminal
sudo grep -RE "CustomLog|ErrorLog" /etc/apache2/

On RHEL-based systems, search /etc/httpd/ instead. On Ubuntu and Debian, most paths use the ${APACHE_LOG_DIR} variable. It is set in /etc/apache2/envvars and points to /var/log/apache2.

Apache and journalctl

With the default file-based logging on these distributions, journalctl -u shows service messages from Apache startup, shutdown, and reloads, including some configuration warnings. Access and error records stay in Apache’s log files. If ErrorLog is configured to send messages to syslog or standard error, those messages can also appear in the journal.

On Ubuntu and Debian, the service is named apache2:

Terminal
sudo journalctl -u apache2 --no-pager

On AlmaLinux, Rocky Linux, Fedora, and other RHEL-based systems, the service is named httpd:

Terminal
sudo journalctl -u httpd --no-pager

For more filtering options, see the journalctl command guide.

Reading and Understanding the Apache Log Files

The log files can be opened and parsed using standard commands like cat , less , grep , cut , awk , and so on.

Here is an example record from the access log file that uses the Debian combined log format:

output
192.168.33.1 - - [08/Jan/2020:21:39:03 +0000] "GET / HTTP/1.1" 200 6169 "-" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36"

Here is what each field of the record means:

  • %h - 192.168.33.1 - The Hostname or the IP address of the client making the request.
  • %l - - - Remote logname. When the user name is not set, this field shows -.
  • %u - - - If the request is authenticated, the remote user name is shown.
  • %t - [08/Jan/2020:21:39:03 +0000] - Local server time.
  • \"%r\" - "GET / HTTP/1.1" - First line of request. The request type, path, and protocol.
  • %>s - 200 - The final server response code. If the > symbol is not used and the request has been internally redirected, it will show the status of the original request.
  • %O - 6169 - The size of the server response in bytes, including headers.
  • \"%{Referer}i\" - "-" - The URL of the referral.
  • \"%{User-Agent}i\" - Mozilla/5.0 ... - The user agent of the client (web browser).

Use the tail command to watch the log file in real time:

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

To filter the access log by HTTP status code, use awk. The status code is the ninth field in the combined format:

Terminal
sudo awk '$9 == 404' /var/log/apache2/access.log

A plain grep " 404 " also matches responses that happen to be 404 bytes in size. Comparing the ninth field avoids these false matches.

To see how many requests returned each status code, count the ninth field:

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

The first column is the number of requests, and the second is the status code:

output
      4 404
      2 200

The same approach works for the first field. The following command lists the ten client IP addresses that sent the most requests:

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

To filter by a specific client IP address:

Terminal
sudo grep "192.168.1.1" /var/log/apache2/access.log

Reading the Error Log

Here is an example record from the error log:

output
[Thu Oct 01 19:09:53.175727 2026] [authz_core:error] [pid 10825:tid 10837] [client 192.168.33.1:48796] AH01630: client denied by server configuration: /var/www/html/private/

Here is what each field means:

  • [Thu Oct 01 19:09:53.175727 2026] - Timestamp of the event.
  • [authz_core:error] - The Apache module that generated the message and the log level.
  • [pid 10825:tid 10837] - The process ID and thread ID of the Apache worker.
  • [client 192.168.33.1:48796] - The IP address and port of the client.
  • AH01630: client denied by server configuration: ... - The error message and affected path.

The AH code identifies the message, and searching for it is often the quickest way to find what the error means.

To watch the error log in real time:

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

Log Rotation

Apache log files grow continuously. On most Linux distributions, logrotate handles log rotation automatically.

On Ubuntu and Debian, the configuration is in /etc/logrotate.d/apache2. It rotates the logs daily, keeps 14 old files, and compresses them. The rotated files are named access.log.1, access.log.2.gz, and so on.

On RHEL-based systems, the configuration is in /etc/logrotate.d/httpd. It does not set its own schedule, so the defaults from /etc/logrotate.conf apply: weekly rotation with four old files kept.

To search the rotated logs together with the current one, use zgrep. It reads both compressed and plain files:

Terminal
sudo zgrep "192.168.1.1" /var/log/apache2/access.log*

Troubleshooting

Apache fails to start after changing a log path
Apache does not create missing log directories. If the directory in an ErrorLog or CustomLog directive does not exist, the configuration check fails with an error like AH02291: Cannot access directory '/var/www/example.com/logs/' for error log of vhost. Create the directory, then run sudo apachectl configtest before reloading Apache.

Permission denied when reading the logs
On Ubuntu and Debian, /var/log/apache2 is owned by root and the adm group, and regular users cannot open it. Prefix the command with sudo, or add your user to the adm group with sudo usermod -aG adm $USER and log in again. On RHEL-based systems, /var/log/httpd is readable only by root.

Quick Reference

TaskCommand or directive
View access logsudo tail -f /var/log/apache2/access.log
View error logsudo tail -f /var/log/apache2/error.log
Show the main error log pathsudo apache2ctl -S
Find all log directivessudo grep -RE "CustomLog|ErrorLog" /etc/apache2/
View service start-up messagessudo journalctl -u apache2 or sudo journalctl -u httpd
Filter by status codesudo awk '$9 == 404' /var/log/apache2/access.log
Count requests by status codesudo awk '{print $9}' /var/log/apache2/access.log | sort | uniq -c | sort -rn
Filter by IP addresssudo grep "1.2.3.4" /var/log/apache2/access.log
Set access log locationCustomLog /path/to/access.log combined
Set error log locationErrorLog /path/to/error.log
Set log levelLogLevel warn
Debug a single moduleLogLevel warn rewrite:trace3
Disable access logCustomLog /dev/null combined

Conclusion

Apache has a flexible logging system that lets you customize formats, split logs per virtual host, and filter output. After changing any logging directive, test the configuration and reload the server as shown in our guide on how to start, stop, and restart Apache .

Tags

Linuxize Weekly Newsletter

A quick weekly roundup of new tutorials, news, and tips.

About the authors

Dejan Panovski

Dejan Panovski

Dejan Panovski is the founder of Linuxize, an RHCSA-certified Linux system administrator and DevOps engineer based in Skopje, Macedonia. Author of 1000+ Linux tutorials with 20+ years of experience turning complex Linux tasks into clear, reliable guides.

View author page