Skip to content

fix: use wss:// for WebSocket connections when served over HTTPS#1583

Open
ianbmacdonald wants to merge 4 commits intolemonade-sdk:mainfrom
ianbmacdonald:fix/websocket-protocol-detection
Open

fix: use wss:// for WebSocket connections when served over HTTPS#1583
ianbmacdonald wants to merge 4 commits intolemonade-sdk:mainfrom
ianbmacdonald:fix/websocket-protocol-detection

Conversation

@ianbmacdonald
Copy link
Copy Markdown
Collaborator

Summary

  • WebSocket URLs in the web app were hardcoded to ws://, causing mixed-content errors when Lemonade is served behind an HTTPS reverse proxy
  • Adds a getWebSocketProtocol() helper that derives ws or wss from the server base URL protocol
  • Both WebSocket clients (log streaming and realtime transcription) now use the correct protocol automatically

This is the narrow protocol-only fix. A broader external_url config for full reverse proxy support (including same-origin WebSocket routing) is available on feat/external-url-config and described in #472 (comment).

Test plan

  • Load web app over http:// — WebSocket connections should use ws:// (unchanged behavior)
  • Load web app over https:// via reverse proxy — WebSocket connections should use wss://, no mixed-content errors in browser console
  • Verify log streaming and realtime transcription connect successfully in both modes

Fixes #472

🤖 Generated with Claude Code

@ianbmacdonald
Copy link
Copy Markdown
Collaborator Author

@Geramy here it is with LePremierLaitier's suggested tweak

@ianbmacdonald
Copy link
Copy Markdown
Collaborator Author

@storm1er can you test this?

@ianbmacdonald
Copy link
Copy Markdown
Collaborator Author

root@ai2:~# dpkg -l | grep caddy
ii  caddy                                            2.11.2                                           amd64        Caddy - Powerful, enterprise-ready, open source web server with automatic HTTPS written in Go
root@ai2:~# cat /etc/caddy/Caddyfile 
https://192.168.79.18:8443 {
	tls internal
	reverse_proxy http://127.0.0.1:13305
}

Before

ii  lemonade-server                                  10.1.0-14-g97ccf8a1~24.04                        amd64        Local LLM serving with GPU and NPU acceleration server
image After

..lol I see to use this fix, you need to fake the port the frontend sees from /health .. so this fixes wss, but needs some iptables foo to get it working. thankfully linux is a network swiss army knife

tada
image

  • Browser → https://192.168.79.18:8443 → Caddy → lemonade HTTP on 127.0.0.1:13305
  • Browser → wss://192.168.79.18:9000 → iptables → Caddy on 9443 (TLS) → lemonade WS on 127.0.0.1:9000
  • /health reports websocket_port: 9000 — matches what the browser hits

lemonade config set host=127.0.0.1 websocket_port=9000

  • host=127.0.0.1 — binds lemonade to loopback only so Caddy handles all external traffic
  • websocket_port=9000 — pins the WS port (instead of auto) so the proxy target is stable

Caddy Config

root@ai2:~# cat /etc/caddy/Caddyfile 
https://192.168.79.18:8443 {
	tls internal
	reverse_proxy http://127.0.0.1:13305
}

https://192.168.79.18:9443 {
	tls internal

	reverse_proxy /realtime http://127.0.0.1:9000 {
		header_up Connection {http.request.header.Connection}
		header_up Upgrade {http.request.header.Upgrade}
	}

	reverse_proxy /logs/stream http://127.0.0.1:9000 {
		header_up Connection {http.request.header.Connection}
		header_up Upgrade {http.request.header.Upgrade}
	}
}

And iptables foo

iptables -t nat -A PREROUTING -p tcp --dport 9000 -j REDIRECT --to-port 9443

@Geramy
Copy link
Copy Markdown
Member

Geramy commented Apr 8, 2026

root@ai2:~# dpkg -l | grep caddy
ii  caddy                                            2.11.2                                           amd64        Caddy - Powerful, enterprise-ready, open source web server with automatic HTTPS written in Go
root@ai2:~# cat /etc/caddy/Caddyfile 
https://192.168.79.18:8443 {
	tls internal
	reverse_proxy http://127.0.0.1:13305
}

