How to configure log rotation for Apache

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:

  1. Open a terminal session with an account that can use sudo.
  2. 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/.

  3. 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.

  4. 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
  5. 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.

  6. 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.

  7. 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.

  8. 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.

  9. 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.

  10. 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.

  11. 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.

  12. 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.

  13. 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.