Introduction

Nginx log rotation uses the system's logrotate utility to manage growing log files. When misconfigured, rotation fails with permission denied errors, causing log files to grow unbounded until they fill the disk. The error typically appears in syslog:

bash
error: error running non-shared postrotate script for /var/log/nginx/*.log
chmod: changing permissions of '/var/log/nginx/access.log': Operation not permitted

This is especially common on systems with SELinux enabled or after manual log directory changes.

Symptoms

  • Logrotate reports "permission denied" or "Operation not permitted" errors
  • Log files grow beyond expected size (multi-gigabyte access.log)
  • Disk fills up with log data
  • Nginx continues writing to the old (rotated) log file
  • /var/log/messages or journalctl shows logrotate failures

Common Causes

  • Log directory ownership is root:root but Nginx worker process runs as www-data or nginx user
  • SELinux security context prevents logrotate from relabeling or truncating files
  • Postrotate script fails to signal Nginx to reopen log file descriptors
  • Custom log directory path does not inherit correct permissions
  • logrotate configuration references wrong user/group

Step-by-Step Fix

  1. 1.Fix log directory ownership and permissions:
  2. 2.```bash
  3. 3.sudo chown -R nginx:adm /var/log/nginx/
  4. 4.sudo chmod 750 /var/log/nginx/
  5. 5.sudo chmod 640 /var/log/nginx/*.log
  6. 6.`
  7. 7.The nginx user needs write access; the adm group allows log reading tools access.
  8. 8.Verify the logrotate configuration at /etc/logrotate.d/nginx:
  9. 9.`
  10. 10./var/log/nginx/*.log {
  11. 11.daily
  12. 12.missingok
  13. 13.rotate 14
  14. 14.compress
  15. 15.delaycompress
  16. 16.notifempty
  17. 17.create 0640 nginx adm
  18. 18.sharedscripts
  19. 19.postrotate
  20. 20.[ -f /var/run/nginx.pid ] && kill -USR1 $(cat /var/run/nginx.pid)
  21. 21.endscript
  22. 22.}
  23. 23.`
  24. 24.The create directive sets the correct ownership and permissions for new log files after rotation.
  25. 25.Handle SELinux contexts if applicable. Check current context:
  26. 26.```bash
  27. 27.ls -Z /var/log/nginx/
  28. 28.`
  29. 29.If the context is wrong, restore it:
  30. 30.```bash
  31. 31.sudo restorecon -Rv /var/log/nginx/
  32. 32.`
  33. 33.If a custom path is used, add a new SELinux policy:
  34. 34.```bash
  35. 35.sudo semanage fcontext -a -t httpd_log_t "/custom/log/path(/.*)?"
  36. 36.sudo restorecon -Rv /custom/log/path/
  37. 37.`
  38. 38.Test logrotate manually without actually rotating:
  39. 39.```bash
  40. 40.sudo logrotate -d /etc/logrotate.d/nginx
  41. 41.`
  42. 42.This shows what would happen without making changes. Look for errors in the output.
  43. 43.Force a rotation to verify the fix:
  44. 44.```bash
  45. 45.sudo logrotate -f /etc/logrotate.d/nginx
  46. 46.ls -la /var/log/nginx/
  47. 47.`
  48. 48.New log files should have the correct ownership (nginx:adm) and permissions (640).

Prevention

  • Include log directory permission setup in your server provisioning scripts (Ansible, Terraform, etc.)
  • Monitor log file sizes with an alerting threshold (e.g., alert when any log exceeds 500MB)
  • Test logrotate configuration after any manual changes to the log directory
  • Use copytruncate instead of create only if the application cannot handle USR1 signal (not recommended for Nginx)
  • Document any custom log paths and their required SELinux contexts in your runbook
  • Run logrotate -d as part of your post-deployment verification checklist