How to configure log rotation for Nginx

Nginx access and error logs can grow quickly on busy sites, and unchecked growth turns a useful record into a disk-space problem. A working rotation policy keeps the active logs writable, preserves enough history for troubleshooting and security review, and compresses older copies before they crowd the rest of the filesystem.

On Linux systems, logrotate usually handles that by renaming the current log file, creating a new file, and compressing older rotations on a schedule. Because Nginx keeps log files open, the rotation workflow also has to tell the master process to reopen them. Current Nginx control documentation still uses the USR1 signal for that reopen step, and the current Debian and Ubuntu package wires it through the postrotate block in /etc/logrotate.d/nginx.

The steps below use the current Debian and Ubuntu package layout with /etc/logrotate.d/nginx, /var/log/nginx, and the nginx service name. If a site writes logs outside /var/log/nginx, add a separate stanza or widen the matched path deliberately instead of assuming the default wildcard is enough. Avoid copytruncate on busy servers, and if a container, chroot, or custom policy prevents the packaged postrotate helper from reopening logs, send USR1 to the Nginx master process and retest.

Steps to configure log rotation for Nginx:

  1. Open a terminal session with an account that can use sudo.
  2. List the active access_log and error_log directives so the real file paths are clear before editing the rotation policy.
    $ sudo grep --recursive --line-number --extended-regexp '^[[:space:]]*(access_log|error_log)[[:space:]]' /etc/nginx/nginx.conf /etc/nginx/conf.d /etc/nginx/sites-enabled 2>/dev/null
    /etc/nginx/nginx.conf:4:error_log /var/log/nginx/error.log;
    /etc/nginx/nginx.conf:40:	access_log /var/log/nginx/access.log;

    Update the logrotate path match if any server block writes to a different directory or filename pattern.

  3. Review the current Nginx logrotate policy.
    $ sudo sed -n '1,200p' /etc/logrotate.d/nginx
    /var/log/nginx/*.log {
    	daily
    	missingok
    	rotate 14
    	compress
    	delaycompress
    	notifempty
    	create 0640 www-data adm
    	sharedscripts
    	prerotate
    		if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
    			run-parts /etc/logrotate.d/httpd-prerotate; \
    		fi \
    	endscript
    	postrotate
    		invoke-rc.d nginx rotate >/dev/null 2>&1
    	endscript
    }

    The postrotate reopen action is required. Current logrotate documentation still defines sharedscripts as “run once for the whole stanza”, which avoids sending multiple reopen signals when both access and error logs rotate together.

  4. Edit the policy so the retention period, compression, ownership, and reopen action match the deployment.
    /var/log/nginx/*.log {
        daily
        missingok
        rotate 14
        compress
        delaycompress
        notifempty
        create 0640 www-data adm
        sharedscripts
        prerotate
            if [ -d /etc/logrotate.d/httpd-prerotate ]; then \
                run-parts /etc/logrotate.d/httpd-prerotate; \
            fi \
        endscript
        postrotate
            invoke-rc.d nginx rotate >/dev/null 2>&1
        endscript
    }

    Add maxsize 100M when daily rotation is not enough, or change rotate 14 to keep more or fewer archives.

    Keep the create owner and group aligned with the packaged service account on the target host. Common values are www-data adm on Debian and Ubuntu and nginx adm or nginx root on other distributions.

  5. Add a separate stanza for any site logs that live outside /var/log/nginx.
    /srv/www/example.com/logs/*.log {
        weekly
        rotate 8
        compress
        delaycompress
        missingok
        notifempty
        create 0640 www-data adm
        sharedscripts
        postrotate
            kill -USR1 "$(cat /run/nginx.pid)"
        endscript
    }

    Use the narrowest path that matches only the Nginx logs for that site.

    Avoid copytruncate on busy servers because log lines can be lost between the copy and truncate phases. Reopening with USR1 keeps the rotation explicit and matches the upstream Nginx control workflow.

  6. Run a dry run to validate the policy without changing files.
    $ sudo logrotate --debug /etc/logrotate.d/nginx
    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/nginx
    Reading state from file: /var/lib/logrotate/status
    state file /var/lib/logrotate/status does not exist
    Allocating hash table for state file, size 64 entries
    
    Handling 1 logs
    
    rotating pattern: /var/log/nginx/*.log  after 1 days (14 rotations)
    empty log files are not rotated, old logs are removed

    Use the custom policy path instead when validation is aimed at a separate stanza for non-default log locations.

  7. Force one rotation during a maintenance window to confirm the stanza behaves as expected.
    $ sudo logrotate --verbose --force /etc/logrotate.d/nginx
    reading config file /etc/logrotate.d/nginx
    Creating stub state file: /var/lib/logrotate/status
    acquired lock on state file /var/lib/logrotate/status
    ##### snipped #####
    rotating pattern: /var/log/nginx/*.log  forced from command line (14 rotations)
    ##### snipped #####
    renaming /var/log/nginx/access.log to /var/log/nginx/access.log.1
    creating new /var/log/nginx/access.log mode = 0640 uid = 33 gid = 4
    running postrotate script

    Forced rotation renames files immediately. Run it when a manual reopen or brief validation request will not interfere with active incident work.

  8. If the packaged postrotate helper is unavailable or blocked, reopen the logs directly from the Nginx master process.
    $ sudo kill -USR1 "$(cat /run/nginx.pid)"

    Current Debian and Ubuntu packages normally do this through invoke-rc.d nginx rotate. The direct signal is most useful in containers, chroots, or custom stanzas where the service wrapper is not managing the process.

  9. Confirm that the current log file and its first rotated copy both exist.
    $ sudo ls -lh /var/log/nginx/access.log* /var/log/nginx/error.log*
    -rw-r----- 1 www-data adm  0 Apr  9 13:08 /var/log/nginx/access.log
    -rw-r----- 1 www-data adm 84 Apr  9 13:08 /var/log/nginx/access.log.1
    -rw-r----- 1 www-data adm  0 Apr  9 13:08 /var/log/nginx/error.log

    With delaycompress enabled, the newest rotated copy stays uncompressed until the next rotation cycle.

  10. Send a local request so the active access log receives a fresh line.
    $ curl -I -sS http://127.0.0.1/
    HTTP/1.1 200 OK
    Server: nginx/1.24.0 (Ubuntu)
    Date: Thu, 09 Apr 2026 13:08:51 GMT
    Content-Type: text/html
    Content-Length: 615
    Last-Modified: Thu, 09 Apr 2026 13:08:46 GMT
    Connection: keep-alive
    ETag: "69d7a4de-267"
    Accept-Ranges: bytes

    Use the real site hostname or a matching Host header when 127.0.0.1 does not hit the virtual host being validated.

  11. Confirm that the fresh request is written to the current access log instead of the rotated file.
    $ sudo tail -n 1 /var/log/nginx/access.log
    127.0.0.1 - - [09/Apr/2026:13:13:25 +0000] "HEAD / HTTP/1.1" 200 0 "-" "curl/8.5.0"

    If new lines keep landing in access.log.1 or error.log.1 after a rotation, the reopen step did not reach the Nginx master process. Fix the postrotate command or send USR1 manually and test again.