# rathole: a Rust reverse proxy for exposing NAT-bound services

> rathole tunnels a home or office service to a public server using a TOML config split between client and server. It is small, token-authenticated, and best suited to people who are comfortable editing config files rather than clicking through a dashboard.

**rathole-org/rathole** — A lightweight and high-performance reverse proxy for NAT traversal, written in Rust. An alternative to frp and ngrok.

- Repository: https://github.com/rathole-org/rathole
- Stars: 14,282 · Forks: 841
- Language: Rust
- License: Apache-2.0
- Published: 2026-09-21 · Updated: 2026-09-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/rathole-org-rathole

## The problem rathole solves: reaching a machine that has no public address

Home NAS boxes, lab machines, and embedded devices usually sit behind carrier-grade NAT or a router that will not forward ports. rathole assumes you have one host with a public IP and one host that does not. The public host runs in server mode and listens for the private host to dial in. The private host runs in client mode and opens an outbound connection, which means no inbound firewall rule is needed on the private side. The README frames this as the same job frp and ngrok do: expose a service on a device behind NAT to the Internet through a server with a public IP. The audience is therefore narrow and technical. You need a VPS or a colocated box, and you need to be comfortable writing TOML. There is no web console mentioned anywhere in the README, and no account system. The unit of configuration is a named service, and each service carries its own token, so one compromised service token does not automatically grant access to the others.

## How the client and server split works in practice

Configuration is deliberately asymmetric. On the server you declare a listening address for clients and a per-service token plus the public port. On the client you declare the server's remote address and, for the same service name, the token and the local address to forward to. The service name is the join key: my_nas_ssh on the server and my_nas_ssh on the client refer to the same tunnel. The README states that rathole can infer its mode from the config file when only one of the [server] or [client] blocks is present, and that you can force it with rathole --server config.toml or rathole --client config.toml when both blocks live in one file. Transport is selected in a transport block with type set to tcp, tls, or noise. The README describes the Noise Protocol option as a way to get encryption without creating a self-signed certificate, and TLS is also supported. There is an application-layer heartbeat, with heartbeat_timeout defaulting to 40 seconds and retry_interval defaulting to 1 second on the client, and the README notes that the heartbeat value must be greater than server.heartbeat_interval. Those defaults matter on flaky links: a client that cannot reach the server will retry once per second, and a service that stops answering heartbeats is treated as gone.

## Installing rathole and forwarding SSH from a NAT-bound host

The README points at the release page for a prebuilt binary, at docs/build-guide.md for building from source on other platforms or to shrink the binary, and at the rathole Docker image. There is no package manager instruction in the README, so treat the release binary as the default path. On the public server, write a server config naming the client-facing port and the public port for the service. The 2333 port is the control port clients dial, and 5202 is what the outside world connects to.

```toml
# server.toml
[server]
bind_addr = "0.0.0.0:2333"

[server.services.my_nas_ssh]
token = "use_a_secret_that_only_you_know"
bind_addr = "0.0.0.0:5202"
```

Start it by passing the file as an argument. The README gives exactly this invocation, with no subcommand.

```bash
./rathole server.toml
```

On the machine behind NAT, the client config points at the server and maps the same service name to a local address. The token must match the server's token or validation fails, which is the README's wording.

```toml
# client.toml
[client]
remote_addr = "myserver.com:2333"

[client.services.my_nas_ssh]
token = "use_a_secret_that_only_you_know"
local_addr = "127.0.0.1:22"
```

Run the client the same way. What you should see is the client establishing an outbound connection to port 2333, after which traffic arriving on the server's port 5202 is forwarded to port 22 on the private host. The README's own test is ssh myserver.com:5202. For a long-running deployment the repository ships systemd examples under examples/systemd, which is where the README sends you rather than documenting a service file inline.

## Where rathole is the wrong tool

The configuration model is the main constraint. Every service needs a matching stanza on both sides, and the token is mandatory, so there is no anonymous or quick-share mode. If your goal is to hand a colleague a temporary URL for a local dev server, this two-file, two-host setup is more ceremony than the job needs. The README also notes that the HTTP API for managing services is WIP, which means dynamic service management is not something you can build against today. Hot reload is the supported way to add or remove services, and it depends on the hot-reload feature being compiled in; the default feature set includes it, but the embedded feature set is described in Cargo.toml as server, client, hot-reload, and noise, with a comment explaining that cross-compiling with TLS is hard. If you build with embedded, you lose TLS. That is a real trade-off for router-class devices, and the README does not document a way around it. Finally, the release history is uneven: v0.5.0 is tagged 2023-10-01 and v0.4.8 is from 2023-05-26, while the most recent artifact is a dev build. Anyone who pins to tagged releases should expect to sit on v0.5.0.

