Halloween DealsAmazon USHalloween week deals: useful picks before month-endAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowHalloween DealsAmazon USOctober picks are moving - compare before checkoutAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
World desk10 min

How to Fix “Permission Denied” Errors With Django, Gunicorn and Nginx

A practical, least-privilege guide to fixing Gunicorn Unix-socket and Django file permission errors behind Nginx, with systemd examples and identity-based diagnostics.
Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

A Gunicorn socket error such as connect() to unix:/run/gunicorn.sock failed (13: Permission denied) is an operating-system access decision. Nginx is running as one user, the socket has an owner, group and mode, and every directory in the path must allow that user to traverse it. Identify the exact denied path and process identity first; changing permissions blindly, especially with recursive chmod 777, can create a security problem without fixing the real cause.

The reliable repair is to align the Nginx worker identity with the socket’s owner, group and mode, or to bind Gunicorn on loopback TCP instead. A socket created by systemd can be given durable ownership and permissions, while Django’s static files, media, logs and generated files must be checked as separate filesystem cases.

As an Amazon Associate I earn from qualifying purchases.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What “permission denied” means in a Django and Gunicorn stack

Linux returns permission denied when a process cannot perform an operation on a specific filesystem object. In this stack, the failing operation is often Nginx trying to connect to a Unix-domain socket, but it may instead be Nginx opening a static file, Gunicorn writing a log, or Django saving an upload.

The path and the process matter. A socket at /run/gunicorn.sock can be perfectly usable by Gunicorn and still inaccessible to Nginx. Conversely, changing the socket mode will not help if the denied path is /var/www/app/static/css/site.css. Treat each error as a separate path-and-identity diagnosis.

Start with the exact error, not a permission guess

Read the Nginx error log and the Gunicorn service journal at the time of the failure. Record the complete path, service name, operation and errno.

sudo tail -n 100 /var/log/nginx/error.log
sudo journalctl -u gunicorn.service -u gunicorn.socket -b
sudo systemctl status nginx gunicorn.service gunicorn.socket

These messages describe different failures:

Message or symptom What it usually means First check
connect() to unix:/run/gunicorn.sock failed (13: Permission denied) The Nginx worker cannot traverse the path or connect to the socket under its Unix permissions or a mandatory access-control policy. Socket owner, group, mode and every parent directory; then SELinux or AppArmor records.
No such file or directory The socket path does not exist, is in a different runtime directory, or the service created it elsewhere. Unit configuration, service state and the actual path.
Connection refused The socket exists but no process is accepting connections, or Gunicorn has stopped. Gunicorn journal, listeners and service status.
Nginx open() denial for /static/ or /media/ Nginx cannot read a Django-served file or traverse its directories. The denied file and each parent directory, independently of the socket.

Do not infer a socket problem from a generic 403 or 502. The error log identifies which object failed.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Confirm which users are actually involved

Find the Nginx worker identity

Inspect the active Nginx configuration for its user directive and confirm the running processes:

grep -R "^[[:space:]]*user[[:space:]]" /etc/nginx/nginx.conf /etc/nginx/conf.d /etc/nginx/sites-enabled 2>/dev/null
ps -eo user,group,comm,args | grep '[n]ginx:'

Common accounts include www-data, nginx and http; none is universal across distributions. Use the account shown by your installation, not a name copied from another server.

Find Gunicorn’s service identity

For a systemd-managed service, inspect the effective unit and its status:

systemctl cat gunicorn.service
systemctl show gunicorn.service -p User -p Group -p WorkingDirectory
systemctl status gunicorn.service

Gunicorn also supports selecting worker credentials with --user and --group. Its settings documentation notes: “Note that this affects unix socket permissions.” The worker identity can therefore differ from the account that starts the master process.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Inspect the socket and every parent directory

For /run/gunicorn.sock, inspect both the socket and the complete path:

ls -l /run/gunicorn.sock
namei -l /run/gunicorn.sock
systemctl status gunicorn.service gunicorn.socket
journalctl -u gunicorn.service -u gunicorn.socket -b

ls -l shows the socket’s type, owner, group and mode. namei -l exposes each component: /, /run and the final socket. The connecting Nginx process needs execute (traversal) permission on every parent directory, plus the required permission on the socket itself. A socket mode that looks correct is not sufficient if a parent directory blocks traversal.

Repeat the same process for any path named in an open() denial. A directory can be readable yet not traversable, and a file can be readable while its parent directory is inaccessible.

Fix a systemd-created Unix socket

A systemd socket unit is usually the most durable arrangement for a runtime socket. Gunicorn’s deployment guide states that “systemd can create a UNIX socket and launch Gunicorn on demand.” The socket unit owns creation, so the mode and group are applied consistently after reboot.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Set the socket owner, group and mode

