- 在路由器或 ARM 小盒子上做常驻隧道:二进制体积小、无运行时依赖,frp 的 Go 二进制和内存占用在这个场景下更吃紧
- 需要按服务做权限隔离:每个服务独立 token,一台 client 泄露不会连带其他服务,frp 的共享 token 模型做不到这一点
- 传输层要能穿过只放行 443 的出口:走 websocket 或 tls 传输伪装成普通 HTTPS 流量,比裸 TCP 更容易过企业防火墙
- 需要同时转发 UDP:DNS、游戏、QUIC 这类场景 frp 早期版本支持有限,rathole 原生支持 TCP/UDP

rathole
rathole-orgApache-2.0活跃维护 有专家点评用 Rust 写的高性能轻量反向代理,专门做 NAT 穿透和内网服务暴露,可以当作 frp 和 ngrok 的替代品。
A lightweight and high-performance reverse proxy for NAT traversal, written in Rust. An alternative to frp and ngrok.
专家点评
上手难度:进阶更新于 2026-11-11Rust 写的反向隧道,用来顶替 frp 在低配机器上把内网服务暴露到公网
适用场景
踩坑
- 配置是强耦合的:服务名、token、remote_addr 与 bind_addr 的端口必须两端逐字对齐,任何一处不一致都表现为连接失败而非明确报错,第一次配的时候基本都要对着文档逐字段核一遍
- 端口范围配置被反复提需求(issue 里 19 个反应),说明当前只能一个服务一条配置;要映射几十个端口时配置文件会膨胀,得自己写脚本生成
- 数据通道报
early eof这类连接中断在 issue 里出现过多次,通常和中间网络设备掐断长连接有关,部署在会主动清理空闲连接的链路上时要留意 - 没有官方的环境变量配置方式(Docker 场景有对应 issue),容器化部署只能挂配置文件,改配置靠热重载而不是重启
同类对比
frp 生态更成熟、文档和示例更多,需要端口范围、插件化或大量现成教程时选它;ngrok 类托管服务适合不想自己维护公网入口的情况。
从 release 页面下载二进制后运行:./rathole server.toml(服务端)与 ./rathole client.toml(客户端);也可使用 Docker 镜像 rapiz1/rathole。README 未说明具体硬件要求;仅提到二进制可小至约 500KiB,以适配路由器等嵌入式设备,未提及显存、CPU 或 GPU 型号。
项目说明
由 README 自动翻译提炼用 Rust 写的轻量级反向代理,把 NAT 后的服务安全暴露到公网,可替代 frp 和 ngrok
用 Rust 写的高性能轻量反向代理,专门做 NAT 穿透和内网服务暴露,可以当作 frp 和 ngrok 的替代品。
核心能力
- 用 Rust 实现,吞吐和连接稳定性优于 frp
- 二进制可小至约 500KiB,适合路由器等嵌入式设备
- 每个服务强制独立 token 认证,支持 Noise 协议与 TLS 加密
- 改配置文件热重载即可动态增删服务
- 支持 TCP 与 UDP 转发,可走 tcp、tls、noise、websocket 传输
容易踩的坑
服务端与客户端的服务名必须完全一致,且 token 必须相同才能通过校验;客户端 remote_addr 的端口必须与服务端 bind_addr 端口一致;若配置文件同时含 [server] 和 [client] 块,需用 --server 或 --client 显式指定运行模式。
- dev-latestDev build (3ee93f8)pre-release2026-08-22 · 2026-08-22 20:04 UTC
查看更新说明
Automated build from
dev.- Commit:
3ee93f8b6d6f7c4e3d4a2c695ecf5e6dd97393a6 - Rust:
1.92.0 - Container:
ghcr.io/rathole-org/rathole:dev - Immutable container:
ghcr.io/rathole-org/rathole@sha256:054e8629b84ae3cf3164d9002884469ef9f292978f234aa99d7a28a7d21abbc2
- Commit:
- v0.5.0最新正式版2023-10-01 · 2023-10-01 10:14 UTC
What's Changed
New transport supported!
- feat(transport): add websocket transport by @rucciva in https://github.com/rapiz1/rathole/pull/290
- We dropped linux musl release to save the trouble of linking musl openssl. Now there's only one release for linux x64. Given that it's compiled on ubuntu-latest, the required glibc version should be decent for most users to use.
- Because of the maintenance burden of linking TLS libraries, we don't compile the TLS feature in the release for embedded platforms anymore. If you need to use it, please build from the source by yourself. We will add it back when a pure rust tls library or a good cross-platform TLS libraries linking solution is available.
- docs: non root user with systemd by @boenshao in https://github.com/rapiz1/rathole/pull/269
- chore: fix shield.io badge in README-zh.md by @shiny in https://github.com/rapiz1/rathole/pull/272
New Contributors
- @boenshao made their first contribution in https://github.com/rapiz1/rathole/pull/269
- @shiny made their first contribution in https://github.com/rapiz1/rathole/pull/272
- @rucciva made their first contribution in https://github.com/rapiz1/rathole/pull/290
Full Changelog: https://github.com/rapiz1/rathole/compare/v0.4.8...v0.5.0
- v0.4.82023-05-26 · 2023-05-26 09:11 UTC
查看更新说明
What's Changed
This release fixes the build by upgrading dependencies.
- chore: update tls cert for test by @rapiz1 in https://github.com/rapiz1/rathole/pull/227
- chore(deps): bump libgit2-sys from 0.13.4+1.4.2 to 0.13.5+1.4.5 by @dependabot in https://github.com/rapiz1/rathole/pull/217
- ci: upgrade to upx v4.0.2 by @rapiz1 in https://github.com/rapiz1/rathole/pull/228
- chore(deps): bump tokio from 1.21.2 to 1.24.2 by @dependabot in https://github.com/rapiz1/rathole/pull/219
- chore(deps): bump openssl from 0.10.42 to 0.10.48 by @dependabot in https://github.com/rapiz1/rathole/pull/234
- chore: bump tracing-subscriber in https://github.com/rapiz1/rathole/pull/247
- chore(deps): bump h2 from 0.3.15 to 0.3.18 by @dependabot in https://github.com/rapiz1/rathole/pull/248
New Contributors
- @dependabot made their first contribution in https://github.com/rapiz1/rathole/pull/217
Full Changelog: https://github.com/rapiz1/rathole/compare/v0.4.7...v0.4.8
- v0.4.72022-11-30 · 2022-11-30 04:52 UTC
查看更新说明
What's Changed
- fix: enable TCP_NODELAY by default by @rapiz1 in https://github.com/rapiz1/rathole/pull/210
Full Changelog: https://github.com/rapiz1/rathole/compare/v0.4.6...v0.4.7
- v0.4.62022-11-29 · 2022-11-29 11:23 UTC
查看更新说明
What's Changed
- chore: bump
vergencrate to supportSOURCE_DATE_EPOCHby @orhun in https://github.com/rapiz1/rathole/pull/204 - feat: configurable client retry interval by @rapiz1 in https://github.com/rapiz1/rathole/pull/208
Full Changelog: https://github.com/rapiz1/rathole/compare/v0.4.5...v0.4.6
- chore: bump
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
rathole

