Firepuncher

A port on your laptop becomes a port inside your network.

Every other tunnel here is inbound: the internet reaches a web app you run. Firepuncher inverts that — which makes it work for the things a browser can't reach. SSH, Postgres, Redis, RDP, VNC. Anything that speaks TCP.

shell
$ hle fp --agent rpi --to 22 --port 9922

Forwarding 127.0.0.1:9922 → rpi → localhost:22
Agent allows: localhost:*
Try: ssh -p 9922 <user>@127.0.0.1
Ctrl+C to stop.
The one confusing part

Which machine is which.

The word localhost appears on both sides of a firepuncher session and means a different machine each time. Read --to as "as the agent would type it": you are naming a target from inside your network, from a terminal outside it.

WhereResolved byExample
--toThe agent--to 22 is the agent's own sshd
--toThe agent--to 192.168.1.50:5432 is a box on the agent's LAN
--portYour laptop--port 9922 opens 127.0.0.1:9922 locally
ssh …@localhostYour laptopConnects to the port you just opened

Targets

--to accepts a bare port or host:port.

--toMeans
22Port 22 on the agent's own loopback
192.168.1.50:5432Postgres on that host, as the agent resolves it
nas:8096A hostname the agent can resolve
[::1]:22IPv6 literals must be bracketed

The local port

Optional. Left out, the local port is the target port + 9000 — so 229022, 543214432. Guessable, and off privileged ports.

--bind defaults to 127.0.0.1. Widening it exposes the forward to your whole local network, which throws away much of the point — think before changing it.
The allowlist

The agent decides what's reachable — not the caller.

Rules live on the server, per agent, and are pushed down the agent's control connection. There is nothing to configure on the agent's machine. A fresh agent with no rules allows its own loopback on any port, which is why SSH-ing to the agent itself needs no setup at all.

RuleAllows
localhost:22Only SSH on the agent itself
localhostAny port on the agent itself
192.168.1.50:5432Only Postgres on that host
192.168.1.50Any port on that host

localhost, 127.0.0.1, ::1 and ip6-localhost are treated as the same target, so a rule for one can't be side-stepped by spelling it another way.

Your first explicit rule replaces the loopback default rather than adding to it. If you allow 192.168.1.50:5432 and still want the agent's own ports, add localhost too. This is deliberate — an allowlist should be exactly what you asked for, never a superset.
refused
Refused: 192.168.1.50:5432 is not allowed.
Allowed: localhost:*

hle fp prints the allowlist in its start-up banner, so you can see what you can reach before you try it.

Keeping a forward up

hle fp reconnects on its own. The local port is bound once and held for the lifetime of the command — a relay restart or a dropped network doesn't take the port away.

shell
$ hle service install --fp --agent-name rpi --to 22 --port 9922

The API key is never written into the service file — it's read at runtime from config or HLE_API_KEY.

Using it with SSH

Add the forwarded port to your SSH config and the indirection disappears.

~/.ssh/config
Host rpi
  HostName localhost
  Port 9922
  User root
shell
$ ssh rpi
Security

Nothing listens on the public internet.

Both ends dial out

Your laptop with an API key, the agent with its agent token. There is no inbound port on either side, and nothing to firewall.

The agent decides

A caller who asks for a target that isn't allowed is refused by the agent itself — not by the relay, and not on trust.

Loopback by default

The local listener binds 127.0.0.1, so other machines on your network can't use your forward.

Not inspected

Carried over the same encrypted WebSocket transport as the rest of HLE. Firepuncher doesn't terminate the stream — SSH's own end-to-end encryption is unaffected.

Read the full security model →

SSH into your homelab from anywhere.

Install an agent, run one command, and the port is yours. No router config, no VPN, no open ports.