## rathole vs frp: same idea, different configuration surface

The README itself names frp as the closest comparison and says rathole's usage is very similar, with one stated difference: the service configuration is split between client and server, and a token is mandatory. That split is the substantive divergence. In frp, the README's framing implies a more consolidated configuration experience; in rathole you maintain two files that must agree on service names and tokens, and a mismatch shows up as a validation failure rather than a silent fallback. The payoff claimed in the README is throughput and stability under many connections, plus a binary that can be as small as roughly 500KiB, which is aimed at embedded devices such as routers. If your constraint is a memory-limited device, that size claim is the reason to look here. If your constraint is operational simplicity, the split config is a cost you pay on every service you add. ngrok sits further away: it is a hosted service, whereas rathole is software you run on a server you control, with no account layer described in the README.

## Maintenance, licensing, and what upgrading costs you

The repository is not archived, and the last push was on 2026-08-23, so work is happening on the main branch. The tagged release line is older: v0.5.0 dates to 2023-10-01. That gap is the practical maintenance fact. If you track main or use the dev build, you get current code but no semantic versioning promise; if you track tags, you are on a release that predates roughly three years of commits. Upgrading across that boundary means reading the configuration specification again, because the README documents keys such as heartbeat_timeout, retry_interval, and the transport sub-blocks, and a config written against an older schema may not match. The licence is Apache-2.0, stated in both the README badge set and Cargo.toml. Apache-2.0 permits commercial use and modification and includes a patent grant, but it also requires that you preserve notices and state changes; if you redistribute a modified rathole binary, read the licence text rather than relying on this summary. The Dockerfile builds into a distroless cc-debian12 image and runs as USER 1000:1000, which is worth knowing if your deployment assumes a root-capable container.

## Transport choices and the encryption decision

The transport block is where most of the security posture lives, and the README is explicit that the whole block is optional with type defaulting to tcp. Choosing tcp means the tunnel is not encrypted by rathole itself, which is defensible if the client-to-server path is already inside a VPN or a trusted network, and questionable otherwise. Choosing tls requires a trusted_root certificate file on the client and optionally a hostname for validation, falling back to client.remote_addr if unset. Choosing noise uses the Noise Protocol and, per the README, avoids the self-signed certificate chore entirely. There is a real operational difference: TLS forces you to manage certificate files and their distribution, while Noise moves the trust problem into a pre-shared key arrangement. The README points to docs/transport.md for the details of the noise block, including the pattern value, rather than spelling out every option in the README itself. The tcp sub-block also carries proxy, nodelay, keepalive_secs, and keepalive_interval, and the README notes that this sub-block affects noise and tls as well, so proxy settings are not exclusive to plain TCP.

## Conclusion

Adopt rathole if you already run a public VPS, want a small binary, and are willing to keep two TOML files in sync: one on the server, one on the NAT side. Do not adopt it if you need a hosted dashboard, automatic TLS issuance, or a stable tagged release cadence, because the most recent tagged release is v0.5.0 from 2023-10-01 while development builds continue. Before committing, verify that your client and server configs use the same token and that the server's bind_addr port is actually reachable from the client's network.

## FAQ

### How do I install rathole?

The README says a full-powered rathole can be obtained from the release page, or built from source following docs/build-guide.md for other platforms and for minimizing the binary size. A Docker image is also available. There is no package manager installation documented in the README.

### What is rathole?

It is a reverse proxy for NAT traversal written in Rust, described in the README as helping expose a service on a device behind NAT to the Internet via a server with a public IP. The README places it alongside frp and ngrok for that purpose.

### How does rathole compare with frp?

The README says rathole's usage is very similar to frp, and that the difference is that a service's configuration is split into the client side and the server side and a token is mandatory. It also claims higher throughput and more stable handling of large connection volumes than frp, with benchmarks linked from the README.

## Sources

- [Issues](https://github.com/rathole-org/rathole/issues)
- [License: Apache-2.0](https://github.com/rathole-org/rathole/blob/main/LICENSE)
- [rathole-org/rathole on GitHub](https://github.com/rathole-org/rathole)
- [README](https://github.com/rathole-org/rathole/blob/main/README.md)
- [Releases](https://github.com/rathole-org/rathole/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/rathole-org-rathole