A secure, stable and high-performance reverse proxy for NAT traversal, written in Rust
rathole, like frp and ngrok, can help to expose the service on the device behind the NAT to the Internet, via a server with a public IP.
Features
- High Performance Much higher throughput can be achieved than frp, and more stable when handling a large volume of connections. See Benchmark
- Low Resource Consumption Consumes much fewer memory than similar tools. See Benchmark. The binary can be as small as ~500KiB to fit the constraints of devices, like embedded devices as routers.
- Security Tokens of services are mandatory and service-wise. The server and clients are responsible for their own configs. With the optional Noise Protocol, encryption can be configured at ease. No need to create a self-signed certificate! TLS is also supported.
- Hot Reload Services can be added or removed dynamically by hot-reloading the configuration file. HTTP API is WIP.
Quickstart
A full-powered rathole can be obtained from the release page. Or build from source for other platforms and minimizing the binary. A Docker image is also available.
The usage of rathole is very similar to frp. If you have experience with the latter, then the configuration is very easy for you. The only difference is that configuration of a service is split into the client side and the server side, and a token is mandatory.
To use rathole, you need a server with a public IP, and a device behind the NAT, where some services that need to be exposed to the Internet.
Assuming you have a NAS at home behind the NAT, and want to expose its ssh service to the Internet:
- On the server which has a public IP
Create server.toml with the following content and accommodate it to your needs.
# server.toml
[server]
bind_addr = "0.0.0.0:2333" # `2333` specifies the port that rathole listens for clients
[server.services.my_nas_ssh]
token = "use_a_secret_that_only_you_know" # Token that is used to authenticate the client for the service. Change to an arbitrary value.
bind_addr = "0.0.0.0:5202" # `5202` specifies the port that exposes `my_nas_ssh` to the Internet
Then run:
./rathole server.toml
- On the host which is behind the NAT (your NAS)
Create client.toml with the following content and accommodate it to your needs.
# client.toml
[client]
remote_addr = "myserver.com:2333" # The address of the server. The port must be the same with the port in `server.bind_addr`
[client.services.my_nas_ssh]
token = "use_a_secret_that_only_you_know" # Must be the same with the server to pass the validation
local_addr = "127.0.0.1:22" # The address of the service that needs to be forwarded
Then run:
./rathole client.toml
- Now the client will try to connect to the server
myserver.comon port2333, and any traffic tomyserver.com:5202will be forwarded to the client's port22.
So you can ssh myserver.com:5202 to ssh to your NAS.
To run rathole run as a background service on Linux, checkout the systemd examples.
Configuration
rathole can automatically determine to run in the server mode or the client mode, according to the content of the configuration file, if only one of [server] and [client] block is present, like the example in Quickstart.
But the [client] and [server] block can also be put in one file. Then on the server side, run rathole --server config.toml and on the client side, run rathole --client config.toml to explicitly tell rathole the running mode.
Before heading to the full configuration specification, it's recommend to skim the configuration examples to get a feeling of the configuration format.
See Transport for more details about encryption and the transport block.
Here is the full configuration specification:
[client]
remote_addr = "example.com:2333" # Necessary. The address of the server
default_token = "default_token_if_not_specify" # Optional. The default token of services, if they don't define their own ones
heartbeat_timeout = 40 # Optional. Set to 0 to disable the application-layer heartbeat test. The value must be greater than `server.heartbeat_interval`. Default: 40 seconds
retry_interval = 1 # Optional. The interval between retry to connect to the server. Default: 1 second
[client.transport] # The whole block is optional. Specify which transport to use
type = "tcp" # Optional. Possible values: ["tcp", "tls", "noise"]. Default: "tcp"
[client.transport.tcp] # Optional. Also affects `noise` and `tls`
proxy = "socks5://user:[email protected]:1080" # Optional. The proxy used to connect to the server. `http` and `socks5` is supported.
nodelay = true # Optional. Determine whether to enable TCP_NODELAY, if applicable, to improve the latency but decrease the bandwidth. Default: true
keepalive_secs = 20 # Optional. Specify `tcp_keepalive_time` in `tcp(7)`, if applicable. Default: 20 seconds
keepalive_interval = 8 # Optional. Specify `tcp_keepalive_intvl` in `tcp(7)`, if applicable. Default: 8 seconds
[client.transport.tls] # Necessary if `type` is "tls"
trusted_root = "ca.pem" # Necessary. The certificate of CA that signed the server's certificate
hostname = "example.com" # Optional. The hostname that the client uses to validate the certificate. If not set, fallback to `client.remote_addr`
[client.transport.noise] # Noise protocol. See `docs/transport.md` for further explanation
pattern = "Noise_NK_25519_ChaChaPoly_BLAKE2s" # Optional. Default value as shown
local_private_key = "key_encoded_in_base64" # Optional
remote_public_key = "key_encoded_in_base64" # Optional
[client.transport.websocket] # Necessary if `type` is "websocket"
tls = true # If `true` then it will use settings in `client.transport.tls`
[client.services.service1] # A service that needs forwarding. The name `service1` can change arbitrarily, as long as identical to the name in the server's configuration
type = "tcp" # Optional. The protocol that needs forwarding. Possible values: ["tcp", "udp"]. Default: "tcp"
token = "whatever" # Necessary if `client.default_token` not set
local_addr = "127.0.0.1:1081" # Necessary. The address of the service that needs to be forwarded
nodelay = true # Optional. Override the `client.transport.nodelay` per service
retry_interval = 1 # Optional. The interval between retry to connect to the server. Default: inherits the global config
[client.services.service2] # Multiple services can be defined
local_addr = "127.0.0.1:1082"
[server]
bind_addr = "0.0.0.0:2333" # Necessary. The address that the server listens for clients. Generally only the port needs to be change.
default_token = "default_token_if_not_specify" # Optional
heartbeat_interval = 30 # Optional. The interval between two application-layer heartbeat. Set to 0 to disable sending heartbeat. Default: 30 seconds
[server.transport] # Same as `[client.transport]`
type = "tcp"
[server.transport.tcp] # Same as the client
nodelay = true
keepalive_secs = 20
keepalive_interval = 8
[server.transport.tls] # Necessary if `type` is "tls"
pkcs12 = "identify.pfx" # Necessary. pkcs12 file of server's certificate and private key
pkcs12_password = "password" # Necessary. Password of the pkcs12 file
[server.transport.noise] # Same as `[client.transport.noise]`
pattern = "Noise_NK_25519_ChaChaPoly_BLAKE2s"
local_private_key = "key_encoded_in_base64"
remote_public_key = "key_encoded_in_base64"
[server.transport.websocket] # Necessary if `type` is "websocket"
tls = true # If `true` then it will use settings in `server.transport.tls`
[server.services.service1] # The service name must be identical to the client side
type = "tcp" # Optional. Same as the client `[client.services.X.type]
token = "whatever" # Necessary if `server.default_token` not set
bind_addr = "0.0.0.0:8081" # Necessary. The address of the service is exposed at. Generally only the port needs to be change.
nodelay = true # Optional. Same as the client
[server.services.service2]
bind_addr = "0.0.0.1:8082"
Logging
rathole, like many other Rust programs, use environment variables to control the logging level. info, warn, error, debug, trace are available.
RUST_LOG=error ./rathole config.toml
will run rathole with only error level logging.
If RUST_LOG is not present, the default logging level is info.
Tuning
From v0.4.7, rathole enables TCP_NODELAY by default, which should benefit the latency and interactive applications like rdp, Minecraft servers. However, it slightly decreases the bandwidth.
If the bandwidth is more important, TCP_NODELAY can be opted out with nodelay = false.
Benchmark
rathole has similar latency to frp, but can handle a more connections, provide larger bandwidth, with less memory usage.
For more details, see the separate page Benchmark.
However, don't take it from here that rathole can magically make your forwarded service faster several times than before. The benchmark is done on local loopback, indicating the performance when the task is cpu-bounded. One can gain quite a improvement if the network is not the bottleneck. Unfortunately, that's not true for many users. In that case, the main benefit is lower resource consumption, while the bandwidth and the latency may not improved significantly.

Planning
- HTTP APIs for configuration
Out of Scope lists features that are not planned to be implemented and why.