n8n Behind nginx: 'Connection lost' and 'Invalid origin!' on a Non-Default Port ($host vs $http_host)

Error message WebSocket text frame on /rest/push, then close code 1008
Invalid origin!
Error message n8n container log
Origin header does NOT match the expected origin.
Error message n8n editor — workflow canvas
Connection lost

Short answer: With n8n behind nginx on a non-default port (8443 in our test), proxy_set_header Host $host; drops the port from Host, while the browser's Origin keeps :8443. n8n rejects the /rest/push WebSocket with Invalid origin! (close code 1008) and the workflow canvas shows "Connection lost". Changing it to proxy_set_header Host $http_host; keeps the port and fixed it in all four toggle rounds.

Request path diagram

Symptom

When accessing n8n through nginx over HTTPS on a non-default port (https://repro.local:8443), signing in succeeds (loginStatus: 200), but opening a workflow in the editor shows a “Connection lost” state on the canvas ("connectionLostVisible":true).

Note where this symptom appears in n8n 2.44.7:

  • The workflows overview page (/home/workflows) never opens a /rest/push WebSocket (0 WebSockets), so neither Invalid origin! nor "Connection lost" appears there.
  • Immediately after login, / lands on /assistant, where the push WebSocket does connect, but no "Connection lost" banner is shown.
  • “Connection lost” appears only on the workflow canvas (/workflow/<id>), so you must open an actual workflow to see the UI warning.

On the workflow canvas, the browser repeatedly attempts to connect to wss://repro.local:8443/rest/push—opening 20 WebSocket connections in 15 seconds (~1.3/s) with the same pushRef. Each socket receives the text frame Invalid origin! and is closed. Probe output (it lists only two of the 20 ws entries):

--- A. probe
{"label":"2-broken","secureCookieWarning":false,"loginStatus":200,"finalUrl":"https://repro.local:8443/signin?redirect=%252F","postLoginUrl":"https://repro.local:8443/assistant","editorUrl":"https://repro.local:8443/workflow/UCxBiJ4TRJmiTuqO","wsCount":20,"connectionLostVisible":true,"ws":[{"url":"wss://repro.local:8443/rest/push?pushRef=a57py7l4wn","frames":["<< Invalid origin!"],"closed":true},{"url":"wss://repro.local:8443/rest/push?pushRef=a57py7l4wn","frames":["<< Invalid origin!"],"closed":true}]}

At the same time, the n8n container logged this error (three lines captured during the broken round; not one per socket):

--- B. n8n log since round start
n8n-1  | Origin header does NOT match the expected origin. (Origin: "https://repro.local:8443" -> "repro.local:8443", Expected: "repro.local" -> "repro.local", Protocol: "https")
n8n-1  | Origin header does NOT match the expected origin. (Origin: "https://repro.local:8443" -> "repro.local:8443", Expected: "repro.local" -> "repro.local", Protocol: "https")
n8n-1  | Origin header does NOT match the expected origin. (Origin: "https://repro.local:8443" -> "repro.local:8443", Expected: "repro.local" -> "repro.local", Protocol: "https")

Confirm Which Layer

First, inspect the raw WebSocket handshake through nginx. Even with Upgrade and Connection headers configured in nginx, the HTTP upgrade itself returns HTTP/1.1 101 Switching Protocols, followed immediately by WebSocket text frame 81 0f ("Invalid origin!") and close frame 88 02 03 f0 (WebSocket close code 1008):

--- C. raw WS handshake via nginx
login 200
000000 48 54 54 50 2f 31 2e 31 20 31 30 31 20 53 77 69  >HTTP/1.1 101 Swi<
000010 74 63 68 69 6e 67 20 50 72 6f 74 6f 63 6f 6c 73  >tching Protocols<
000020 0d 0a 53 65 72 76 65 72 3a 20 6e 67 69 6e 78 2f  >..Server: nginx/<
000030 31 2e 32 37 2e 35 0d 0a 44 61 74 65 3a 20 54 68  >1.27.5..Date: Th<
000040 75 2c 20 32 39 20 4f 63 74 20 32 30 32 36 20 31  >u, 29 Oct 2026 1<
000050 37 3a 35 32 3a 32 37 20 47 4d 54 0d 0a 43 6f 6e  >7:52:27 GMT..Con<
000060 6e 65 63 74 69 6f 6e 3a 20 75 70 67 72 61 64 65  >nection: upgrade<
000070 0d 0a 55 70 67 72 61 64 65 3a 20 77 65 62 73 6f  >..Upgrade: webso<
000080 63 6b 65 74 0d 0a 53 65 63 2d 57 65 62 53 6f 63  >cket..Sec-WebSoc<
000090 6b 65 74 2d 41 63 63 65 70 74 3a 20 73 33 70 50  >ket-Accept: s3pP<
0000a0 4c 4d 42 69 54 78 61 51 39 6b 59 47 7a 7a 68 5a  >LMBiTxaQ9kYGzzhZ<
0000b0 52 62 4b 2b 78 4f 6f 3d 0d 0a 0d 0a 81 0f 49 6e  >RbK+xOo=......In<
0000c0 76 61 6c 69 64 20 6f 72 69 67 69 6e 21 88 02 03  >valid origin!...<
0000d0 f0 0a                                            >..<
0000d2
curl exit: 28

Second, test n8n directly on 127.0.0.1:5679 without nginx to confirm what n8n is comparing:

  • When Host is 127.0.0.1:5679 and Origin is http://127.0.0.1:5679 (matching port), n8n returns 101 Switching Protocols and keeps the WebSocket open until curl hits its timeout (curl exit 28).
  • When the same direct request is sent with Origin: http://127.0.0.1 (port dropped), n8n returns Invalid origin!.
# D. direct n8n baseline (no nginx), Host/Origin = 127.0.0.1:5679
direct login 200
cookie jar secure flags: 
-- without manual Cookie header (curl withholds Secure cookie on http):
HTTP/1.1 101 Switching Protocols
-- with Cookie header passed manually (value not printed):
000000 48 54 54 50 2f 31 2e 31 20 31 30 31 20 53 77 69  >HTTP/1.1 101 Swi<
000010 74 63 68 69 6e 67 20 50 72 6f 74 6f 63 6f 6c 73  >tching Protocols<
000080 0a                                               >.<
000081
curl exit 28
-- direct, mismatched Origin (port dropped) as control:
.
..Invalid origin!....

This shows the check itself is Host vs Origin (host and port); nginx matters only because $host drops the port from Host before forwarding.

Cause

In the broken nginx configuration (2-broken.conf), the proxy forwards Host using $host:

# state: 2-broken.conf
server {
  listen 8081;
  server_name repro.local;
  location / {
    return 301 https://$host:8443$request_uri;
  }
}
server {
  listen 8443 ssl;
  server_name repro.local;
  ssl_certificate     /etc/nginx/certs/repro.crt;
  ssl_certificate_key /etc/nginx/certs/repro.key;
  location / {
    proxy_pass http://n8n:5678;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_read_timeout 3600s;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

As general nginx mechanism knowledge (documented in the nginx variables reference and consistent with the n8n log output above):

  • $host evaluates to the host name from the request line or Host header without the port number (repro.local).
  • $http_host evaluates to the raw Host request header exactly as sent by the client, including the non-default port (repro.local:8443).

When nginx uses proxy_set_header Host $host;, n8n receives Host: repro.local alongside the browser’s Origin: https://repro.local:8443. As shown in n8n’s log (Origin: "https://repro.local:8443" -> "repro.local:8443", Expected: "repro.local" -> "repro.local"), the expected value (taken from Host) has no :8443, so it does not match the origin, and n8n closes the WebSocket with Invalid origin!.

The One Change

In the nginx location / block on port 8443, change proxy_set_header Host $host; to proxy_set_header Host $http_host; and reload nginx:

-    proxy_set_header Host $host;
+    proxy_set_header Host $http_host;

Verify

With proxy_set_header Host $http_host; active (2-fixed.conf), the Playwright browser probe logs in, opens the workflow canvas, and stays connected (1–2 WebSockets opened, kept alive, receiving collaboratorsChanged events, with "connectionLostVisible":false and no origin error lines in the n8n log):

--- out/10-2-fixed.txt (excerpt: probe summary)
"label":"2-fixed","secureCookieWarning":false,"loginStatus":200,"finalUrl":"https://repro.local:8443/signin?redirect=%252F","postLoginUrl":"https://repro.local:8443/assistant","editorUrl":"https://repro.local:8443/workflow/UCxBiJ4TRJmiTuqO","wsCount":2,"connectionLostVisible":false,"ws":[{"url":"wss://repro.local:8443/rest/push?pushRef=mq8fl6eqj… (excerpt truncated)

Toggling between 2-broken.conf (Host $host) and 2-fixed.conf (Host $http_host) across four consecutive rounds reproduced and resolved the error every time (secureCookieWarning=false and loginStatus=200 held in every round):

Round nginx Config Browser Push WebSocket "Connection lost" Visible on Canvas Raw curl Handshake
2-broken Host $host 20 WS in 15 s, each gets text frame Invalid origin! then closed true 101 → 81 0f "Invalid origin!" → 88 02 03 f0 (close code 1008)
2-fixed Host $http_host 1–2 WS, stay open, receive collaboratorsChanged false 101, no frames, curl times out (exit 28)
2-broken-again Host $host same as broken true same as broken
2-fixed-again Host $http_host same as fixed false same as fixed

Minimal Working Config

# state: 2-fixed.conf
server {
  listen 8081;
  server_name repro.local;
  location / {
    return 301 https://$host:8443$request_uri;
  }
}
server {
  listen 8443 ssl;
  server_name repro.local;
  ssl_certificate     /etc/nginx/certs/repro.crt;
  ssl_certificate_key /etc/nginx/certs/repro.key;
  location / {
    proxy_pass http://n8n:5678;
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_read_timeout 3600s;
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-Proto $scheme;
  }
}

Untested / Not Covered

  • Missing Upgrade / Connection headers in nginx (C2 comparison): Not tested in this run; proxy_set_header Upgrade $http_upgrade; and proxy_set_header Connection "upgrade"; were present throughout all four test rounds. We make no claim here about what error message or status code appears when those headers are omitted.