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.
- Check who runs the app (
ps -o user= -p <pid>or theUser=line in the systemd unit) and who owns the file (ls -l). - Create a directory owned by that user:
sudo install -d -o deploy -g deploy -m 750 /var/log/myapp. - Fix the existing file:
sudo chown deploy:deploy /var/log/myapp/django.log. - Run management commands as the app user (
sudo -u deploy ...), never as root. - 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.
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.
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 avcand usesemanage fcontextto label it. - delay=True. Setting
"delay": Trueon 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
- If logs are written but empty, see Fix Django Logging Not Working in Production.
- Set up rotation properly with Rotate Django Logs on Linux.
- Build a full configuration with Set Up Django Logging in Production.
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.