Before

ii  lemonade-server                                  10.1.0-14-g97ccf8a1~24.04                        amd64        Local LLM serving with GPU and NPU acceleration server

image After
..lol I see to use this fix, you need to fake the port the frontend sees from /health .. so this fixes wss, but needs some iptables foo to get it working. thankfully linux is a network swiss army knife

tada image

  • Browser → https://192.168.79.18:8443 → Caddy → lemonade HTTP on 127.0.0.1:13305
  • Browser → wss://192.168.79.18:9000 → iptables → Caddy on 9443 (TLS) → lemonade WS on 127.0.0.1:9000
  • /health reports websocket_port: 9000 — matches what the browser hits

lemonade config set host=127.0.0.1 websocket_port=9000

  • host=127.0.0.1 — binds lemonade to loopback only so Caddy handles all external traffic
  • websocket_port=9000 — pins the WS port (instead of auto) so the proxy target is stable

Caddy Config

root@ai2:~# cat /etc/caddy/Caddyfile 
https://192.168.79.18:8443 {
	tls internal
	reverse_proxy http://127.0.0.1:13305
}

https://192.168.79.18:9443 {
	tls internal

	reverse_proxy /realtime http://127.0.0.1:9000 {
		header_up Connection {http.request.header.Connection}
		header_up Upgrade {http.request.header.Upgrade}
	}

	reverse_proxy /logs/stream http://127.0.0.1:9000 {
		header_up Connection {http.request.header.Connection}
		header_up Upgrade {http.request.header.Upgrade}
	}
}

And iptables foo

iptables -t nat -A PREROUTING -p tcp --dport 9000 -j REDIRECT --to-port 9443

why are we doing iptables? if you go to https://xxx.xxx.xxx.xxx/ it should hit the proxy and redirect to the real host/destination then the web app should automatically pull the browser URI without any acknowledgement of ports, so basically you just pull whatever is in the request uri and apply it to the wss side of things. Thats my understanding at least unless external_url is set then we use that. Does that make sense?

ianbmacdonald and others added 2 commits April 8, 2026 17:33
WebSocket URLs were hardcoded to ws://, causing mixed-content errors
when Lemonade is served behind an HTTPS reverse proxy. Derives the
WebSocket protocol from the server base URL so wss:// is used
automatically on HTTPS deployments.

Fixes lemonade-sdk#472

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ianbmacdonald ianbmacdonald force-pushed the fix/websocket-protocol-detection branch from afd7244 to 0b82025 Compare April 8, 2026 21:34
@ianbmacdonald
Copy link
Copy Markdown
Collaborator Author

ianbmacdonald commented Apr 9, 2026

And iptables foo

iptables -t nat -A PREROUTING -p tcp --dport 9000 -j REDIRECT --to-port 9443

why are we doing iptables? if you go to https://xxx.xxx.xxx.xxx/ it should hit the proxy and redirect to the real host/destination then the web app should automatically pull the browser URI without any acknowledgement of ports, so basically you just pull whatever is in the request uri and apply it to the wss side of things. Thats my understanding at least unless external_url is set then we use that. Does that make sense?

This is because of the limitations on binding the proxy and lemonade to the same port on the same host .. if you wre testing with an external proxy, you could use a slightly different pattern and skip the iptables foo. Basically caddy and lemonade can not both bind to 9000 on the same interface, so we move one and use iptables.

@ianbmacdonald
Copy link
Copy Markdown
Collaborator Author

@Geramy any idea why CI is failing .. best I can tell some kind of crash

@Geramy
Copy link
Copy Markdown
Member

Geramy commented Apr 10, 2026

@Geramy any idea why CI is failing .. best I can tell some kind of crash

so far it looks like a lot of things are "already downloaded"
But no it seems to be some sort of instability in CLI of some sort maybe. i'll look into this more.

@jeremyfowers
Copy link
Copy Markdown
Member

@ianbmacdonald I sent you a long-overdue invite to the project so that you can make branches on the main repo without forking, and can run your own CI automatically.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Frontend does not follow url that loaded the page

3 participants