Reference Guide Detailed deployment notes with production context and concrete examples.

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:

bash
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

  1. Identify the socket path used by Nginx and Gunicorn
    Check your Nginx site config:
    bash
    sudo grep -R "unix:" /etc/nginx/sites-enabled /etc/nginx/conf.d
    

    Example:
    nginx
    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn/app.sock;
    }
    

    Check your Gunicorn service:
    bash
    systemctl cat gunicorn
    

    Look for:
    ini
    ExecStart=/path/to/venv/bin/gunicorn --workers 3 --bind unix:/run/gunicorn/app.sock wsgi:app
    

    The paths must match exactly.
  2. Confirm which user Nginx runs as
    Run:
    bash
    ps -eo user,group,cmd | grep 'nginx: worker'
    

    Or inspect:
    bash
    grep -n "^user" /etc/nginx/nginx.conf
    

    On Ubuntu, the worker user is usually www-data.
  3. Confirm which user and group Gunicorn runs as
    Inspect the service:
    bash
    systemctl cat gunicorn
    

    Look for:
    ini
    [Service]
    User=gunicorn
    Group=www-data
    UMask=0007
    

    If Group= is missing, add it explicitly.
  4. Fix the socket directory permissions
    If you use /run/gunicorn, create it with access for the Nginx group:
    bash
    sudo install -d -m 775 -o gunicorn -g www-data /run/gunicorn
    

    Validate:
    bash
    ls -ld /run/gunicorn
    

    Expected pattern:
    bash
    drwxrwxr-x 2 gunicorn www-data ...
    
  5. Fix the Gunicorn systemd unit
    Edit the unit:
    bash
    sudo 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/gunicorn correctly on each start and ensures the socket is group-accessible.
  6. Reload systemd and restart Gunicorn
    Run:
    bash
    sudo systemctl daemon-reload
    sudo systemctl restart gunicorn
    sudo systemctl status gunicorn --no-pager
    
  7. Validate socket ownership and mode
    Check both the directory and socket:
    bash
    ls -lah /run/gunicorn /run/gunicorn/app.sock
    

    Typical result:
    bash
    drwxrwxr-x 2 gunicorn www-data ...
    srw-rw---- 1 gunicorn www-data ...
    

    For full path traversal debugging, run:
    bash
    namei -l /run/gunicorn/app.sock
    

    This often reveals a restrictive parent directory.
  8. Test and reload Nginx
    Validate config:
    bash
    sudo nginx -t
    

    Reload:
    bash
    sudo systemctl reload nginx
    
  9. Retest requests through Nginx
    Test locally:
    bash
    curl -I http://127.0.0.1
    

    Then test your public domain:
    bash
    curl -I https://your-domain.com
    

    You should see 200, 301, or 302 instead of upstream permission errors.
  10. Check SELinux or AppArmor if permissions look correct

On SELinux systems:

bash
getenforce

On AppArmor systems:

bash
sudo aa-status

If Unix permissions look correct but access is still denied, review policy logs.

  1. Avoid bad socket locations

Do not place the socket under a restrictive app path unless you control directory traversal permissions. Prefer:

text
/run/gunicorn/app.sock

Avoid patterns like:

text
/var/www/myapp/app.sock
/home/deploy/myapp/app.sock
/tmp/app.sock
  1. Restart services after user/group changes

If you changed group membership, restart both services so the new access model is active:

bash
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 as www-data and use UMask=0007.
  • Using /tmp or 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 /run without the right permissions → Manual fixes disappear after restart → Use RuntimeDirectory= and RuntimeDirectoryMode= 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 usermod or unit changes.
  • Overly restrictive UMask → Gunicorn creates the socket without group access → Set UMask=0007.

Debugging Section

Check the exact failure point with these commands:

bash
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 logs
  • bind: permission denied or 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/gunicorn or app.sock
  • missing RuntimeDirectory, Group, or UMask in 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, and UMask.
  • The socket file is owned by the Gunicorn user and a group Nginx can access.
  • systemctl restart gunicorn recreates the socket successfully.
  • nginx -t passes and Nginx reloads without errors.
  • Requests through Nginx no longer return 502 or upstream permission errors.

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.