NginxProxyManager / NginxProxyManager/nginx-proxy-manager

WebSocket connections fail over HTTPS (HTTP/2) — 400 Bad Request from backend

Open
#5,503 2 comments 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
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

  1. Create a Proxy Host with scheme https, enable Websockets Support and SSL with HTTP/2 enabled
  2. Backend serves HTTPS and supports HTTP/2
  3. 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

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.