Skip to content
Login

Firepuncher

Every tunnel described elsewhere in these docs is inbound: the internet reaches a web app on your network. Firepuncher inverts that. Your laptop dials the relay, the relay pairs it with one of your agents, and a port on your laptop becomes a port inside your network.

That makes it work for things a browser can’t reach: SSH, Postgres, Redis, RDP, VNC — anything that speaks TCP.

Terminal window
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.

Then, in another terminal:

Terminal window
ssh -p 9922 root@localhost

Which machine is which

This is the one genuinely confusing part, so it’s worth being blunt about it. The word localhost appears on both sides of a firepuncher session and means a different machine each time.

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
--port / --bindyour laptop--port 9922 opens 127.0.0.1:9922 locally
ssh … root@localhostyour laptopconnects to the port you just opened

Read --to as “as the agent would type it”. You are naming a target from inside your network, from a terminal outside it.

Requirements

An enrolled, running agent on the far end — see Agent — and an API key for yourself (hle auth login). The agent needs no firepuncher-specific setup.

Targets

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

--toMeans
22port 22 on the agent’s own loopback
localhost:22the same thing, written out
192.168.1.50:5432port 5432 on that host, as resolved by the agent
nas:8096a hostname the agent can resolve
[::1]:22IPv6 literals must be bracketed

The local port

--port is optional. Left out, the local port is the target port + 9000 (so 229022, 543214432), which keeps the mapping 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

An agent only forwards to targets it has been told to allow. The allowlist is per agent and stored on the server — there is nothing to configure on the agent’s machine. It is pushed down the agent’s control connection when it connects and again whenever you change it.

A fresh agent with no rules configured allows its own loopback on any port. That is why SSH-ing to the agent’s own machine needs no setup at all.

Manage rules in the dashboard under Connections → Firepuncher.

A rule is a host plus an optional port. Omitting the port allows any port on that host.

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.

When a target isn’t allowed, the connection is refused and the CLI tells you what is allowed:

Refused: 192.168.1.50:5432 is not allowed. Allowed: localhost:*

hle fp also prints the agent’s 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, so a relay restart or a dropped network doesn’t take the port away — connections in flight when the link drops are closed (the far end is genuinely gone), but the listener stays ready.

For a forward that should always be there, install it as a service:

Terminal window
hle service install --fp --agent-name rpi --to 22 --port 9922

The API key is never written into the service file; it is read at runtime from ~/.config/hle/config.toml or HLE_API_KEY.

Using it with SSH

Add the forwarded port to ~/.ssh/config and the indirection disappears:

Host rpi
HostName localhost
Port 9922
User root
Terminal window
ssh rpi

Security

  • Nothing listens on the public internet. Both ends dial out to the relay: 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 what is reachable, not the client. A caller who asks for a target that isn’t allowed is refused by the agent itself.
  • The local listener is loopback-only by default, so other machines on your network can’t use your forward.
  • Traffic is carried over the same encrypted WebSocket transport as the rest of HLE. Firepuncher does not terminate or inspect the stream — SSH’s own end-to-end encryption is unaffected.

Flags

FlagDefaultMeaning
--agentrequiredAgent name or id to forward through
--torequiredTarget as the agent sees it: HOST:PORT, or a bare port
--porttarget port + 9000Local port to listen on
--bind127.0.0.1Local address to bind
--api-keyenv / configAPI key, if not already saved
--relay-hosthle.worldRelay host
--relay-port443Relay port

Troubleshooting

Missing option '--agent' — both --agent and --to are required.

Refused: … is not allowed — the target isn’t in that agent’s allowlist. Add it in Connections → Firepuncher, and remember that your first rule replaces the loopback default.

No API key — run hle auth login, or pass --api-key.

“Waiting for it to come back…” — the agent is offline. hle fp holds the local port and reconnects by itself once the agent returns.

could not bind 127.0.0.1:9922 — something else already has that local port. Pick another with --port.