Gunicorn Socket Permission Denied (Fix Guide)
If you're seeing a permission denied error on a Gunicorn socket or Nginx cannot access the Gunicorn Unix socket, this guide shows you how to fix it step-by-step. The goal is to restore communication between Nginx, systemd, and Gunicorn and confirm the app serves requests normally.
Quick Fix / Quick Setup
Run the following with your actual Gunicorn service user and socket path:
sudo usermod -aG www-data gunicorn
sudo install -d -m 775 -o gunicorn -g www-data /run/gunicorn
sudo chown gunicorn:www-data /run/gunicorn
sudo chmod 775 /run/gunicorn
# example systemd values
# User=gunicorn
# Group=www-data
# UMask=0007
# ExecStart=/path/to/venv/bin/gunicorn --workers 3 --bind unix:/run/gunicorn/app.sock wsgi:app
sudo systemctl daemon-reload
sudo systemctl restart gunicorn
sudo nginx -t && sudo systemctl reload nginx
ls -lah /run/gunicorn /run/gunicorn/app.sock
Use the actual Gunicorn service user and socket path from your setup. The Nginx worker user must be able to traverse the socket directory and read/write the socket.
What’s Happening
Gunicorn creates a Unix socket file and Nginx connects to it instead of a TCP port. A permission denied error usually means Nginx cannot access the socket file or its parent directory. The usual causes are mismatched users/groups, restrictive directory permissions, a bad UMask, or /run being recreated with the wrong ownership on restart.
Step-by-Step Guide
- Identify the socket path used by Nginx and Gunicorn
Check your Nginx site config:bashsudo grep -R "unix:" /etc/nginx/sites-enabled /etc/nginx/conf.d
Example:nginxlocation / { include proxy_params; proxy_pass http://unix:/run/gunicorn/app.sock; }
Check your Gunicorn service:bashsystemctl cat gunicorn
Look for:iniExecStart=/path/to/venv/bin/gunicorn --workers 3 --bind unix:/run/gunicorn/app.sock wsgi:app
The paths must match exactly. - Confirm which user Nginx runs as
Run:bashps -eo user,group,cmd | grep 'nginx: worker'
Or inspect:bashgrep -n "^user" /etc/nginx/nginx.conf
On Ubuntu, the worker user is usuallywww-data. - Confirm which user and group Gunicorn runs as
Inspect the service:bashsystemctl cat gunicorn
Look for:ini[Service] User=gunicorn Group=www-data UMask=0007
IfGroup=is missing, add it explicitly. - Fix the socket directory permissions
If you use/run/gunicorn, create it with access for the Nginx group:bashsudo install -d -m 775 -o gunicorn -g www-data /run/gunicorn
Validate:bashls -ld /run/gunicorn
Expected pattern:bashdrwxrwxr-x 2 gunicorn www-data ... - Fix the Gunicorn systemd unit
Edit the unit:bashsudo systemctl edit --full gunicorn
Example service:ini[Unit] Description=Gunicorn for Flask app After=network.target [Service] User=gunicorn Group=www-data WorkingDirectory=/var/www/myapp RuntimeDirectory=gunicorn RuntimeDirectoryMode=0775 UMask=0007 ExecStart=/var/www/myapp/venv/bin/gunicorn \ --workers 3 \ --bind unix:/run/gunicorn/app.sock \ wsgi:app [Install] WantedBy=multi-user.target
This makes systemd create/run/gunicorncorrectly on each start and ensures the socket is group-accessible. - Reload systemd and restart Gunicorn
Run:bashsudo systemctl daemon-reload sudo systemctl restart gunicorn sudo systemctl status gunicorn --no-pager - Validate socket ownership and mode
Check both the directory and socket:bashls -lah /run/gunicorn /run/gunicorn/app.sock
Typical result:bashdrwxrwxr-x 2 gunicorn www-data ... srw-rw---- 1 gunicorn www-data ...
For full path traversal debugging, run:bashnamei -l /run/gunicorn/app.sock
This often reveals a restrictive parent directory. - Test and reload Nginx
Validate config:bashsudo nginx -t
Reload:bashsudo systemctl reload nginx - Retest requests through Nginx
Test locally:bashcurl -I http://127.0.0.1
Then test your public domain:bashcurl -I https://your-domain.com
You should see200,301, or302instead of upstream permission errors. - Check SELinux or AppArmor if permissions look correct
On SELinux systems:
getenforce
On AppArmor systems:
sudo aa-status
If Unix permissions look correct but access is still denied, review policy logs.
- Avoid bad socket locations
Do not place the socket under a restrictive app path unless you control directory traversal permissions. Prefer:
/run/gunicorn/app.sock
Avoid patterns like:
/var/www/myapp/app.sock
/home/deploy/myapp/app.sock
/tmp/app.sock
- Restart services after user/group changes
If you changed group membership, restart both services so the new access model is active:
sudo systemctl restart gunicorn
sudo systemctl restart nginx
Common Causes
- Nginx user cannot traverse the socket directory → The directory containing the socket lacks execute permission for the Nginx user/group → Set correct ownership and mode on the parent directory.
- Socket file owned by the wrong group → Gunicorn creates the socket with a group Nginx does not belong to → Set
Group=in systemd to a shared group such aswww-dataand useUMask=0007. - Using
/tmpor an app directory for the socket → Cleanup or restrictive permissions break access after reboot or deploy → Move the socket to/run/gunicorn/app.sock. - systemd recreates
/runwithout the right permissions → Manual fixes disappear after restart → UseRuntimeDirectory=andRuntimeDirectoryMode=in the unit file. - Nginx points to a different socket path than Gunicorn → Nginx reports connect permission or missing socket errors → Make both configs use the same path.
- SELinux or AppArmor policy blocks access → File mode looks correct but access is denied → Check security policy logs and adjust context or profile rules.
- Socket created before group membership change took effect → Nginx or Gunicorn still runs with old group state → Restart both services after
usermodor unit changes. - Overly restrictive
UMask→ Gunicorn creates the socket without group access → SetUMask=0007.
Debugging Section
Check the exact failure point with these commands:
sudo journalctl -u gunicorn -n 100 --no-pager
sudo tail -n 100 /var/log/nginx/error.log
systemctl cat gunicorn
namei -l /run/gunicorn/app.sock
ls -lah /run/gunicorn /run/gunicorn/app.sock
ps -eo user,group,cmd | grep 'nginx: worker'
sudo nginx -t
sudo systemctl restart gunicorn && sleep 1 && ls -lah /run/gunicorn/app.sock
curl -I http://127.0.0.1
getenforce
sudo aa-status
What to look for:
connect() to unix:/... failed (13: Permission denied)in Nginx logsbind: permission deniedor startup failures in Gunicorn logs- mismatch between Nginx socket path and Gunicorn bind path
- directory traversal failures in
namei -l - wrong owner/group on
/run/gunicornorapp.sock - missing
RuntimeDirectory,Group, orUMaskin the unit file
If Gunicorn does not start at all, also check Flask Gunicorn Service Failed to Start. If Nginx cannot reach Gunicorn for non-permission reasons, see Fix: Nginx Not Connecting to Gunicorn (Connection Refused).
Checklist
- Nginx and Gunicorn use the exact same Unix socket path.
- The socket parent directory exists and is accessible by the Nginx worker user/group.
- The Gunicorn systemd unit sets the correct
User,Group, andUMask. - The socket file is owned by the Gunicorn user and a group Nginx can access.
-
systemctl restart gunicornrecreates the socket successfully. -
nginx -tpasses and Nginx reloads without errors. - Requests through Nginx no longer return
502or upstream permission errors.
Related Guides
- Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide)
- Fix: Nginx Not Connecting to Gunicorn (Connection Refused)
- Flask Gunicorn Service Failed to Start
- Flask Production Checklist (Everything You Must Do)
FAQ
Q: What does connect() to unix:/... failed (13: Permission denied) mean?
A: Nginx found the socket path but cannot access the socket file or one of its parent directories due to permission or security policy restrictions.
Q: Is changing the socket to mode 777 a good fix?
A: No. It is insecure and usually unnecessary. Use the correct shared group, parent directory permissions, and systemd UMask instead.
Q: Why use RuntimeDirectory=gunicorn in systemd?
A: It makes systemd create /run/gunicorn automatically on service start with predictable ownership and permissions.
Q: Can I avoid socket permission problems entirely?
A: Yes. You can bind Gunicorn to 127.0.0.1:8000 and proxy to TCP from Nginx, though Unix sockets are still a common production choice on single-host deployments.
Final Takeaway
Gunicorn socket permission errors are usually access-control mismatches, not application bugs. Fix the socket path, parent directory permissions, systemd user/group/umask, and runtime directory creation, then validate with logs and file permission checks.