Nginx Permission Denied: Resolving Unix Socket Access on macOS Local Dev
Troubleshoot Nginx worker process 'permission denied' errors when opening Unix sockets on macOS, often due to user/group mismatches or incorrect socket permissions.
Troubleshoot Nginx worker process 'permission denied' errors when opening Unix sockets on macOS, often due to user/group mismatches or incorrect socket permissions.
When developing web applications locally on macOS, encountering "permission denied" errors related to Nginx and Unix sockets can be a frustrating roadblock. This error typically signifies that your Nginx web server, acting as a frontend proxy, cannot establish a connection with your backend application (such as PHP-FPM, Gunicorn for Python, or a Node.js process) via a Unix domain socket. This guide will meticulously detail the root causes and provide expert, step-by-step resolutions applicable to both Homebrew-managed setups and Dockerized environments on macOS.
Symptom & Error Signature
The most common symptom is your web application failing to load, displaying a "502 Bad Gateway" or similar error in the browser. In your Nginx error logs, you will find entries similar to these:
2023/10/26 10:30:15 [crit] 12345#0: *1 connect() to unix:/usr/local/var/run/php-fpm.sock failed (13: Permission denied) while connecting to upstream, client: 127.0.0.1, server: localhost, request: "GET /index.php HTTP/1.1", upstream: "fastcgi://unix:/usr/local/var/run/php-fpm.sock:", host: "localhost"
Or, if using a different backend like Gunicorn:
2023/10/26 10:30:15 [crit] 12345#0: *1 connect() to unix:/tmp/gunicorn.sock failed (13: Permission denied) while connecting to upstream, client: 127.0.0.1, server: myapp.local, request: "GET / HTTP/1.1", upstream: "http://unix:/tmp/gunicorn.sock:", host: "myapp.local"
You might also see these messages in your system logs or application-specific logs, though the Nginx error log is the primary indicator for this specific issue.
Root Cause Analysis
The "Permission denied (13)" error when Nginx tries to connect to a Unix socket points to a fundamental access control problem. Here's a breakdown of the underlying reasons:
- Nginx Worker Process User Mismatch: By default, Nginx worker processes run under a specific user (e.g.,
_wwwon macOS Homebrew installations,nginxorwww-datain Linux/Docker environments). If this user does not have sufficient read/write permissions to the Unix socket file created by the backend application, Nginx cannot connect. - Incorrect Socket File Permissions: The Unix socket file itself might have restrictive permissions (e.g.,
0600) that prevent users other than its creator from accessing it. - Parent Directory Permissions: Even if the socket file has correct permissions, Nginx might be denied access if the directory containing the socket (e.g.,
/usr/local/var/run/,/var/run/,/tmp/) does not grant execute permissions to the Nginx worker process user. Execute permission on a directory allows listing its contents and traversing into it. - Backend Application User Mismatch: The backend application (e.g.,
php-fpm,gunicorn,uwsgi) might be configured to run as a user different from Nginx and create the socket with restrictive permissions, leading to the access denial. - Socket Path Mismatch: While less common for "permission denied" specifically, a misconfigured socket path in either Nginx or the backend application can indirectly contribute to issues. Ensure both configurations point to the exact same socket file.
Step-by-Step Resolution
The resolution involves ensuring that the Nginx worker process has the necessary permissions to access the Unix socket. We'll cover common scenarios for macOS.
1. Verify Nginx Worker Process User
First, identify the user Nginx is running as. This user needs read and write access to the socket.
On macOS (Homebrew Nginx):
ps aux | grep nginx | grep 'worker process'
Typically, Nginx installed via Homebrew runs its worker processes as the _www user.
In Dockerized Environments (within Nginx container):
docker ps
# Find your Nginx container ID or name, e.g., myapp_nginx_1
docker exec -it myapp_nginx_1 ps aux | grep nginx | grep 'worker process'
The user might be nginx, www-data, or even root (though root is discouraged for worker processes). Note this user.
2. Identify Backend Socket Path, Permissions, and User
Next, determine where your backend application is creating the Unix socket, what its permissions are, and which user/group creates it.
For PHP-FPM (Homebrew on macOS or within Docker container):
Locate PHP-FPM configuration:
- Homebrew: Typically
$(brew --prefix)/etc/php/<php_version>/php-fpm.d/www.conf - Docker: Often
/etc/php/<php_version>/fpm/pool.d/www.confor/usr/local/etc/php-fpm.d/www.confinside the PHP-FPM container.
- Homebrew: Typically
Find the
listendirective: This specifies the socket path.; Example from www.conf listen = /usr/local/var/run/php-fpm.sock ; or for Docker listen = /var/run/php-fpm/php-fpm.sockFind
listen.owner,listen.group, andlisten.mode: These directives control the ownership and permissions of the created socket.; Example from www.conf listen.owner = _www listen.group = _www listen.mode = 0660 ; This means owner and group can read/write, others no access- On Homebrew,
_wwwis typical for both Nginx and PHP-FPM. - In Docker,
www-datais common.
- On Homebrew,
Verify actual socket status:
ls -la /usr/local/var/run/php-fpm.sock # Or your specific socket pathExamine the owner, group, and permissions (e.g.,
srw-rw----). If the Nginx user is not the owner or in the group, andlisten.modeis restrictive (like0660), you have found the issue.
For Gunicorn/uWSGI (Python apps):
The socket path and permissions are typically configured in your Gunicorn/uWSGI startup command or configuration file.
- Gunicorn: Look for
--bind unix:/path/to/socket.sock --user <user> --group <group> - uWSGI: Look for
socket = /path/to/socket.sockandchown-socket = <user>:<group>orchmod-socket.
3. Adjust Socket File Permissions and Ownership
This is the core fix. The goal is to ensure the Nginx worker process user can read and write to the socket.
Option A: For Homebrew-Managed Nginx and PHP-FPM on macOS
The most robust solution is to ensure PHP-FPM creates the socket with permissions that Nginx can access.
Edit PHP-FPM pool configuration (
www.conf): Open$(brew --prefix)/etc/php/<php_version>/php-fpm.d/www.confin your preferred editor (e.g.,nanoorvi).; Find these lines or add them if missing listen.owner = _www listen.group = _www listen.mode = 0660Ensure
listen.ownerandlisten.groupmatch the Nginx worker process user identified in Step 1. For Homebrew, this is typically_www.listen.mode = 0660grants read/write access to the owner (_www) and group (_www), which is suitable if Nginx also runs as_www.Check Parent Directory Permissions: Verify that the directory containing the socket (e.g.,
/usr/local/var/run/) allows the_wwwuser to traverse it.ls -ld /usr/local/var/run/Output should show something like
drwxr-xr-xfor_wwwowner. If not, adjust:sudo chmod 755 /usr/local/var/run/ sudo chown _www:_www /usr/local/var/run/ # Only if ownership is incorrectRestart Services: After making changes, restart both PHP-FPM and Nginx to apply them.
brew services restart php brew services restart nginx
Option B: For Dockerized Environments on macOS
This involves configuring permissions within your Docker containers and potentially managing shared volume permissions.
Match Nginx and Backend Users: The most common and secure approach is to configure both your Nginx container and your backend application container to use the same user (e.g.,
www-dataornginx) when interacting with the socket.Example
php-fpm.conforwww.conf(inside PHP-FPM container):listen = /var/run/php-fpm/php-fpm.sock listen.owner = www-data # Or 'nginx' if that's Nginx's user listen.group = www-data # Or 'nginx' listen.mode = 0660Example
nginx.conf(inside Nginx container):user www-data; # Or 'nginx' to match PHP-FPM # ... server { # ... location ~ .php$ { fastcgi_pass unix:/var/run/php-fpm/php-fpm.sock; # ... } }Adjust Permissions in Dockerfile or Entrypoint Script: Ensure the directory where the socket is created exists and has correct permissions inside the container. This is often done in the
Dockerfilefor the PHP-FPM image.# Dockerfile for PHP-FPM FROM php:8.2-fpm-alpine # ... RUN mkdir -p /var/run/php-fpm && chown www-data:www-data /var/run/php-fpm && chmod 755 /var/run/php-fpm # ...Docker Compose Configuration: If you're using Docker Compose, ensure any shared volumes that might contain the socket path are correctly handled. For local development on macOS, Docker Desktop uses a virtualized Linux environment, so permissions inside containers are generally independent of the macOS host, unless you're mounting a host directory directly where the socket would reside.
# docker-compose.yml version: '3.8' services: nginx: image: nginx:stable-alpine volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro # No need to share /var/run for socket if within container ports: - "80:80" php-fpm: image: php:8.2-fpm-alpine volumes: - ./app:/var/www/html # Your application code - ./php-fpm.conf:/usr/local/etc/php-fpm.d/www.conf # Mount custom PHP-FPM config # Ensure PHP-FPM runs as www-data user to create socket user: www-dataUsing the
userdirective indocker-compose.ymlcan explicitly define the user a service runs as, affecting the ownership of files it creates, including sockets.Temporary
listen.mode(for debugging): If you're still stuck in a Docker environment, as a temporary debugging step, you can setlisten.mode = 0666or0777inwww.conf. This makes the socket globally writable. If this fixes the issue, you know it's purely a permissions problem. Immediately revert this and fix the ownership/group setup properly for security.listen.mode = 0666 ; WARNING: DO NOT USE IN PRODUCTION!Setting
listen.mode = 0666or0777for a Unix socket grants write access to all users on the system, which is a significant security risk. Only use this for temporary debugging and ensure you revert it immediately. The proper fix is to match user/group ownership.Restart Docker Compose Services:
docker-compose restart
Option C: Linux Server Environments (e.g., in a VM on macOS)
If your "local environment" on macOS involves a Linux VM (e.g., Vagrant, Multipass) where Nginx and PHP-FPM are installed directly, you'll use standard Linux commands.
Edit PHP-FPM pool configuration (
www.conf): Typically/etc/php/<php_version>/fpm/pool.d/www.conf.listen = /var/run/php/php<php_version>-fpm.sock listen.owner = www-data listen.group = www-data listen.mode = 0660In most Debian/Ubuntu systems, Nginx runs as
www-dataand PHP-FPM also runs aswww-data, makinglisten.owner = www-dataandlisten.group = www-datathe correct configuration.Check Parent Directory Permissions:
ls -ld /var/run/php/ sudo chown www-data:www-data /var/run/php/ # If necessary sudo chmod 755 /var/run/php/ # If necessaryRestart Services with
systemctl:sudo systemctl restart php<php_version>-fpm.service sudo systemctl restart nginx.service
4. Verify Nginx Configuration
Double-check your Nginx server block configuration to ensure the fastcgi_pass (or proxy_pass for Gunicorn/uWSGI) directive points to the correct Unix socket path.
# Nginx site configuration (e.g., /usr/local/etc/nginx/servers/myapp.conf or within Docker container)
server {
listen 80;
server_name localhost; # or your domain
root /path/to/your/webapp;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ .php$ {
fastcgi_pass unix:/usr/local/var/run/php-fpm.sock; # Ensure this path is correct!
# For Docker, it might be unix:/var/run/php-fpm/php-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
After any changes to Nginx configuration, always test the syntax:
nginx -t
If nginx -t reports success, reload Nginx:
brew services reload nginx # For Homebrew on macOS
# OR
sudo nginx -s reload # If Nginx is running directly on macOS
# OR
docker exec -it myapp_nginx_1 nginx -s reload # For Docker
5. Confirm Resolution
After applying the changes and restarting services, refresh your web application in the browser. Check the Nginx error logs again (/usr/local/var/log/nginx/error.log for Homebrew, or docker logs <nginx_container>) to ensure the "permission denied" error is no longer appearing.
By systematically verifying user permissions, socket ownership, directory access, and configuration paths, you can effectively resolve the Nginx "permission denied opening unix socket" error on your macOS local development environment.