Apache access and error logs can grow quickly on busy sites, and unchecked growth turns a useful audit trail into a disk-space problem. A working rotation policy keeps current logs writable, preserves enough history for troubleshooting, and compresses older copies before they crowd out the rest of the system.
On Linux systems, logrotate usually handles this by renaming the active log, creating a fresh file, and compressing older rotations on a schedule or when a size threshold is reached. Apache keeps the old file descriptor open until it gets a reload or graceful restart, so the rotation policy must include a postrotate action that tells the server to reopen the log files.
The steps below use the current Debian and Ubuntu package layout with /etc/logrotate.d/apache2, /var/log/apache2, and the apache2 service name. If your virtual hosts write to custom paths, add those paths to a separate stanza instead of relying on the default wildcard alone. Avoid copytruncate on busy servers because it can lose or duplicate log lines, and avoid adding file-based rotation when the logs already use piped rotatelogs.
$ sudo grep --recursive --line-number --extended-regexp '^[[:space:]]*(ErrorLog|CustomLog)[[:space:]]' /etc/apache2/ 2>/dev/null | head -n 12
/etc/apache2/conf-available/other-vhosts-access-log.conf:2:CustomLog ${APACHE_LOG_DIR}/other_vhosts_access.log vhost_combined
/etc/apache2/apache2.conf:134:ErrorLog ${APACHE_LOG_DIR}/error.log
/etc/apache2/sites-available/000-default.conf:20: ErrorLog ${APACHE_LOG_DIR}/error.log
/etc/apache2/sites-available/000-default.conf:21: CustomLog ${APACHE_LOG_DIR}/access.log combined
/etc/apache2/sites-available/default-ssl.conf:12: ErrorLog ${APACHE_LOG_DIR}/error.log
/etc/apache2/sites-available/default-ssl.conf:13: CustomLog ${APACHE_LOG_DIR}/access.log combined
##### snipped #####
On Red Hat-family systems, check /etc/httpd instead of /etc/apache2/.
$ sudo bash -c 'source /etc/apache2/envvars && printf "%s\n" "$APACHE_LOG_DIR"' /var/log/apache2
Skip this step when your ErrorLog and CustomLog directives already point to absolute paths.
$ sudo ls -lh /var/log/apache2/ | head -n 20 total 8.0K -rw-r----- 1 root adm 86 Apr 8 04:20 access.log -rw-r----- 1 root adm 279 Apr 8 04:20 error.log -rw-r----- 1 root adm 0 Apr 8 04:20 other_vhosts_access.log
$ sudo sed -n '1,200p' /etc/logrotate.d/apache2
/var/log/apache2/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 640 root adm
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then
run-parts /etc/logrotate.d/httpd-prerotate
fi
endscript
postrotate
if pgrep -f ^/usr/sbin/apache2 > /dev/null; then
invoke-rc.d apache2 reload 2>&1 | logger -t apache2.logrotate
fi
endscript
}
A postrotate reload or graceful reopen is required because Apache otherwise keeps writing to the renamed log file after rotation.
/var/log/apache2/*.log {
daily
missingok
rotate 14
compress
delaycompress
notifempty
create 640 root adm
sharedscripts
prerotate
if [ -d /etc/logrotate.d/httpd-prerotate ]; then
run-parts /etc/logrotate.d/httpd-prerotate
fi
endscript
postrotate
if pgrep -f ^/usr/sbin/apache2 > /dev/null; then
invoke-rc.d apache2 reload 2>&1 | logger -t apache2.logrotate
fi
endscript
}
Add maxsize 100M when a daily run is not enough, or adjust rotate 14 to keep more or fewer archived copies.
/srv/www/example.com/logs/*.log {
weekly
rotate 8
compress
delaycompress
missingok
notifempty
create 640 root adm
sharedscripts
postrotate
/usr/sbin/apachectl graceful >/dev/null 2>&1 || true
endscript
}
Use the narrowest path that matches only the Apache logs for that site.
If logrotate refuses to rotate a directory owned by an application user, add su <user> <group> that matches that directory and the ownership you expect on new log files.
$ sudo logrotate --debug /etc/logrotate.d/apache2 warning: logrotate in debug mode does nothing except printing debug messages! Consider using verbose mode (-v) instead if this is not what you want. reading config file /etc/logrotate.d/apache2 ##### snipped ##### rotating pattern: /var/log/apache2/*.log after 1 days (14 rotations) empty log files are not rotated, old logs are removed
Use the custom policy file path instead when you created a separate stanza for a non-default log location.
$ sudo logrotate --force /etc/logrotate.d/apache2
Forced rotation renames the files immediately. Run it when brief log churn or a manual reload will not interfere with active incident work.
$ sudo apachectl graceful
The current Debian and Ubuntu package already reloads apache2 from the postrotate block, but a manual graceful reopen is a safe validation step after custom changes. Use sudo systemctl reload httpd or sudo apachectl graceful on Red Hat-family systems.
$ sudo ls -lh /var/log/apache2/access.log* /var/log/apache2/error.log* -rw-r----- 1 root adm 86 Apr 8 04:21 /var/log/apache2/access.log -rw-r----- 1 root adm 172 Apr 8 04:21 /var/log/apache2/access.log.1 -rw-r----- 1 root adm 279 Apr 8 04:21 /var/log/apache2/error.log -rw-r----- 1 root adm 586 Apr 8 04:21 /var/log/apache2/error.log.1
With delaycompress enabled, the newest rotated copy stays uncompressed until the next rotation cycle.
$ curl -I -sS http://127.0.0.1/ HTTP/1.1 200 OK Date: Wed, 08 Apr 2026 04:21:32 GMT Server: Apache/2.4.58 (Ubuntu) Last-Modified: Wed, 08 Apr 2026 04:20:03 GMT ETag: "29af-64eeb36c9d984" Accept-Ranges: bytes Content-Length: 10671 Vary: Accept-Encoding
Use the site hostname or a matching Host header when 127.0.0.1 does not hit the virtual host you want to validate.
$ sudo tail -n 3 /var/log/apache2/access.log 127.0.0.1 - - [08/Apr/2026:04:21:32 +0000] "HEAD / HTTP/1.1" 200 255 "-" "curl/8.5.0"
If new lines keep landing in access.log.1 after the rotation, the postrotate action did not reopen the files. Fix the reload or graceful command and test again.