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:
- Open a terminal session with an account that can use sudo.
- 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.
Related: How to test Nginx configuration
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
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.
