Fixing Node.js + Nginx “Permission Denied” (13) Error When Connecting to Upstream on Restricted Ports


2 views

Recently while setting up multiple Node.js applications behind Nginx on CentOS 7, I encountered a puzzling scenario where some ports worked perfectly while others threw 502 Bad Gateway errors. The Nginx error logs revealed:

[crit] 12807#0: *13 connect() to 127.0.0.1:7777 failed (13: Permission denied)

Strangely, the same application worked flawlessly when configured to use port 8008.

The root cause lies in SELinux's port restrictions. On CentOS/RHEL systems, SELinux by default only allows certain ports for HTTP communication. You can check the allowed ports with:

semanage port -l | grep http
http_port_t      tcp      80, 81, 443, 488, 8008, 8009, 8443, 9000

Notice how 7777 isn't in this list while 8008 is? That explains our different behavior.

Option 1: Add Your Port to http_port_t

The most secure approach is to formally add your port to SELinux's allowed set:

sudo semanage port -a -t http_port_t -p tcp 7777

Option 2: Temporarily Disable SELinux Enforcement

For development environments only (not recommended for production):

sudo setenforce 0

Option 3: Modify Your Node.js Application

Simply switch to a standard HTTP port that SELinux already allows:

const port = process.env.PORT || 8008;
server.listen(port, () => {
    console.log(Server running on port ${port});
});

After applying any solution, verify Nginx can connect to your upstream:

curl -I http://localhost:7777
sudo ausearch -m avc -ts recent

For production environments, I recommend:

  • Always use Option 1 (adding ports properly)
  • Keep SELinux in enforcing mode
  • Consider using reverse proxy headers in your Node app:
app.enable('trust proxy');
app.use((req, res, next) => {
    req.headers['x-forwarded-proto'] === 'https' 
        ? next() 
        : res.redirect('https://' + req.headers.host + req.url);
});

Here's a complete Nginx configuration that works with SELinux:

server {
    listen 80;
    server_name myapp.com;
    
    location / {
        proxy_pass http://127.0.0.1:8008;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

When configuring Nginx as a reverse proxy for Node.js applications on CentOS 7, you might encounter this frustrating situation where certain ports work perfectly (like 8008) while others (like 7777) throw 13: Permission denied errors in the Nginx error logs.

In CentOS/RHEL systems, SELinux enforces strict security policies. By default, non-standard ports (anything outside common web ports like 80, 443, 8000-9000) are blocked from network communication unless explicitly permitted.

Check SELinux status with:

sestatus
getenforce

To see if your problematic port (7777) has the correct context:

semanage port -l | grep http

You'll likely see output showing allowed ports like:

http_port_t      tcp      80, 81, 443, 488, 8008, 8009, 8443

Add your custom port to SELinux's allowed list:

sudo semanage port -a -t http_port_t -p tcp 7777

For multiple ports:

sudo semanage port -a -t http_port_t -p tcp 7777,8888,9999

If you prefer not to modify SELinux policies (not recommended for production):

Temporarily set SELinux to permissive mode:

sudo setenforce 0

Or disable SELinux enforcement (strongly discouraged):

sudo sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config

Ensure your Nginx config includes proper proxy settings:

location / {
    proxy_pass http://127.0.0.1:7777;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection 'upgrade';
    proxy_set_header Host $host;
    proxy_cache_bypass $http_upgrade;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

After making changes, always:

  1. Restart Nginx: sudo systemctl restart nginx
  2. Check SELinux logs: sudo ausearch -m avc -ts recent
  3. Test connectivity: curl -v http://localhost:7777

Don't forget about firewalld on CentOS 7:

sudo firewall-cmd --zone=public --add-port=7777/tcp --permanent
sudo firewall-cmd --reload