Horizon

Expose a local WebSocket server

Expose a local WebSocket server with a Horizon tunnel and connect to it over wss from a browser, a phone or another machine.

Reach a WebSocket server on your laptop from anywhere, over a secure wss:// URL.

Before you begin

  • Node.js 18 or later
  • A Horizon account and the CLI (see Getting started)
  • Optional: a reserved subdomain for -s. Reserve one on the Subdomains page.

Start a WebSocket server

Create a project and install ws.

mkdir ws-demo && cd ws-demo
npm init -y
npm install ws

The server sends a greeting when a client connects. It echoes each message back.

server.mjs
import { WebSocketServer } from "ws";

const server = new WebSocketServer({ port: 3000 });

server.on("connection", (socket) => {
  socket.on("error", console.error);
  socket.on("message", (data) => {
    console.log("received: %s", data);
    socket.send(`echo: ${data}`);
  });
  socket.send("connected");
});

Run it.

node server.mjs

Start a tunnel

Horizon passes WebSockets through. Point the tunnel at the HTTP address of the server.

hrzn tunnel http://localhost:3000 -s my-app
Output
HORIZON: Tunnel connected
  URL          https://my-app.hrzn.run (reserved)
  Forwarding   http://localhost:3000
  Request log  https://hrzn.run/dashboard/tunnels/my-app

Drop -s my-app for a free random subdomain. It changes every run. Reserved subdomains are a paid feature, see Pricing.

Connect over wss

Use wss:// with the tunnel's hostname. The tunnel serves HTTPS, so use wss://, not ws://. This client uses the same ws package.

client.mjs
import WebSocket from "ws";

const socket = new WebSocket("wss://my-app.hrzn.run");

socket.on("error", console.error);
socket.on("open", () => socket.send("hello"));
socket.on("message", (data) => {
  console.log("received: %s", data);
});
node client.mjs

Check it works

The client prints:

Output
received: connected
received: echo: hello

The server prints:

Output
received: hello

In a browser, new WebSocket("wss://my-app.hrzn.run") connects the same way.

Troubleshooting

The connection fails

  • Use wss://, not ws://, with the tunnel URL.
  • Check that the Horizon terminal is still running. If its last line is Connection lost. Reconnecting…, wait for Reconnected.
  • Check that the server runs on the port in the hrzn tunnel command.

Visitors see a Before you continue page

A browser that opens the tunnel URL as a page sees it once every 7 days per IP address. They select Continue to site. Requests with an x-hrzn-skip-warning header or a non-browser user agent skip it.

The URL changed after a restart

You started the tunnel without -s. Restart with -s my-app. -s needs a subdomain you reserved, see Pricing.

Next steps

On this page