n8n Behind nginx: 'Connection lost' and 'Invalid origin!' on a Non-Default Port ($host vs $http_host)
Invalid origin!Origin header does NOT match the expected origin.Connection lostShort 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.

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/pushWebSocket (0WebSockets), so neitherInvalid 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
Hostis127.0.0.1:5679andOriginishttp://127.0.0.1:5679(matching port), n8n returns101 Switching Protocolsand keeps the WebSocket open untilcurlhits its timeout (curl exit 28). - When the same direct request is sent with
Origin: http://127.0.0.1(port dropped), n8n returnsInvalid 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):
$hostevaluates to the host name from the request line orHostheader without the port number (repro.local).$http_hostevaluates to the rawHostrequest 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/Connectionheaders in nginx (C2comparison): Not tested in this run;proxy_set_header Upgrade $http_upgrade;andproxy_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.
Related
- n8n Webhook Returns 404 Behind nginx (Sub-Path Proxy): that article’s config uses
Host $hoston port8081; it covers webhooks only, and the editor on that setup hits this error. - n8n’s Secure Cookie Warning Behind nginx: the same HTTPS-on-
8443setup, opened over plain HTTP.