Fix Django Log File PermissionError [Errno 13] Permission Denied

Fix PermissionError Errno 13 Permission denied for a Django log file such as /var/log/django.log: give the app user ownership, stop root-run commands from creating the file, handle log rotation, or log to stdout instead.

Problem statement

Gunicorn workers fail to boot, a management command crashes, or systemctl status shows the service restarting with this traceback:

ValueError: Unable to configure handler 'file'
...
PermissionError: [Errno 13] Permission denied: '/var/log/myapp/django.log'

Django configures logging while it loads settings. If a FileHandler cannot open its file for writing, the whole configuration fails, and so does the process. The usual cause is that the file or its directory is owned by a different user than the one running the app, very often because someone ran manage.py as root once and root created the log file.

Why logging blocks startupdictConfig runsat settings importFileHandler opensthe log fileWrong ownerErrno 13Process exitsbefore servingA log handler that cannot open its file stops Django from starting at all.
  1. Check who runs the app (ps -o user= -p <pid> or the User= line in the systemd unit) and who owns the file (ls -l).
  2. Create a directory owned by that user: sudo install -d -o deploy -g deploy -m 750 /var/log/myapp.
  3. Fix the existing file: sudo chown deploy:deploy /var/log/myapp/django.log.
  4. Run management commands as the app user (sudo -u deploy ...), never as root.
  5. Make log rotation recreate the file with the right owner, or log to stdout and let journald or Docker handle files.

Step-by-step solution

1. Compare the process user with the file owner

systemctl show -p User myapp.service
ls -ld /var/log/myapp
ls -l /var/log/myapp/

If the unit runs as deploy and the file is root root -rw-r--r--, root created it. The process needs write permission on the file and, to create a missing file, write and execute permission on the directory.

2. Give the app its own log directory

Do not write into /var/log directly; regular users cannot create files there. Create a dedicated directory:

sudo install -d -o deploy -g deploy -m 750 /var/log/myapp
sudo chown -R deploy:deploy /var/log/myapp

With systemd you can let it create the directory on every start, with the right owner:

[Service]
User=deploy
Group=deploy
LogsDirectory=myapp
LogsDirectoryMode=0750

LogsDirectory=myapp creates /var/log/myapp owned by the service user before the process starts.

what to checkWhat ls showsCause and fixfile owned by roota root-run command createdit; chown to app userdirectory is /var/log itselfusers cannot create filesthere; own subdirectorynew file root-owned dailylogrotate create line;set the app userpath inside containernon-root image user;log to stdout insteadThe writer needs permission on the file and on its directorydjango-deployment.com
Fix ownership once, then fix whatever keeps recreating the file with the wrong owner.

3. Stop root from recreating the file

Running sudo python manage.py migrate or a root cron job loads the same logging configuration. If the file is missing, root creates it, and the app user cannot write to it afterwards. Run commands as the app user:

sudo -u deploy /srv/app/.venv/bin/python /srv/app/manage.py migrate

Put cron jobs in the app user's crontab (sudo crontab -u deploy -e) rather than root's.

4. Make log rotation keep the owner

logrotate creates a new file after rotating. Without a create line matching the app user, or with su missing, the new file is owned by root:

/var/log/myapp/*.log {
    daily
    rotate 14
    compress
    delaycompress
    missingok
    notifempty
    su deploy deploy
    create 0640 deploy deploy
}

Python's FileHandler keeps the old file open after rotation, so either reload the app in a postrotate script or use copytruncate. See Rotate Django Logs on Linux for the full setup.

5. Prefer stdout under systemd and Docker

The simplest fix removes the file entirely. Log to the console and let the supervisor capture it:

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "handlers": {"console": {"class": "logging.StreamHandler"}},
    "root": {"handlers": ["console"], "level": "INFO"},
}

journald stores output from systemd services (journalctl -u myapp), and Docker stores container output with its logging driver. No file, no permissions, no rotation inside the app.

where logs should goFileHandler on disk✕owner must match app user✕needs logrotate and reload✕one file per serverstdout to journald/Docker✓no file permissions✓rotation handled for you✓easy to ship centrallyLet the process supervisor own log filesdjango-deployment.com
Keep file logging only when a tool specifically needs to read a file.

Verification check:

sudo -u deploy /srv/app/.venv/bin/python /srv/app/manage.py check
sudo systemctl restart myapp && systemctl is-active myapp

Both should succeed without a traceback.

Explanation

logging.config.dictConfig instantiates every handler immediately, and a FileHandler opens its file in append mode at that moment. Because settings are imported by every entry point (Gunicorn, Celery, manage.py, cron), any of them can create the file first. Whichever user does so owns it. That is why the error often appears hours after a deploy: a nightly root job or logrotate recreates the file and the next restart fails.

Edge cases / notes

  • SELinux. On Rocky Linux or RHEL, a correct owner can still be denied if the directory lacks the right context. Check ausearch -m avc and use semanage fcontext to label it.
  • delay=True. Setting "delay": True on the handler postpones opening the file until the first log record. It avoids startup crashes but hides the problem until something logs.
  • Celery and web as different users. If both write one file, give them a shared group and create 0660, or better, separate files.

Next steps

FAQ

Why does it work when I run the command with sudo?

Root can write anywhere. But the file it creates is then owned by root, which breaks the next non-root process.

Can I just chmod 777 the log file?

It hides the symptom and lets any user on the server write fake log entries. Fix ownership instead.

Does WatchedFileHandler help?

It reopens the file after rotation, which avoids writing to a deleted file. It does not fix ownership.

Should Docker containers write log files?

Rarely. Log to stdout and let the Docker logging driver or your log shipper store them.

Keep going