HLE started as one command per tunnel. Over the last few months it became something else: one process per machine, holding every service you publish — plus raw TCP, service discovery, and a chart for all of it. Here is the whole of it in one place.
hle expose is one command, one tunnel, one terminal you can't close. That's
right for trying something and wrong for a homelab with nine services, where it becomes nine
processes, nine service units, and nine places to look when one stops.
The agent is one process per machine. You enroll it once, then declare endpoints in the dashboard — label, local address, domain, who's allowed — and it converges within seconds. No SSH, no restart, no per-tunnel config file.
curl -fsSL https://get.hle.world | sh -s -- --agent
It installs, enrolls, and registers a service that starts at boot. Adding your tenth service after that is a form in a browser, not a command on a box. And it runs where the boxes actually are: Docker, the Home Assistant add-on, Proxmox, Unraid, pfSense and OPNsense — the last of which needed pydantic removed from the client before it would install at all.
Tunnels carry HTTP and WebSocket. SSH, Postgres, RDP and everything else were out of scope — the answer was a VPN, or forwarding a port and hoping.
Firepuncher inverts the data plane. Instead of the internet reaching in, an authenticated client dials the relay and gets paired to your agent, which opens the connection from its side:
hle fp --agent rpi --to 22
ssh -p 9022 root@localhost
Both those localhosts mean different machines, and that's the one genuinely
confusing thing about it: --to 22 is resolved by the agent — the
Pi's own sshd. The root@localhost underneath is your laptop, where
hle fp is listening.
It is never a public TCP port. Both ends authenticate, and the agent enforces a server-side
allowlist — so a stolen API key can't turn your agent into a general-purpose pivot into
the LAN. New agents default to localhost:* only, which covers the SSH case with
no setup while LAN targets stay opt-in.
Every tunnel needs a target address. On a Kubernetes cluster that's four parts you have to be right about and don't remember. So the agent now lists what's running around it — Kubernetes Services cluster-wide, Docker containers via the socket — and you pick from the list instead of looking anything up.
It's read-only, and that's the whole design: one ClusterRole with
get/list/watch on services and endpoints, no secrets, no pods, no write verb
anywhere. The Helm chart now installs the agent alongside the operator, or instead of it.
Four features had grown four different charts with four bucket sizes. They're now one: every connection — tunnel, webhook, agent, forward — reports traffic over time plus three numbers, in the same shape, over 24 hours, 7 days or 30 days.
Webhook receivers split their deliveries into delivered, failed, rate limited and rejected, which answers the only question anyone asks when a webhook goes quiet. And the endpoint takes an API key, so a status board can read its own numbers without a browser session.
SSO rules, PINs, basic auth, share links and address allow-lists used to live in separate places with separate mental models. They're now one ordered list per tunnel: rules are evaluated top to bottom, and a visitor matching none of them is refused by default rather than falling through to whatever the last setting happened to be.
Share links sit in that list too, each with its own log line, so “who used the link I sent on Tuesday” is a question with an answer. They're capped at 100 active per account and seven days each — not a paid tier boundary, just the point past which a pile of live bearer tokens stops being something a person can review.
Agent-to-agent forwards. Today the local end of a firepuncher forward is
always a laptop running hle fp. Nothing in the protocol requires that — an
agent could bind a port on its own machine that reaches a service on a different agent,
making a mesh out of what is currently hub-and-spoke.
Better hle fp ergonomics. An ssh_config
ProxyCommand mode, so ssh rpi just works without starting the
forward first.
Webhook receivers with teeth. Signature verification at the relay, delivery replay, and retries that buffer while your homelab is down — the last of which is the thing a cloud receiver can't offer you.
Not planned: UDP over firepuncher. The relay is a reliable, ordered WebSocket; tunnelling UDP through it turns lossy-unordered into lossless-ordered, which is wrong for most things people want UDP for. WireGuard through it would be the classic TCP-over-TCP meltdown. If you want a VPN, firepuncher is the wrong tool and the conversation should start from what you're trying to reach.
Free tier, no card. Install once, manage from the dashboard.