Skip to main content
← Back to list
01Issue
BugClosedSwamp CLITeam
AssigneesNone

Relationships

#1363 Remote --server wss:// fails through an HTTP/2 TLS reverse proxy (WS client ALPN is h2-only)

Opened by magistr · 7/23/2026

Summary

swamp <cmd> --server wss://<host> --token <name>.<secret> cannot open a WebSocket to swamp serve when serve sits behind a TLS reverse proxy that advertises HTTP/2 via ALPN (Traefik v3, and likely nginx/Caddy with http2 enabled). The client's TLS ClientHello appears to offer h2 without http/1.1, so the wss handshake either negotiates h2 — which has no WebSocket upgrade mechanism — or fails ALPN outright. Plain ws:// (no TLS) works end to end.

This is the reverse-proxy deployment that PR #1717 ("skip Host header validation on loopback bind to support reverse proxy deployments") was meant to enable: the serve side now accepts proxied requests, but the client still can't complete the wss handshake through an h2-capable TLS proxy.

Environment

  • swamp 20260722.210030.0-sha.7ff1e2f0 (both client and serve)
  • serve: swamp serve --auth-mode token --admins ... --host [IP-1] --port 9091 --trust-proxy (binds loopback)
  • proxy: Traefik v3.3 terminating TLS (Let's Encrypt cert), ALPN advertising h2 + http/1.1 on :443, forwarding to serve

Reproduction / observed

  1. Proxy ALPN = {h2, http/1.1} (Traefik default): client error -> NetworkError: failed to connect to WebSocket: stream error received: unspecific protocol error detected (an HTTP/2 stream error — the client negotiated h2)
  2. Proxy TLS option pinned to alpnProtocols=[http/1.1]: client error -> received fatal alert: NoApplicationProtocol (the client's ClientHello ALPN does NOT contain http/1.1, so there is no common protocol)
  3. curl --http1.1 performing the WS upgrade to the same URL reaches serve correctly — 401 without a token, 101/authenticated with a valid one. So the proxy and serve are both fine.
  4. Plain ws:// to serve (no TLS/ALPN, via an SSH tunnel) works end to end: a valid token authenticates and the command returns results; a bogus token returns 401.

So serve, the proxying, and token auth are all correct — only the client's wss ALPN is incompatible with an h2-capable TLS proxy.

Expected

A WebSocket client connecting over wss:// should offer http/1.1 in its TLS ALPN (WebSocket is an HTTP/1.1 Upgrade), so it interoperates with standard h2-capable TLS reverse proxies — the exact deployment #1717 enabled on the serve side.

Likely fix

In the client's wss WebSocket / TLS connection setup, set ALPN to include (or restrict to) http/1.1 for --server wss:// connections (Deno WebSocket-over-TLS / Deno.connectTls alpnProtocols).

Impact

Blocks all remote --server wss:// usage through a TLS reverse proxy — the intended production shape for a hosted serve. Current workarounds are a non-h2 TCP-passthrough entrypoint or a plain-ws tunnel. Likely the same root cause behind earlier "remote --server blocked through a proxy" reports.

02Bog Flow
OPENTRIAGEDIN PROGRESSCLOSED

Closed

7/23/2026, 10:42:52 PM

No activity in this phase yet.

03Sludge Pulse
Editable. Press Enter to edit.

shelson commented 7/23/2026, 8:39:37 AM

I've seen this behaviour too, but i didn't get it written up nearly this well - thanks!

stack72 commented 7/23/2026, 10:42:51 PM

This was the same issue as 1345 - it has been fixed by https://github.com/swamp-club/swamp/pull/1941

dougschaefer commented 7/29/2026, 4:48:43 PM

Still reproduces on 20260729.160541.0-sha.2ab9ce7e (both client and serve), which should include PR #1941. Reporting because this instance differs from the original in a way that may matter for triage: there is no reverse proxy involved at all. This is a client talking directly to swamp serve's own TLS listener.

Setup

  • serve: swamp serve --host <private-ip> --port 9090 --auth-mode token --admins user:<id> --cert-file /etc/swamp/tls.crt --key-file /etc/swamp/tls.key
  • self-signed cert, SANs cover the IP and hostname
  • client: same binary version, same LAN, reaching the host over a site-to-site VPN
  • token registered via swamp auth server-login — succeeded, so the credential path is fine

Observed

swamp model method run <model> <method> --server wss://<host>:9090
{ "error": "Could not connect to wss://<host>:9090/: NetworkError: failed to connect to WebSocket: HTTP/2 not supported by this client" }

Note the error string differs from the two in the original report — this one comes back from the client after it has negotiated h2.

ALPN measured directly against serve (not a proxy)

client offers http/1.1     -> ALPN protocol: http/1.1
client offers h2           -> ALPN protocol: h2
client offers h2,http/1.1  -> ALPN protocol: h2      <-- server prefers h2

So swamp serve's own TLS listener advertises both and prefers h2 when a client offers both. Whatever the WS client is offering, the negotiation lands on h2 and then fails.

Why there is no way around it from the operator side

  1. TLS is mandatory for any off-loopback bind — Error: Off-loopback binding requires TLS — provide --cert-file and --key-file, or bind to 127.0.0.1.
  2. swamp serve exposes no ALPN or HTTP-version flag (only --cert-file, --key-file, --ws-idle-timeout).

Taken together, a TLS-enabled swamp serve reachable from any other host cannot be reached by a swamp client. The documented --server workflow appears unusable over TLS unless serve is bound to loopback and tunnelled, which is what the original reporter fell back to.

Happy to run any diagnostic build against this deployment — it is a stable environment and easy to iterate on.

Sign in to post a ripple.