tunnel-me
Reach the services running on your home machines through your own domain — without port forwarding, without a static IP at home, and without opening your network.
You run one small program on a server with a public address, and the same program on each machine that has something worth sharing. Visitors open myapp.tunnels.example.com and reach the service on your desk as if it were hosted there.
Why
Typical reasons homelabbers reach for a tunnel:
- The ISP box is in the way. CGNAT (carrier-grade NAT — your ISP stacks dozens of subscribers behind one shared address, so no incoming connection can ever reach you), no static IP, or a router you are not allowed to configure — none of it matters, because your home machines connect outward to the server. No ports are opened at home.
- One port, many services. The server listens on a single port. Every additional service is just another subdomain.
- Reliable where it matters. Everything rides on plain HTTPS and websockets — the most well-trodden, battle-tested paths on the internet. Load balancers, CDNs, strict firewalls, hotel Wi-Fi: tunnel-me goes wherever a browser goes, and if this does not get through, odds are nothing else will. The price is peak throughput — raw-socket tunnels are faster on paper, and on the networks tunnel-me targets they often do not connect at all.
- Self-contained. One binary per machine, no external runtime dependencies, nothing "calls home" to any vendor. All state is a single database file you can back up.
- Runs on leftovers. The whole trick has one real requirement: the server itself must live somewhere with a public address. That is less scary than it sounds — it is a single small binary with a tiny database file, happy on the cheapest low-end VPS on the market, needing exactly one open port (443 if you put a TLS proxy in front).
- Automation-friendly. The panel is backed by an HTTP API — everything the UI does, scripts can do (the API contract is described in
openapi.yaml).
Known limits: throughput (see above — deliberately traded for reliability), personal or small-team scale (one server, one database — not a distributed fleet), and a backend that grew organically while the project found its shape, so a few tangles survived. Life is life, live with it 🙂
How it works
flowchart LR
visitor(["Visitor"]) -->|"myapp.tunnels.example.com"| server["Server<br/>public host, one port"]
you(["You"]) -->|"admin panel"| server
server <-->|"always-on tunnel<br/>(agent dials out)"| agent["Agent<br/>your home machine"]
agent -->|"localhost:3000"| service(["Your service"])
- The server runs on the public host. It serves the admin panel on the base domain and forwards every subdomain request into the matching tunnel.
- The agent runs next to your service. It keeps an always-on connection open to the server. Visitor requests travel down that connection to the agent and on to your service; the answers travel back up.
- Because the agent initiates the connection, it works from anywhere: behind NAT, residential routers, strict firewalls. Reconnects after any outage are automatic.
- One tunnel can have several agents (say, two machines sharing the load) — requests alternate between the machines that are connected.
The words used in these docs
| Word | Meaning |
|---|---|
| Server | The program on the public host: tunnel-me serve |
| Agent | The program next to your service: tunnel-me connect |
| Tunnel | One shared service: a sub-domain (myapp.tunnels.example.com) plus its settings and tokens |
| Token | A secret a machine presents to join a tunnel as an agent |
| Upstream | The local service the agent forwards to — a port, a host:port, or a folder of files |
| Base domain | The domain you gave the server (tunnels.example.com); the panel lives on it |
Yes, agent is an unfortunate name for what is really just a small connector program. It stuck early in the project, and renaming it now would break everyone's scripts and muscle memory — so it stays.
What you need
- A domain you control, with a wildcard DNS record pointing at the server (
*.tunnels.example.com) — this is what makes tunnels work. - An always-on host with a public address for the server. This is the one part that cannot live at home — but it can be almost anything: the cheapest low-end VPS on the market has more than enough muscle, since tunnel-me is a single static binary and a small database file.
- TLS: either a reverse proxy in front with one wildcard certificate, or the server's built-in HTTPS. Plain HTTP works too, but is not recommended beyond testing.
The admin panel
A web interface to manage everything: tunnels and their tokens, per-tunnel sign-in rules (public, your identity provider, or built-in users and groups), visitor accounts, usage charts, and blocked-IP management. Protect the panel itself with a password, an htpasswd file, or your own SSO. A guided tour: Admin panel.
Documentation map
| If you want to… | Read |
|---|---|
| Get from zero to a working tunnel | Getting started |
| Understand the concepts | This page |
| See what it is good for | Use cases |
| Configure the server (all flags) | Server reference |
| Configure agents, keep them running | Agent guide |
| Decide who can open the panel and who can visit tunnels | Authentication |
| Serve HTTPS and integrate with proxies | Reverse proxy |
| Find the panel page you need | Admin panel |
| Fix a problem | Troubleshooting |