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.
$ 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.
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.
| Where | Resolved by | Example |
|---|---|---|
| --to | The agent | --to 22 is the agent's own sshd |
| --to | The agent | --to 192.168.1.50:5432 is a box on the agent's LAN |
| --port | Your laptop | --port 9922 opens 127.0.0.1:9922 locally |
| ssh …@localhost | Your laptop | Connects to the port you just opened |
Targets
--to accepts a bare port or host:port.
| --to | Means |
|---|---|
| 22 | Port 22 on the agent's own loopback |
| 192.168.1.50:5432 | Postgres on that host, as the agent resolves it |
| nas:8096 | A hostname the agent can resolve |
| [::1]:22 | IPv6 literals must be bracketed |
The local port
Optional. Left out, the local port is the target port + 9000 — so 22 → 9022, 5432 → 14432. 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 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.
| Rule | Allows |
|---|---|
| localhost:22 | Only SSH on the agent itself |
| localhost | Any port on the agent itself |
| 192.168.1.50:5432 | Only Postgres on that host |
| 192.168.1.50 | Any 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.
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: 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.
$ 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.
Host rpi HostName localhost Port 9922 User root
$ ssh rpi
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.
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.