Horizon

Host a web app behind CGNAT or Starlink

Host a web app from home behind CGNAT or Starlink with an outbound Horizon tunnel, when port forwarding isn't possible.

Serve a web app from your home network at https://my-app.hrzn.run, even when your ISP gives you no public IP address and you can't forward a port.

Why port forwarding fails

Carrier-grade NAT (CGNAT) puts many customers behind one shared public IP address. Your router never gets a public address of its own, so forwarding a port on it reaches nothing. Some ISPs, mobile networks and satellite services such as Starlink work this way.

A tunnel avoids the problem. The Horizon CLI connects out from your machine to Horizon, and outbound connections work behind CGNAT. Visitors reach Horizon, and Horizon sends their requests down that connection to your app. You change nothing on your router.

What it supports

  • HTTP and HTTPS web apps and APIs. WebSockets and streamed responses work.
  • Horizon doesn't support raw TCP services. Game servers, SSH, databases and similar services won't work through a tunnel.

Before you begin

  • Node.js 18 or later
  • A Horizon account and the CLI (see Getting started)
  • A reserved subdomain for -s. Reserve one on the Subdomains page.
  • A web app running on your machine, for example on port 3000

Start your app

Start your web app and note its port. The example uses port 3000.

npm run dev

Start a tunnel

Use -s with a subdomain you reserved. Without it, the subdomain is random and changes every run, so visitors would need a new link after each restart. Reserved subdomains are a paid feature, see Pricing.

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

Your public URL is https://my-app.hrzn.run. Keep this terminal open. The site is reachable only while the CLI runs on a machine that is on and online.

If your app builds absolute URLs or must know it is served over HTTPS, set its public https://my-app.hrzn.run URL in its own configuration. Don't rely on X-Forwarded-Proto: Horizon appends to it, so your app can receive a list such as https,http and read the last value as http. Horizon sets the Host header to localhost:3000. The public host arrives in X-Forwarded-Host.

Check it works

Open https://my-app.hrzn.run from a phone on mobile data, not your home Wi-Fi. A browser visitor sees Before you continue on the first visit, then selects Continue to site. Your app loads.

Your Horizon terminal prints a line per request:

Output
  GET     200  /

Troubleshooting

The site works at home but not from outside

Open the URL from a network other than your own. If it loads there, the tunnel works. Check that the Horizon terminal is still running.

A game server or SSH doesn't connect

Horizon carries HTTP and HTTPS only. It can't forward raw TCP. Use a service built for TCP, or a VPN, for those.

The URL changed after a restart

You started the tunnel without -s, so Horizon gave you a new random subdomain. Restart with -s my-app and the URL stays the same. -s needs a subdomain you reserved, see Pricing.

Next steps

On this page