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.
Steps to configure log rotation for Apache:
- Open a terminal session with an account that can use sudo.
- List the active ErrorLog and CustomLog directives so the real log paths are clear before changing rotation.
$ 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/.
- Resolve APACHE_LOG_DIR on Debian and Ubuntu if the configuration uses that variable.
$ 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.
- Record the current log files and their sizes before changing the policy.
$ 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
- Review the current Apache logrotate policy.
$ 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.
- Edit the Apache policy so the retention, compression, permissions, and reopen action match your environment.
/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.
- Add a separate stanza for any virtual host logs that live outside the default directory.
/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.
- Run a dry run to validate the policy without changing 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.
- Force one rotation during a maintenance window to confirm the stanza behaves as expected.
$ 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.
- Reopen the Apache log files after the test rotation if your policy does not already reload the service.
$ 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.
- Confirm that the current log file and its first rotated copy both exist.
$ 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.
- Send a local request so the new active access log receives a fresh line.
$ 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.
- Confirm that the fresh request is written to the current access log instead of the rotated file.
$ 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.
Mohd Shakir Zakaria is a cloud architect with deep roots in software development and open-source advocacy. Certified in AWS, Red Hat, VMware, ITIL, and Linux, he specializes in designing and managing robust cloud and on-premises infrastructures.
