harn tunnel
harn tunnel up | down | reload | status | logs | allow [...]harn tunnel puts a local reverse-proxy gate in front of a provider tunnel, so you can reach a local service (the web UI, a dev server, etc.) from your phone or another machine without opening a firewall port or deploying. The gate rewrites the upstream Host header and forwards the request. Cloudflare mode uses a Cloudflare quick tunnel plus an IP allowlist checked against CF-Connecting-IP; Tailscale mode uses Tailscale Serve/Funnel and lets Tailscale own the access boundary.
Commands
Section titled “Commands”harn tunnel up # start the default instanceharn tunnel up --target 127.0.0.1:9000 --vhost localhost:9000 # custom upstream + Hostharn tunnel up --name web # a second, independently-managed instanceharn tunnel up --provider cloudflare # default: random *.trycloudflare.com URL + IP allowlistharn tunnel up --provider tailscale # tailnet-only Tailscale Serve URLharn tunnel up --provider tailscale --visibility public # public Tailscale Funnel URLharn tunnel up --provider tailscale --name web --path /web # named instance at a pathharn tunnel status # table of running instances + their URLsharn tunnel down [--name <id>|--all] # stop one / allharn tunnel reload [--name <id>|--all] # pick up allowlist edits, keeping the URLharn tunnel logs [-f] # tail the gate log (--provider for the provider log)Providers
Section titled “Providers”| Provider | Command | URL shape | Access model |
|---|---|---|---|
| Cloudflare | harn tunnel up --provider cloudflare |
random https://*.trycloudflare.com |
gate requires Cloudflare’s CF-Connecting-IP to match harn tunnel allow |
| Tailscale Serve | harn tunnel up --provider tailscale |
https://<machine>.<tailnet>.ts.net/ |
tailnet members only |
| Tailscale Funnel | harn tunnel up --provider tailscale --visibility public |
https://<machine>.<tailnet>.ts.net/ |
public internet, if Funnel is enabled in the tailnet policy |
Cloudflare gives each instance a separate random hostname. Tailscale gives the machine one MagicDNS hostname, so named instances default to path mounts: the default instance uses /, and --name web uses /web unless you pass --path.
Cloudflare Allowlist
Section titled “Cloudflare Allowlist”The Cloudflare provider denies every request whose origin IP isn’t on the allowlist (shared across Cloudflare instances):
harn tunnel allow # list allowed IPsharn tunnel allow add 1.2.3.4 # add, then `harn tunnel reload --all` to applyharn tunnel allow rm 1.2.3.4 # removeEach gate reads the allowlist once, when it starts, so editing the config alone changes nothing for a tunnel that is already running.
Reload
Section titled “Reload”harn tunnel reload restarts an instance’s gate in place and leaves the provider process alone:
harn tunnel reload # the default instanceharn tunnel reload --name web # one named instanceharn tunnel reload --all # every live instanceThe reason to reach for this instead of down then up is the URL. A Cloudflare quick tunnel’s hostname is minted by cloudflared when it starts, so a full restart hands back a different *.trycloudflare.com address and invalidates every link you have already shared. cloudflared only ever forwards to the gate’s local port, so swapping the gate underneath it keeps the hostname intact and the edge reconnects on its own.
Denied requests are logged with the client IP, so the usual sequence after someone hits a 403 is to read the IP out of the gate log, add it, and reload:
harn tunnel logs # find the `deny: ... ip=<addr>` lineharn tunnel allow add <addr>harn tunnel reload --all--all covers live instances only. Stale state files from earlier runs are counted and skipped rather than reported as failures. Reloading a single stale instance refuses instead, because a dead provider means the public URL is already gone and only harn tunnel up can mint a new one.
Caveats
Section titled “Caveats”- Quick tunnels reroll their hostname on every restart. Use
harn tunnel reloadfor allowlist changes so the URL survives. A hostname that persists across a fulldown/upwould need a named Cloudflare tunnel tied to a domain, which isn’t built today. cloudflaredauto-installs to~/.local/bin/on first run (Linux); on macOS,brew install cloudflared.- Tailscale mode requires the
tailscaleCLI to be installed, signed in, and reporting a MagicDNS name fromtailscale status --json. - Tailscale Funnel is public internet exposure. Use Tailscale Serve when the reviewing device is in the tailnet.
- State lives under
.cache/tunnel/.