Fix Nginx "connect() to unix:/run/gunicorn.sock Failed (2: No Such File or Directory)"

Fix the Nginx 502 error connect() to unix:/run/gunicorn.sock failed (2: No such file or directory): start the Gunicorn socket or service, match the socket path in Nginx and systemd, and stop PrivateTmp and RuntimeDirectory from hiding the file.

Problem statement

The site returns 502 Bad Gateway, and /var/log/nginx/error.log says:

connect() to unix:/run/gunicorn.sock failed (2: No such file or directory)
while connecting to upstream, client: 203.0.113.9, server: example.com,
request: "GET / HTTP/1.1", upstream: "http://unix:/run/gunicorn.sock:/"

Nginx is configured to talk to Gunicorn through a Unix socket file, and that file does not exist at the path Nginx uses. Either Gunicorn is not running, it created the socket somewhere else, or the socket is inside a private directory Nginx cannot see. This is different from (13: Permission denied), where the file exists but Nginx may not use it.

Why Nginx gets errno 2Nginx proxiesto /run/gunicorn.sockLooks for fileat that pathFile missingnot started or moved502 Bad Gatewayerrno 2Errno 2 means the socket file is not where Nginx expects, not that access is denied.
  1. Check the file: ls -l /run/gunicorn.sock.
  2. Check the services: systemctl status gunicorn.socket gunicorn.service.
  3. Start or enable them: sudo systemctl enable --now gunicorn.socket.
  4. Make the path identical in three places: the systemd socket unit (or Gunicorn --bind), Nginx proxy_pass, and any RuntimeDirectory.
  5. Do not put the socket in /tmp if the service uses PrivateTmp=true; use /run/<name>/.

Step-by-step solution

1. See whether the socket exists

ls -l /run/gunicorn.sock
sudo ss -xlp | grep gunicorn

No file and no listening Unix socket means nothing created it. If ss shows Gunicorn listening on a different path, you have a path mismatch.

2. Start Gunicorn through systemd

With socket activation, systemd creates the socket and starts Gunicorn on the first request:

# /etc/systemd/system/gunicorn.socket
[Unit]
Description=gunicorn socket

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

[Install]
WantedBy=sockets.target
sudo systemctl daemon-reload
sudo systemctl enable --now gunicorn.socket
curl --unix-socket /run/gunicorn.sock http://localhost/

The curl call triggers gunicorn.service; check it with systemctl status gunicorn. If the service fails, its journal shows why: journalctl -u gunicorn -n 50.

what to checkWhat you findCause and fixsocket unit inactivenever enabled; enable--now gunicorn.socketservice keeps failingapp error; read journalctl -u gunicornGunicorn on another pathpaths differ; alignbind and proxy_passsocket under /tmpPrivateTmp hides it; move to /run/appA missing file is a startup or path problem, not a permission problemdjango-deployment.com
Errno 13 would mean permissions; errno 2 always means the path.

3. Align the paths

The same path must appear in the socket unit or Gunicorn command and in Nginx:

upstream app {
    server unix:/run/gunicorn.sock fail_timeout=0;
}
server {
    location / {
        proxy_pass http://app;
    }
}

Without socket activation, Gunicorn creates the file itself from --bind unix:/run/gunicorn/gunicorn.sock. In that case, the directory must exist and be writable by the Gunicorn user, which brings us to /run.

4. Use RuntimeDirectory instead of /tmp

/run is a tmpfs, cleared on boot, and only root can create entries in it. Let systemd create a directory owned by the service user:

[Service]
User=deploy
Group=www-data
RuntimeDirectory=gunicorn
ExecStart=/srv/app/.venv/bin/gunicorn --bind unix:/run/gunicorn/gunicorn.sock config.wsgi

Avoid /tmp/gunicorn.sock when either unit uses PrivateTmp=true: each service then has its own private /tmp, and Nginx looks in a different one than Gunicorn wrote to.

5. Survive reboots

After a reboot, everything in /run is gone. Units that are not enabled do not recreate it. Make sure both are enabled:

systemctl is-enabled gunicorn.socket nginx
three places, one pathSettingExamplegunicorn.socketListenStream=/run/gunicorn.sockGunicorn --bindunix: pathonly without socket unitNginx upstreamserver unix:/run/gunicorn.sockRuntimeDirectorycreates /run/NAMEfor Gunicorn-made socketUse socket activation or a Gunicorn bind, not both, and keep one pathdjango-deployment.com
Copy the path from one file into the others rather than retyping it.

Explanation

A Unix domain socket is a file-system object that a server process creates and listens on. Nginx connects to it by path, and the kernel returns ENOENT (errno 2) if no such file exists. Because /run is cleared at boot and Gunicorn removes its socket on shutdown, the file only exists while something is listening. Systemd socket activation makes the file independent of Gunicorn's lifetime, which is why it is the most robust setup.

Edge cases / notes

  • Docker. Unix sockets between containers need a shared volume; TCP on the Compose network is usually simpler.
  • Multiple apps. Give each app its own socket name and directory, such as /run/shop/gunicorn.sock.
  • SELinux. On RHEL-family systems, Nginx may need the httpd_can_network_connect boolean or a socket context, which surfaces as permission denied, not errno 2.

Next steps

FAQ

What does errno 2 mean in this message?

"No such file or directory": the socket file does not exist at the configured path.

Why did it break after a reboot?

/run is emptied at boot. If the socket unit or service is not enabled, nothing recreates the socket.

Should I use a Unix socket or TCP?

On one server, a Unix socket is slightly faster and not exposed to the network. TCP is simpler across containers or hosts.

Is it bad to put the socket in /tmp?

With PrivateTmp=true, each service sees a different /tmp, so Nginx cannot find it. Use /run with RuntimeDirectory.

Keep going