Switching between 2 NGINX proxy-ed web apps, the II do not work

Ave Nix-ers,
I’m going out of my mind trying to switch from Strfry to Haven, two Nostr relays, both with a NixOS module. Strfry works, Haven works on its own, but behind NGINX with the exact same configuration that works for Strfry, it doesn’t. Technically they work in the exact same way and implement the exact same protocol; I see no reason why I’m getting a 400 Bad Request via NGINX > Haven, while I get the expected HTML with NGINX > Strfry or directly on localhost with Haven.

They both use WebSockets, they both exchange JSON on a specific port (I choose the same switching them, so entirely not touching the vhost config. I put both on 127.0.0.1:21498and NGINX proxyPass with the very same config:

  enableACME = true;
  forceSSL = true;
  default = true;

  locations."/" = {
    proxyPass = "http://127.0.0.1:21498";
    proxyWebsockets = true;
    extraConfig = ''
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_buffering off;
      ''; # extraConfig
  }; # locations."/"

I can connect normally to both via localhost, but with Strfry curl-ing to my hostname I got text/html as expect, while to Haven I got just 400 Bad Requestwith nothing helpful from both NGINX and Haven logs, with Haven NGINX seems to return 400 before even trying to pass something to Haven.

Does anyone have an idea?

Try to digg more via sudo tcpdump -i lo -n -A port 21498 (my Haven local port) I see

> curl -v --http1.1 https://nostr.dom.tld/; echo

GET / HTTP/1.1
Connection: close
Host: nostr.dom.tld
X-Real-IP: XXX.XXX.XXX.XXX
X-Forwarded-Host: nostr.dom.tld
X-Forwarded-For: XXX.XXX.XXX.XXX
X-Forwarded-Proto: https
Connection: upgrade
Host: nostr.dom.tld
X-Real-IP: XXX.XXX.XXX.XXX
X-Forwarded-For: XXX.XXX.XXX.XXX
X-Forwarded-Proto: https
X-Forwarded-Host: nostr.dom.tld
X-Forwarded-Server: kws
User-Agent: curl/8.18.0
Accept: */*

> curl -v --http1.1 http://127.0.0.1:21498

GET / HTTP/1.1
Host: 127.0.0.1:21498
User-Agent: curl/8.18.0
Accept: */*

HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Vary: Origin
Date: Thu, 19 Feb 2026 20:41:19 GMT
Content-Type: text/html; charset=utf-8
Transfer-Encoding: chunked

800
<!DOCTYPE html>
...

While with Strfry

GET / HTTP/1.1
Connection: close
Host: nostr.dom.tld
X-Real-IP: XXX.XXX.XXX.XXX
X-Forwarded-Host: nostr.dom.tld
X-Forwarded-For: XXX.XXX.XXX.XXX
X-Forwarded-Proto: https
Connection: upgrade
Host: nostr.dom.tld
X-Real-IP: XXX.XXX.XXX.XXX
X-Forwarded-For: XXX.XXX.XXX.XXX
X-Forwarded-Proto: https
X-Forwarded-Host: nostr.dom.tld
X-Forwarded-Server: kws
User-Agent: curl/8.18.0
Accept: */*

HTTP/1.1 200 OK
Content-Type: text/html
Access-Control-Allow-Origin: *
Connection: keep-alive
Server: strfry
Content-Length: 6078

<html> 

I guess that’s a Haven problem, but not idea of which, maybe the Connection: close at start, but it’s right per-se so it should works anyway.

I’m open to any idea.

Hi,

This sounds like it could be related to subtle differences in how Haven handles WebSocket upgrades and header validation compared to Strfry, even if the protocol is technically the same. I’d suggest double-checking NGINX’s proxy_http_version, Upgrade, and Connection headers, and also testing with explicit proxy_set_header Connection $connection_upgrade; to ensure compatibility.

I ran into a somewhat similar situation while working on deployment automation and testing workflows using blox scripts, where slight proxy and header handling differences caused unexpected 400 errors. In my case, adjusting WebSocket upgrade handling and request buffering fixed it.

You might also want to try temporarily disabling SSL and ACME to rule out TLS-related issues.

Hope this helps!

1 Like

Hi and thanks for the reply!

I actually tried replicating the NGINX headers via curl directly on localhost; if I pass -H "Upgrade: websocket", it fails no matter what else I do, but if I don’t pass it, it works. It still works with -H "Connection: Upgrade" and every other header passed manually via curl… Even inserting -H "Sec-WebSocket-Key: SomeGarbageString” -H “Sec-WebSocket-Version: 13” without -H “Upgrade: websocket” works…

proxy_http_version is 1.1, I don’t think I can set it for a specific vhost BTW.

curl -v --http1.1 -H "Host: nostr.dom.tld"  \
                  -H "Upgrade: websocket"  \
                  -H "Connection: Upgrade" \
                  http://127.0.0.1:21498

fails while

curl -v --http1.1 -H "Host: nostr.dom.tld"  \
                  -H "Connection: Upgrade" \
                  http://127.0.0.1:21498

works. At this point I’m at a complete loss as to what to do…

1 Like