NginxProxyManager / NginxProxyManager/nginx-proxy-manager
WebSocket connections fail over HTTPS (HTTP/2) — 400 Bad Request from backend
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 34.2k
- Forks
- 3.9k
- Avg merge
- 21h 12m
- Merged PRs (30d)
- 20
Description
Description
WebSocket connections through NPM fail when using HTTPS, returning 400 Bad Request from the backend. The same connection works correctly over HTTP. The root cause is that NPM negotiates HTTP/2 with the client
on HTTPS, but the upstream proxy_pass also uses HTTP/2 (when backend supports it), which breaks the classic WebSocket upgrade mechanism (Connection: Upgrade / Upgrade: websocket headers are not valid in
HTTP/2).
Environment
- NPM version: 2.14.0 (Docker: jc21/nginx-proxy-manager:2.14.0)
- Backend: NanoKVM (HTTPS, supports both HTTP/1.1 and HTTP/2)
- WebSocket endpoint: wss://example.com/api/ws
- Proxy Host config: Scheme https, Websockets Support enabled
Steps to Reproduce
- Create a Proxy Host with scheme https, enable Websockets Support and SSL with HTTP/2 enabled
- Backend serves HTTPS and supports HTTP/2
- Attempt a WebSocket connection:
FAILS — curl negotiates HTTP/2 over HTTPS
curl -k -i -N
-H "Connection: Upgrade"
-H "Upgrade: websocket"
-H "Sec-WebSocket-Version: 13"
-H "Sec-WebSocket-Key: dGVzdA=="
https://example.com/api/ws
Response:
HTTP/2 400
content-type: text/plain; charset=utf-8
sec-websocket-version: 13
content-length: 12
Bad Request
Expected Behavior
WebSocket upgrade should succeed with 101 Switching Protocols.
Workarounds That Confirm the Cause
Workaround 1: Force HTTP/1.1 on the client side — works:
curl -k -i -N --http1.1
-H "Connection: Upgrade"
-H "Upgrade: websocket"
-H "Sec-WebSocket-Version: 13"
-H "Sec-WebSocket-Key: dGVzdA=="
https://example.com/api/ws
Response:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: upgrade
Sec-WebSocket-Accept: ...
Workaround 2: Use HTTP instead of HTTPS — works (because HTTP doesn't negotiate HTTP/2).
Workaround 3: Disable HTTP/2 Support in the SSL tab — works but disables HTTP/2 for all traffic.
Root Cause Analysis
When Websockets Support is enabled, NPM adds:
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
But it does not add:
proxy_http_version 1.1;
Without proxy_http_version 1.1;, Nginx may use HTTP/2 for the upstream connection (when the backend supports it). In HTTP/2, the Connection and Upgrade headers are prohibited (RFC 7540 §8.1.2.2), so the
WebSocket handshake never reaches the backend correctly.
Suggested Fix
When Websockets Support is toggled on, NPM should also inject proxy_http_version 1.1; into the generated location block. This ensures the upstream connection uses HTTP/1.1, which is required for the classic
WebSocket upgrade handshake.
Note: Adding proxy_http_version 1.1; manually in the Custom Nginx Configuration (Advanced tab) at the server level causes a config syntax error and sets the host Offline, because this directive is only valid
inside a location block.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by locating the generated location block used when Websockets Support is enabled, then reproduce the failure with the provided curl command and inspect the resulting Nginx configuration. Done means the HTTPS WebSocket handshake returns 101 while HTTP/2 remains available for other traffic and the generated configuration stays valid.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- nginx, typescript
- Domain
- api, backend
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 58/100