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 wsThe server sends a greeting when a client connects. It echoes each message back.
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.mjsStart a tunnel
Horizon passes WebSockets through. Point the tunnel at the HTTP address of the server.
hrzn tunnel http://localhost:3000 -s my-appHORIZON: Tunnel connected
URL https://my-app.hrzn.run (reserved)
Forwarding http://localhost:3000
Request log https://hrzn.run/dashboard/tunnels/my-appDrop -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.
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.mjsCheck it works
The client prints:
received: connected
received: echo: helloThe server prints:
received: helloIn a browser, new WebSocket("wss://my-app.hrzn.run") connects the same way.
Troubleshooting
The connection fails
- Use
wss://, notws://, with the tunnel URL. - Check that the Horizon terminal is still running. If its last line is
Connection lost. Reconnecting…, wait forReconnected. - Check that the server runs on the port in the
hrzn tunnelcommand.
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
Test a mobile app against your local API
Point an iOS, Android, React Native or Expo app at your local API through a Horizon HTTPS tunnel, without device network setup.
Stream Server-Sent Events through a tunnel
Stream Server-Sent Events from a local Next.js route handler through a Horizon tunnel, and watch them arrive with curl -N.