Fix Nginx “Permission denied” when connecting to an upstream

Separate Unix socket permissions from SELinux, AppArmor, and network policy when Nginx cannot connect to a backend.

Best practices
Illustration of application infrastructure and operations

Nginx's (13: Permission denied) reports an access denial, not simply a missing backend listener. Start by identifying whether the upstream is a Unix socket or a TCP address. Then inspect permissions and policy from the Nginx worker's execution context.

For Unix sockets, both the socket and its parent directories matter. For TCP connections, investigate mandatory access control and network policy using audit evidence.

Check Unix socket access

A typical error is:

connect() to unix:/var/run/app.sock failed (13: Permission denied)

Inspect the actual socket:

ls -l /var/run/php-fpm.sock
# Example: srw-rw---- 1 www-data www-data ...

On Linux, connecting to a pathname socket requires appropriate socket access and search permission on the path's directories. The Nginx worker must have the right identity, group membership, or ACL permissions; SELinux/AppArmor can impose additional restrictions.

For PHP-FPM, configure ownership and mode where the socket is created:

listen.owner = www-data
listen.group = www-data
listen.mode = 0660

Use the actual service users for your distribution. An alternative is adding the Nginx user to the socket's permitted group, for example usermod -aG www-data nginx, followed by restarting the affected workers so they obtain the new supplementary groups. Check configured socket ACLs as well, because they can change how ownership settings apply.

Give only the required identities access to the parent directories. Do not use world-writable socket or directory modes as a general fix.

Investigate a denied TCP connection

If Nginx reports a denial connecting to 127.0.0.1:3000 while an interactive curl succeeds, the difference in process security context is a useful clue. It does not prove SELinux is the cause.

getenforce
sudo ausearch -m AVC -ts recent

Enforcing describes SELinux's state. Look for a matching denial involving the worker domain and intended connection. Where the installed policy and approved access require it, an administrator may use:

# Broad network-connect permission: assess narrower policy first.
sudo setsebool -P httpd_can_network_connect 1

This Boolean is broader than permission to one arbitrary endpoint. Confirm the applicable domain and policy rather than applying it blindly. For AppArmor, inspect the relevant profile and denial records instead.

Check network namespaces too

In separate Docker containers or Kubernetes Pods, localhost may refer to Nginx's own namespace rather than the backend. A wrong destination often produces refusal or timeout; the exact error also depends on network policy. Verify the address from Nginx's execution environment.

Validate the repaired path

nginx -t && sudo systemctl reload nginx
curl -I https://app.example.com
tail -f /var/log/nginx/error.log

Use the real site and expected response; some routes do not support HEAD requests, so test an appropriate request method too. A persistent 502 requires reading the new upstream error, not assuming the original permission problem remains.

For an important endpoint, configure a Guance HTTP synthetic check from a node that can reach it, with the intended response assertions. Correlate a later failure's timestamp with Nginx and security audit logs.

Frequently asked questions

Why do permissions break again after PHP-FPM restarts?

The service recreates its socket. Manual chmod changes are temporary. Set the socket creator's persistent configuration, such as PHP-FPM's ownership/mode settings or the appropriate systemd socket unit settings.

Why did a Boolean change disappear after reboot?

Without -P, setsebool changes runtime state only. Persist only a policy change that has been reviewed for the intended access.

What changes when Nginx runs in a container?

Host security policy still applies. Check host AVC records, actual process domains, mounted-file labels, and network policy. Docker volume :z/:Z options address volume labeling; they are not general fixes for denied TCP connections.

References

Related reading

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting