An Nginx 502 Bad Gateway when terminating SSL and proxying WebSocket traffic in aaPanel occurs when Nginx fails to establish or maintain the upstream TCP connection. This is caused by missing HTTP/1.1 upgrade headers (stripping Upgrade and Connection on hop-by-hop transitions), the upstream daemon not listening on the specified loopback interface or port, or an SSL handshake mismatch (such as proxying via https:// upstream when the backend is expecting plain HTTP).
Root Cause Diagnosis
aaPanel’s standard GUI Reverse Proxy template defaults to standard HTTP/1.0-style reverse proxy directives that drop dynamic connection upgrade headers, causing WebSocket handshakes (101 Switching Protocols) to drop immediately. Additionally, if aaPanel manages the frontend SSL via Let’s Encrypt while the backend app runs plain HTTP on an internal port (e.g., 127.0.0.1:8080), configuring the proxy target scheme as https:// forces an invalid upstream TLS handshake that immediately throws an Nginx 502 Bad Gateway.
Quick Fix
Open the target site configuration in aaPanel or terminal, find the upstream location / block, and inject WebSocket upgrade directives and HTTP/1.1 support:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
Reload Nginx immediately:
nginx -t && nginx -s reload
Step-by-Step Resolution
- Verify Backend Daemon Status and Binding Ensure the backend WebSocket application is actually active and bound to the loopback interface:
ss -tulpn | grep :<YOUR_APP_PORT>
If the process binds exclusively to 127.0.0.1, ensure your proxy passes to [http://127.0.0.1](http://127.0.0.1):<PORT> and not http://localhost:<PORT> (to avoid IPv6 ::1 resolution failures). 2. Define Connection Header Mapping Ensure the global Nginx HTTP block maps empty upgrade headers properly. Check or edit /www/server/nginx/conf/nginx.conf within the http { ... } block:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
- Configure the Site Reverse Proxy in aaPanel Edit the site-specific configuration located at: /www/server/panel/vhost/nginx/yourdomain.com.conf Locate or add the dedicated WebSocket proxy block:
location / {
proxy_pass http://127.0.0.1:8080; # Must match backend port and protocol (http vs https)
proxy_http_version 1.1;
# WebSocket upgrade headers
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
# Standard reverse proxy preservation
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeout adjustments for persistent WebSocket connections
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
proxy_connect_timeout 60s;
}
- Verify Frontend SSL Directives In the same virtual host file, verify that the SSL block is listening on port 443 with valid certificates:
listen 443 ssl http2;
ssl_certificate /www/server/panel/vhost/cert/yourdomain.com/fullchain.pem;
ssl_certificate_key /www/server/panel/vhost/cert/yourdomain.com/privkey.pem;
- Test Configuration and Apply Execute via CLI:
nginx -t && nginx -s reload
Why This Fails for Some Users
- aaPanel GUI Overrides: Editing the proxy directly within aaPanel’s “Reverse Proxy” GUI tab can regenerate the site configuration, wiping custom
proxy_set_header Upgradeparameters. Edits must either use the panel’s “Config” tab directly or maintain a dedicated separateincludefile. - IPv6 Resolution Mismatch (
localhostvs127.0.0.1): Specifyingproxy_pass http://localhost:PORT;fails when/etc/hostsprioritizes::1forlocalhost, but the upstream application is bound strictly to the IPv4 address127.0.0.1. - Upstream Protocol Mismatch: Setting
proxy_pass [https://127.0.0.1](https://127.0.0.1):PORT;when the backend process (Node.js, Python ASGI, Go) expects plain HTTP. Unless upstream runs its own TLS certificate,proxy_passmust usehttp://. - WAF/Security Plugin Interception: Security modules like aaPanel Nginx Free WAF, ngx_lua_waf, or fail2ban may classify open WebSocket HTTP
101 Switching Protocolshandshakes or persistent long-polling connections as abnormal requests or slowloris attacks and terminate the connection upstream. - Buffer and Timeout Terminations: Default
proxy_read_timeout(60 seconds) causes Nginx to sever idle WebSocket connections if no ping/pong keepalive heartbeat is transmitted across the socket within that window.