One process per machine. Every endpoint in your browser.
Install it once. After that, adding a tunnel is a form — not an SSH session. The agent picks up changes from the dashboard within seconds and keeps serving everything else while it does.
A tunnel shouldn't be a process you babysit.
hle expose is one tunnel tied to one terminal. The agent is one connection serving as many endpoints as you declare.
hle expose | hle agent | |
|---|---|---|
| Tunnels per process | One | Many |
| Configured in | The command line | The dashboard |
| Changing a tunnel | Restart the command | Takes effect in seconds |
| Survives reboot | No | Yes — installed as a service |
| Credential | API key hle_… | Agent token hlea_… |
| Firepuncher | — | Yes |
| Service discovery | — | Yes — Kubernetes & Docker |
Enrolled and running in one command.
Install and enroll
The installer sets up the client, enrolls the machine, and installs a service that starts at boot. It prompts for an agent token — grab one from Connections → New → Agent. It's shown only once.
$ curl -fsSL https://get.hle.world | sh -s -- --agent
For cloud-init, Ansible, or CI, pass the token instead: --token hlea_xxxxx
Declare endpoints in the dashboard
Each endpoint is a label, a local URL the agent can reach, a domain, and whether it sits behind SSO. The target is any URL the agent can reach — not just its own machine, anything on its network.
| Label | Local URL | Public URL |
|---|---|---|
| ha | http://homeassistant:8123 | ha-x7k.hle.world |
| jelly | http://nas:8096 | jelly.example.com |
| git | http://git:3000 | git-x7k.hle.world |
Custom domains like jelly.example.com need pay-as-you-go; *-x7k.hle.world subdomains are on every plan.
Change things without touching the box
Edit an endpoint and the agent converges within seconds. Nothing restarts, and the other endpoints keep serving traffic throughout.
Manage the service
# is it running? $ hle service status --agent # watch it in the foreground $ hle agent run # logs (linux, system) $ journalctl -u hle-agent -f
A systemd unit on Linux, a launchd job on macOS.
hle agent enroll writes it to ~/.config/hle/agent.toml with mode 0600 — never into the service definition, so the unit file is safe to back up or commit as a template.
It already knows what's running.
An agent inside a Kubernetes cluster or on a Docker host inventories what it can see and reports the list. You pick one and it becomes an endpoint — no hostname to look up, no port to remember. Discovery is strictly read-only.
$ hle agent services
Discovered services (k8s, docker)
Name Where Address Suggested label
grafana monitoring http://grafana.monitoring.svc… grafana
gitea git http://gitea.git.svc.cluster… gitea
plex media http://172.18.0.4:32400 plex
Read-only, always
Discovery never creates, changes, or deletes anything in your cluster or on your Docker host.
Self-detecting
Each provider is asked whether it applies before it's used. An agent on a plain VM finds nothing and reports nothing rather than erroring.
Traffic, split by transport.
Because everything passes through the relay, the dashboard breaks your usage down by transport and protocol without you instrumenting anything — tunnels and firepuncher streams side by side, filterable down to HTTP, WebSocket or raw TCP.
Install the agent on one box.
Enroll it, declare an endpoint, and see how it feels to change a tunnel from a browser tab.