# Problem connecting to websocket with the client in a different origin than the server

**URL:** https://openziti.discourse.group/t/problem-connecting-to-websocket-with-the-client-in-a-different-origin-than-the-server/4725
**Category:** zrok
**Created:** [June 10, 2025, 1:07pm UTC](https://openziti.discourse.group/t/problem-connecting-to-websocket-with-the-client-in-a-different-origin-than-the-server/4725 "2025-06-10T13:07:17Z")
**Posts on this page:** 14
**Page:** 1

<div class="post-metadata">

### Author: ![giorgiobazzo](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/giorgiobazzo/32/1827_2.png) [@giorgiobazzo](https://openziti.discourse.group/u/giorgiobazzo)
#### Post date: [June 10, 2025, 1:07pm UTC](https://openziti.discourse.group/t/problem-connecting-to-websocket-with-the-client-in-a-different-origin-than-the-server/4725/1 "2025-06-10T13:07:17Z")

</div>

I'm trying to make a simple web application (html+css+js) to monitor the status of servers of other applications that use zrok.

My servers that use zrok support connection upgrade to websocket. So the idea is that the monitor maintains the websocket connection to show the servers that are active or not. As in the image below.

 ![image](https://global.discourse-cdn.com/free1/uploads/netfoundry/original/2X/e/e62043e74f9e885d8886c431e04765efefe42c77.png)

I'm having problems connecting to the websocket using the zrok url when the websocket client has a different origin than the zrok url. When the client is in the same request origin, the connection works perfectly.

Initially I thought it was some browser restriction or CORS of my application on the server. But then I did a test using:  
wss://echo.websocket.org and I was able to connect perfectly even with the client in a different origin, which seems to eliminate a browser restriction.

I also did another test using CURL with zrok's url but specifying some headers:

> curl --include --no-buffer --header "Connection: Upgrade" --header "Upgrade: websocket" --header "Host: [wfwljbgpjt5n.share.zrok.io](http://wfwljbgpjt5n.share.zrok.io)" --header "Origin: [https://wfwljbgpjt5n.share.zrok.io](https://wfwljbgpjt5n.share.zrok.io)" --header "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" --header "Sec-WebSocket-Version: 13" [https://wfwljbgpjt5n.share.zrok.io/](https://wfwljbgpjt5n.share.zrok.io/)

And it also seems to have worked, as I had the following return:

> HTTP/1.1 101 Switching Protocols  
> Date: Tue, 10 Jun 2025 12:42:29 GMT  
> Connection: upgrade  
> Sec-Websocket-Accept: qGEgH3En71di5rrssAZTmtRTyFk=  
> Server: Pca 2.5.2.1  
> Upgrade: websocket
> 
> Warning: Binary output can mess up your terminal. Use "--output -" to tell curl to output it to your terminal anyway,  
> Warning: or consider "--output " to save to a file.

 ![image](https://global.discourse-cdn.com/free1/uploads/netfoundry/original/2X/3/3f99fc69d2fe26f618ded4dce61232d6cc33b83d.png)

So the problem seems to be when you try to use javascript on a page with a different origin like:

> const ws = new WebSocket(wss://wfwljbgpjt5n.share.zrok.io);

I also tried using the --insecure option on the "zrok share" command but had the same problem.

 ![image](https://global.discourse-cdn.com/free1/uploads/netfoundry/original/2X/6/6bd42a4e2608659b19a44ab93acadb8ca17505e5.png)

 ![image](https://global.discourse-cdn.com/free1/uploads/netfoundry/original/2X/7/7ae792e7e10a9a04d04a2ef44cb2758ae334fff5.png)

I'll also leave here the code I'm using to test:  
[Monitor.zip](https://openziti.discourse.group/uploads/short-url/qz7RizqvnLUo9btfPBj1F33bQza.zip) (2.0 KB)

Thanks!

---

<div class="post-metadata">

### Author: ![giorgiobazzo](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/giorgiobazzo/32/1827_2.png) [@giorgiobazzo](https://openziti.discourse.group/u/giorgiobazzo)
#### Post date: [June 10, 2025, 5:08pm UTC](https://openziti.discourse.group/t/problem-connecting-to-websocket-with-the-client-in-a-different-origin-than-the-server/4725/2 "2025-06-10T17:08:38Z")

</div>

I did some more tests, and it seems to be something related to the "host" header, when we use a native Javascript WebSocket instance like:

> const ws = new WebSocket(wss://wfwljbgpjt5n.share.zrok.io);

We don't have parameters to inform custom headers, in this case the **"Host"** header will necessarily be the same as the HTML document opened in the browser.

When we use **curl** , we can customize or hide the request headers, and then I can get the websocket to respond under zrko, Ex:

**This works** (here I hid the **Host** ):

> curl --include --no-buffer --header "Connection: Upgrade" --header "Upgrade: websocket" --header "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" --header "Sec-WebSocket-Version: 13" wss://wfwljbgpjt5n.share.zrok.io/

But if I try to inform a Host header that is different from the Host of the zrok url like:

**This does not work** (here I set the **Host = localhost:8080** ):

> curl --include --no-buffer --header "Connection: Upgrade" --header "Upgrade: websocket" --header "Host: localhost:8080" --header "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" --header "Sec-WebSocket-Version: 13" wss://wfwljbgpjt5n.share.zrok.io/

I get the error:  
**curl: (22) Refused WebSockets upgrade: 200**

**If I leave the Host the same as the zrok url, it also works:**

> curl --include --no-buffer --header "Connection: Upgrade" --header "Upgrade: websocket" --header "Host: [wfwljbgpjt5n.share.zrok.io](http://wfwljbgpjt5n.share.zrok.io)" --header "Sec-WebSocket-Key: SGVsbG8sIHdvcmxkIQ==" --header "Sec-WebSocket-Version: 13" wss://wfwljbgpjt5n.share.zrok.io/

So it seems to me that there is some kind of validation that is requiring that the Host header must be the same on the zrok server side.

How can we solve this?

---

<div class="post-metadata">

### Author: ![TheLumberjack](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/thelumberjack/32/113_2.png) [@TheLumberjack](https://openziti.discourse.group/u/TheLumberjack)
#### Post date: [June 10, 2025, 5:24pm UTC](https://openziti.discourse.group/t/problem-connecting-to-websocket-with-the-client-in-a-different-origin-than-the-server/4725/3 "2025-06-10T17:24:09Z")

</div>

Hi @giorgiobazzo, welcome to the community and to zrok!

I believe you can run zrok in 'caddy' mode and rewrite the header but I haven't tried it myself. Have you had a look at that?

> **[Self-hosting guide for Docker | zrok](https://docs.zrok.io/docs/guides/self-hosting/docker/#caddy-configuration)**

---

<div class="post-metadata">

### Author: ![giorgiobazzo](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/giorgiobazzo/32/1827_2.png) [@giorgiobazzo](https://openziti.discourse.group/u/giorgiobazzo)
#### Post date: [June 10, 2025, 5:32pm UTC](https://openziti.discourse.group/t/problem-connecting-to-websocket-with-the-client-in-a-different-origin-than-the-server/4725/4 "2025-06-10T17:32:24Z")

</div>

Hi @TheLumberjack ,

I don't know about caddy mode.

Today I run it using the command:

zrok share reserved wfwljbgpjt5n --override-endpoint [http://localhost:9496](http://localhost:9496)

How do I use this mode?

Thanks!

---

<div class="post-metadata">

### Author: ![giorgiobazzo](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/giorgiobazzo/32/1827_2.png) [@giorgiobazzo](https://openziti.discourse.group/u/giorgiobazzo)
#### Post date: [June 10, 2025, 6:55pm UTC](https://openziti.discourse.group/t/problem-connecting-to-websocket-with-the-client-in-a-different-origin-than-the-server/4725/5 "2025-06-10T18:55:52Z")

</div>

Hi @TheLumberjack,

According to [WebSocket](https://javascript.info/websocket)

WebSockets are cross-origin by nature, but in my tests I see that this is not possible if we are using zrok.

I think zrok should make this possible, because when I test using other methods such as VPN, reverse proxy, or direct IP, the connection works.

From what I saw in the link you sent I would have to use a custom Self-Host for caddy mode, right? I really wouldn't like to have to do that.

Thanks!

---

<div class="post-metadata">

### Author: ![TheLumberjack](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/thelumberjack/32/113_2.png) [@TheLumberjack](https://openziti.discourse.group/u/TheLumberjack)
#### Post date: [June 10, 2025, 10:49pm UTC](https://openziti.discourse.group/t/problem-connecting-to-websocket-with-the-client-in-a-different-origin-than-the-server/4725/6 "2025-06-10T22:49:58Z")

</div>

Oh my bad, that is the wrong link, I was thinking about using `--backend-mode=caddy` as in `zrok share public --backend-mode=caddy caddy.file.here`. I think with that you'd be able to rewrite the host header example caddy file is here that you'd use: [https://docs.zrok.io/simple\_reverse\_proxy.Caddyfile](https://docs.zrok.io/simple_reverse_proxy.Caddyfile)

---

<div class="post-metadata">

### Author: ![giorgiobazzo](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/giorgiobazzo/32/1827_2.png) [@giorgiobazzo](https://openziti.discourse.group/u/giorgiobazzo)
#### Post date: [June 11, 2025, 12:45pm UTC](https://openziti.discourse.group/t/problem-connecting-to-websocket-with-the-client-in-a-different-origin-than-the-server/4725/7 "2025-06-11T12:45:13Z")

</div>

I tried this approach, but I had the same problem. When the websocket request is created by the browser in a URL from another origin, or with origin = null (in the case of opening the HTML directly from the HD), this request does not even reach my backend.

When I use curl with the "Host" header, for example: --header "Host: [wfwljbgpjt5n.share.zrok.io](http://wfwljbgpjt5n.share.zrok.io)", I can make the request arrive. The problem is that the browser does not allow you to manipulate the websocket client headers like the curl command does.

This seems to be some kind of handling by the API used by the zrok command, which is not redirecting websocket connections with an origin other than the share created.

Another problem is that I cannot use --backend-mode=caddy with zrok share reserved.

---

<div class="post-metadata">

### Author: ![TheLumberjack](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/thelumberjack/32/113_2.png) [@TheLumberjack](https://openziti.discourse.group/u/TheLumberjack)
#### Post date: [June 11, 2025, 11:46pm UTC](https://openziti.discourse.group/t/problem-connecting-to-websocket-with-the-client-in-a-different-origin-than-the-server/4725/8 "2025-06-11T23:46:15Z")

</div>

It's not zrok, it's a problem with any proxied http response. Your client sends an http request to zrok and it needs to use zrok's host header. That's how zrok finds your service. It is proxied to your zrok share where the share creates a normal tcp connection to the target. zrok is sending your original http request untouched. That's the problem. At your share side, you need to rewrite the http request and replace the host header. I thought caddy could do this but I've never done it myself. This doc seems to indicate it's possible but like i said, I've never tried it myself. [reverse\_proxy (Caddyfile directive) — Caddy Documentation](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#examples)

I'll see if anyone has done what you're trying to do if that's not enough info. Cheers

---

<div class="post-metadata">

### Author: ![michael.quigley](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/michael.quigley/32/1123_2.png) [@michael.quigley](https://openziti.discourse.group/u/michael.quigley)
#### Post date: [June 12, 2025, 12:32am UTC](https://openziti.discourse.group/t/problem-connecting-to-websocket-with-the-client-in-a-different-origin-than-the-server/4725/9 "2025-06-12T00:32:33Z")

</div>

There's an example of rewriting the `Host` header using the `caddy` backend here:

> <https://github.com/openziti/zrok/blob/main/etc/caddy/multiple_upstream.Caddyfile>

---

<div class="post-metadata">

### Author: ![giorgiobazzo](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/giorgiobazzo/32/1827_2.png) [@giorgiobazzo](https://openziti.discourse.group/u/giorgiobazzo)
#### Post date: [June 17, 2025, 4:17pm UTC](https://openziti.discourse.group/t/problem-connecting-to-websocket-with-the-client-in-a-different-origin-than-the-server/4725/10 "2025-06-17T16:17:37Z")

</div>

Hello,

Today I'm trying this approach again, as I need a reserved address using the command:

`zrok share reserved wfwljbgpjt5n --override-endpoint caddy.txt`

according to: [zrok/etc/caddy/README.md at main · openziti/zrok · GitHub](https://github.com/openziti/zrok/blob/main/etc/caddy/README.md)

my caddy file is like this:

```auto
# global config must be first
{
    # no listen on 2019/tcp with admin API
    admin off
}

# zrok site block
http:// {
    # Bind to the zrok share
    bind {{ .ZrokBindAddress }}

    # All other traffic goes to 127.0.0.1:9496
    reverse_proxy /* 127.0.0.1:9496 {
        header_up Host wfwljbgpjt5n.share.zrok.io
        header_up X-Real-IP {http.request.header.x-forwarded-for}                                                          
    }
}

```

but sharing is not working, I'm getting the error:

> ERROR zrok/endpoints/proxy.newReverseProxy.func2: error proxying: unsupported protocol scheme ""

 ![image](https://global.discourse-cdn.com/free1/uploads/netfoundry/original/2X/a/ae3aa14ba297724688dc65c242d84afc1cc9f9e1.png)

zrok.exe version is v1.0.4 [3f5db643]

What am I doing wrong?

Thanks again!

PS:

--override-endpoint seems to ignore caddy.txt and doesn't load the settings

---

<div class="post-metadata">

### Author: ![giorgiobazzo](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/giorgiobazzo/32/1827_2.png) [@giorgiobazzo](https://openziti.discourse.group/u/giorgiobazzo)
#### Post date: [June 18, 2025, 12:23am UTC](https://openziti.discourse.group/t/problem-connecting-to-websocket-with-the-client-in-a-different-origin-than-the-server/4725/11 "2025-06-18T00:23:06Z")

</div>

I just tried using:  
zrok share public --backend-mode=caddy

But we had the same problems with websockets, even removing or rewriting the Host header through the caddy file.

When I remove or rewrite the Host header in the request using the CURL command, the websocket works.

So it seems to me that manipulating the Host header in the caddy file has no effect.

I ran it using --verbose and when the Host header is different from the share, no message appears in the console.

When I remove or rewrite the Host header in the request using the CURL command, the messages start to appear.

---

<div class="post-metadata">

### Author: ![giorgiobazzo](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/giorgiobazzo/32/1827_2.png) [@giorgiobazzo](https://openziti.discourse.group/u/giorgiobazzo)
#### Post date: [June 25, 2025, 5:10pm UTC](https://openziti.discourse.group/t/problem-connecting-to-websocket-with-the-client-in-a-different-origin-than-the-server/4725/12 "2025-06-25T17:10:11Z")

</div>

I'm also covering this in more detail and with examples here:

> <https://github.com/openziti/zrok/issues/988>
>
> Problem connecting to websocket with the client in a different origin than the s…erver
> 
> I'm having problems when trying to make connections via websocket, through a zrok share, using javascript in a simple html page. The problem occurs when this page is hosted on a host other than the share, or when trying to open the html from the HD directly in the browser (without using an http server).
> 
> If this same page is loaded using the zrok share, the websocket connection works fine.
> 
> I'll leave here a simple nodejs project (\[websocket\_zrok\_test.zip\](https://github.com/user-attachments/files/20807971/websocket\_zrok\_test.zip)) that can be used to reproduce the problem. It consists of a server, server.js file, which basically waits for webscoket type connections, and also provides access to index.html using a nodejs http server, at the url http://localhost:8080, (ws://localhost:8080 for the websecket)
> 
> To run the project, just install the dependencies:
> \`npm install\`
> And then:
> \`node server.js\`
> 
> !\[Image\](https://github.com/user-attachments/assets/79ee40c2-7c1b-4c76-a94c-744b8f7b0e2c)
> 
> Accessing the url http://localhost:8080 in the browser we have the page:
> 
> !\[Image\](https://github.com/user-attachments/assets/c5f111d3-8ca2-4cb9-bd8d-3ca9a69b3239)
> 
> Then we can connect to the server and send messages.
> 
> So I activate the zrok share:
> 
> \`zrok share reserved cxrtk417ujbh --override-endpoint http://localhost:8080\`
> 
> Accessing the same page through zrok sharing and changing the websocket url to use sharing, everything also works:
> 
> !\[Image\](https://github.com/user-attachments/assets/b8c99a1a-3eed-49de-a9e0-87cac6a4f6ce)
> 
> !\[Image\](https://github.com/user-attachments/assets/12b55866-d48e-4901-aed8-abdef05348b1)
> 
> As I mentioned before, the problem occurs when this same page is not being provided by sharing, but we try to use the sharing url in the websocket, that is, different host and origin:
> 
> !\[Image\](https://github.com/user-attachments/assets/51143070-55ce-4ec7-90ff-3d72cd069045)
> 
> !\[Image\](https://github.com/user-attachments/assets/1f931408-1446-4a48-9c7a-8b33bc7957df)
> 
> In my opinion this should work, because websockets are by nature cross-origin. And we can prove this using wss://echo.websocket.org which works in all situations:
> 
> !\[Image\](https://github.com/user-attachments/assets/eaafc1f9-b8a3-4643-9423-d0ce050ba5c0)
> 
> I have this problem only when using zrok sharing, using other methods everything works as expected.
> 
> Thanks!
> 
> Edited:
> 
> Another test we can do is with the wscat tool, where we can test websocket connections outside the browser and also manipulate the request headers, \*\*which cannot be done with javascript in the browser\*\*. To use wscat is very simple:
> 
> Install it globally:
> \`npm install -g wscat\`
> 
> Then we can run:
> \`wscat -c wss://cxrtk417ujbh.share.zrok.io -H Host:localhost:8080\`
> 
> In the example above I defined the Host header as localhost:8080, to simulate the browser behavior when we access the page at http://localhost:8080. And so we will receive the error:
> 
> \*\*error: Unexpected server response: 200\*\*
> 
> If you enter the same Host as the share:
> 
> \`wscat -c wss://cxrtk417ujbh.share.zrok.io -H Host:cxrtk417ujbh.share.zrok.io\`
> 
> The connection will work.
> 
> So it seems to me that there is some kind of internal comparison in the zrok service that prevents a websocket connection from being redirected to the endpoint when the request's Host header is different from the one being shared.
> 
> !\[Image\](https://github.com/user-attachments/assets/6683b6ab-bcf3-48a8-84d3-17a2db1deffc)

---

<div class="post-metadata">

### Author: ![michael.quigley](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/michael.quigley/32/1123_2.png) [@michael.quigley](https://openziti.discourse.group/u/michael.quigley)
#### Post date: [June 25, 2025, 5:17pm UTC](https://openziti.discourse.group/t/problem-connecting-to-websocket-with-the-client-in-a-different-origin-than-the-server/4725/13 "2025-06-25T17:17:27Z")

</div>

I also responded in the issue on GitHub... but the short version is that `public` shares using the current public frontend implementation require the exact `Host` header of the share to operate correctly. If you send traffic to the `public` frontend with a `Host` header other than the exact name of your share, your traffic will go to `/dev/null`.

_This is unrelated to the `caddy` backend mode stuff mentioned above... we were misunderstanding the problem and assuming your backend was sensitive to the `Host` header. In this case, you're sending traffic to the share public frontend with a `Host` header it doesn't understand._

---

<div class="post-metadata">

### Author: ![michael.quigley](https://yyz2.discourse-cdn.com/free1/user_avatar/openziti.discourse.group/michael.quigley/32/1123_2.png) [@michael.quigley](https://openziti.discourse.group/u/michael.quigley)
#### Post date: [June 25, 2025, 5:19pm UTC](https://openziti.discourse.group/t/problem-connecting-to-websocket-with-the-client-in-a-different-origin-than-the-server/4725/14 "2025-06-25T17:19:41Z")

</div>

If you use `zrok access private`, you can bind a single share token to any number of addresses or DNS names. Each `zrok access private` binds a single share to a specific `ip:port` combination, which you can then access using any DNS/`Host` header that you would like.

If you want to expose this to the internet, it would require standing a `zrok access private` up on a cheap/free VPS.

There is a video that describes the approach here:

[![](https://global.discourse-cdn.com/free1/uploads/netfoundry/original/2X/a/a0d3802eeca03859779cdcfc0f7f8d2ce73419ba.jpeg "zrok Office Hours: Personalized Frontend") ](https://www.youtube.com/watch?v=LrD8OeZivRY)
