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
Anyone who has the URL can reach your app. Horizon has no password protection or IP allowlist, so your app has to sign visitors in itself. Stop the tunnel when you don't need it.
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-appHORIZON: Tunnel connected
URL https://my-app.hrzn.run (reserved)
Forwarding http://localhost:3000
Request log https://hrzn.run/dashboard/tunnels/my-appYour 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:
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
- Read Getting started for the full CLI flow.