Configuring the Apache Error and Access Logs

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:
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:
LogFormat "%h %l %u %t \"%r\" %>s %O \"%{Referer}i\" \"%{User-Agent}i\"" combined
CustomLog logs/access.log combinedThe same format can also be written directly in the CustomLog directive:
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:
SetEnvIf Request_URI \.css$ css-file
CustomLog logs/access.log combined env=!css-fileWhile 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:
CustomLog /dev/null combinedLog Format Strings
Ubuntu and Debian define several formats in /etc/apache2/apache2.conf. The default virtual host uses combined:
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" commonThe 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- TheServerNameof the virtual host that served the request.%p- The canonical port of the server that handled the request. Use%{local}pfor 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:
LogFormat "%h %l %u %t \"%r\" %>s %O %D \"%{Referer}i\" \"%{User-Agent}i\"" combined_timeThen replace the existing CustomLog line in /etc/apache2/sites-available/000-default.conf with this one:
CustomLog ${APACHE_LOG_DIR}/access.log combined_timeReplace 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:
::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:
ErrorLog log_fileIf 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:
LogLevel warn rewrite:trace3Each rewrite step is now written to the error log, tagged with the module name and level:
[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:
<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:
| Distribution | Access log | Error 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:
sudo apache2ctl -SThe relevant lines look like this:
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
:
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:
sudo journalctl -u apache2 --no-pagerOn AlmaLinux, Rocky Linux, Fedora, and other RHEL-based systems, the service is named httpd:
sudo journalctl -u httpd --no-pagerFor 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:
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:
sudo tail -f /var/log/apache2/access.logTo filter the access log by HTTP status code, use awk. The status code is the ninth field in the combined format:
sudo awk '$9 == 404' /var/log/apache2/access.logA 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:
sudo awk '{print $9}' /var/log/apache2/access.log | sort | uniq -c | sort -rnThe first column is the number of requests, and the second is the status code:
4 404
2 200The same approach works for the first field. The following command lists the ten client IP addresses that sent the most requests:
sudo awk '{print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | headTo filter by a specific client IP address:
sudo grep "192.168.1.1" /var/log/apache2/access.logReading the Error Log
Here is an example record from the error log:
[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:
sudo tail -f /var/log/apache2/error.logLog 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:
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
| Task | Command or directive |
|---|---|
| View access log | sudo tail -f /var/log/apache2/access.log |
| View error log | sudo tail -f /var/log/apache2/error.log |
| Show the main error log path | sudo apache2ctl -S |
| Find all log directives | sudo grep -RE "CustomLog|ErrorLog" /etc/apache2/ |
| View service start-up messages | sudo journalctl -u apache2 or sudo journalctl -u httpd |
| Filter by status code | sudo awk '$9 == 404' /var/log/apache2/access.log |
| Count requests by status code | sudo awk '{print $9}' /var/log/apache2/access.log | sort | uniq -c | sort -rn |
| Filter by IP address | sudo grep "1.2.3.4" /var/log/apache2/access.log |
| Set access log location | CustomLog /path/to/access.log combined |
| Set error log location | ErrorLog /path/to/error.log |
| Set log level | LogLevel warn |
| Debug a single module | LogLevel warn rewrite:trace3 |
| Disable access log | CustomLog /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 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