A Debian-style example is:

[Unit]
Description=Gunicorn socket

[Socket]
ListenStream=/run/gunicorn.sock
SocketUser=www-data
SocketGroup=www-data
SocketMode=0660

[Install]
WantedBy=sockets.target

SocketUser and SocketGroup determine who owns the endpoint. 0660 permits the owner and group to read and write the socket while denying access to others. Substitute the actual Nginx account or a deliberately chosen shared group; do not assume www-data is correct everywhere.

Keep the service identity explicit

The service unit should separately declare the account that runs Gunicorn and the directory from which the application starts:

[Service]
User=django
Group=django
WorkingDirectory=/srv/example
ExecStart=/srv/example/venv/bin/gunicorn --workers 3 --bind unix:/run/gunicorn.sock example.wsgi:application

The socket owner does not have to be the same as the Gunicorn service user. What matters is that Gunicorn can use the endpoint and Nginx can connect to it. If the service writes through the socket or replaces it, verify that the chosen arrangement works with the unit dependencies and runtime directory.

Reload and verify instead of assuming

  1. Save the socket and service unit with the exact paths used by your Nginx upstream.
  2. Run sudo systemctl daemon-reload after editing unit files.
  3. Enable and restart the socket and service as appropriate: sudo systemctl enable --now gunicorn.socket, then sudo systemctl restart gunicorn.service.
  4. Check systemctl status, ls -l /run/gunicorn.sock and namei -l /run/gunicorn.sock.
  5. Confirm that the resulting owner, group and mode match the values you intended.

A runtime directory such as /run is recreated at boot. Having systemd recreate the socket prevents a manually created endpoint with stale ownership from returning after a restart.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Fix a socket that Gunicorn creates itself

If the Gunicorn command binds directly to a Unix socket, review all credential and permission options:

/srv/example/venv/bin/gunicorn 
  --user django 
  --group webapps 
  --umask 007 
  --bind unix:/run/gunicorn.sock 
  example.wsgi:application

--user changes the worker user and --group selects the worker group. Gunicorn documents that --umask affects Unix-socket permissions. The resulting mode depends on the requested mode and the umask, so inspect the created socket rather than calculating from memory.

Make sure the account creating or replacing the socket can write to its parent directory. If the socket is under a directory owned by a different service, the next restart may fail even if the current socket is usable. A systemd socket unit is generally easier to make persistent because creation and ownership are kept outside the application command.

Test the connection as Nginx

Testing as your shell account can hide the problem because your account may have extra group membership or elevated privileges. Make the request as the real Nginx worker user:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
sudo -u www-data curl --unix-socket /run/gunicorn.sock http://localhost/

Replace www-data with the account identified in your active Nginx configuration. Gunicorn’s deployment examples use this form on Debian-style systems. A successful HTTP response proves that the proxy identity can traverse the path and connect; it does not prove that every Django static or upload path is readable.

If the test still returns permission denied, rerun namei -l and check mandatory-access-control logs. If it returns connection refused, inspect whether Gunicorn is listening and healthy rather than changing modes.

Use a shared group instead of broad world permissions

When Nginx and Gunicorn need access to the same socket, a dedicated shared group is usually easier to maintain than making the socket world-accessible. Add only the required service accounts to that group, set the socket’s group and group-readable/group-writable mode, and ensure the parent directory permits traversal.

Direct ownership changes can be adequate for a single service, but they become fragile when deployments recreate the socket, rotate users or add another proxy. A shared group expresses the boundary explicitly and avoids granting unrelated local accounts access. Do not solve membership problems by using 0777 or by recursively changing an entire application tree.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Unix socket or loopback TCP?

Both transports can be valid. Choose based on the access-control and operational requirements of your host.

Consideration Unix socket Loopback TCP
Access control Uses filesystem owner, group, mode, parent-directory traversal and possibly SELinux/AppArmor labels. Uses the bind address and port, firewall exposure and service policy; there is no socket pathname to traverse.
Proxy configuration Nginx upstream points to a path such as unix:/run/gunicorn.sock. Nginx upstream points to an address such as 127.0.0.1:8000.
Runtime durability Requires reliable recreation and ownership of a runtime file; systemd can manage that lifecycle. Requires a stable port and a service that binds it on startup.
Typical failure surface Missing path, denied traversal, wrong mode or stale socket. Port conflict, refused connection or an unintended listening address.

Switching to loopback TCP can remove a Unix-permission failure, but it is a configuration change, not a repair of the underlying filesystem policy. Bind only to loopback unless you intentionally need network access, and update both Gunicorn and Nginx to the same address and port.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Static files, media, logs and generated files need separate fixes

Django deployments commonly expose several filesystem trees. Diagnose each according to the process that reads or writes it:

Static files

Nginx must be able to traverse the static directory and read the collected files. Check the exact path named in the log:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
namei -l /srv/example/static/css/site.css
ls -ld /srv/example/static /srv/example/static/css
ls -l /srv/example/static/css/site.css

Grant read and traversal access to the Nginx account or an appropriate group. The socket’s owner and mode have no effect on this path.

Media and uploads

Uploaded media often needs Nginx read access while Django needs write access to the upload directory. Use ownership and group permissions that provide those capabilities without making uploads writable by the proxy unless the application design requires it. Inspect both the directory and existing files; a writable directory does not automatically make every existing file writable.

Logs

Gunicorn, Django and Nginx may write to different log locations. Check the service account, directory ownership, rotation configuration and parent traversal. A service can start successfully and later fail when it attempts to create a rotated file in a directory it cannot write.

Generated files

Reports, caches and export directories should be writable only by the process that generates them, with read access granted separately to Nginx if it serves the results. Fix the specific directory and operation shown by the error rather than changing the permissions of the whole project.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

When Unix permissions look correct: SELinux and AppArmor

Mandatory access-control systems can deny an operation even when owner, group and mode appear correct. On SELinux systems, inspect audit records and the active file contexts; on AppArmor systems, inspect profile denials in the journal or system logs. The correct adjustment depends on the distribution, profile, service confinement and path labels.

Do not copy a generic policy rule from an unrelated server. Capture the local denial, identify the confined process and grant only the operation required for the specific socket or file path. A policy change should be followed by a fresh test as the Nginx user.

A repeatable recovery checklist

  1. Copy the exact Nginx or Gunicorn error, including the path and errno.
  2. Classify it as permission denied, missing path, refused connection or a file denial.
  3. Identify the Nginx worker account and Gunicorn service/worker account.
  4. Inspect the target and every parent directory with ls -l and namei -l.
  5. For a socket, align owner, group and mode through a systemd socket unit or Gunicorn’s --user, --group and --umask settings.
  6. Reload systemd units or restart the relevant services, then verify the resulting runtime object.
  7. Test with sudo -u using the actual Nginx identity.
  8. If the path is static, media, logs or generated output, repair that tree independently.
  9. Review SELinux or AppArmor denials when ordinary Unix permissions are not the explanation.

Frequently asked questions

Why can Gunicorn run Django while Nginx cannot access the socket?

Gunicorn and Nginx are separate processes with potentially different users and groups. Gunicorn can open its own socket while Nginx lacks permission to traverse a parent directory or connect under the socket’s mode. Compare both identities and the complete path.

Does changing the socket to mode 0660 always fix the error?

No. Mode 0660 works only when Nginx belongs to the socket’s group and every parent directory allows traversal. A mandatory-access-control policy can still deny the connection.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Why does the problem return after reboot?

A socket under /run is a runtime object. If it is recreated by a shell command or by Gunicorn with different credentials, its ownership can change on every boot. A systemd socket unit can recreate it with declared SocketUser, SocketGroup and SocketMode values.

Should I use chmod -R 777 on the project?

No. It grants broad access to files and directories, can expose secrets or permit unwanted writes, and may still leave the actual parent path or security policy wrong. Correct the specific identity, path and capability required by the failing operation.

Frequently Asked Questions

How do I fix Nginx’s “connect() to unix:/run/gunicorn.sock failed (13: Permission denied)” error?

Identify the Nginx worker user, inspect the socket and every parent directory with ls -l and namei -l, then align the socket owner/group/mode through systemd or Gunicorn settings. Verify with sudo -u <nginx-user> curl --unix-socket /run/gunicorn.sock http://localhost/.

What if the socket permissions are correct but Nginx is still denied?

Check SELinux or AppArmor audit and journal denials. Mandatory access-control policy can block a path despite ordinary Unix permissions; use the local denial and service profile to make a narrowly scoped adjustment.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

More from the Wire

  1. World desk4 min
    How to Spot an AI Voice Scam Before Sending MoneyDon’t rely on how a caller sounds. Pause, call back through a known number, and verify the emergency with another trusted person before sending money.
  2. Mountain View desk4 min
    Google’s SynthID Detector: How to Check AI-Generated Images, Video and AudioGoogle’s SynthID Detector looks for an embedded watermark in supported images, video and audio. Here is what its results do—and do not—show.
  3. Redmond desk20 min
    How to create a link to File or Folder in Windows 11Windows 11 gives you several ways to point to a file or folder without moving or duplicating it. You can create a desktop shortcut,…
Recommended PC Tool
Recommended PC Tool
Crashes, No Sound, or Screen Glitches?Free driver scan
PC Slower Than It Used to Be?Free scan - under a minute

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.