RepoPilotOpen in app →

rathole-org/rathole

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

Healthy

Healthy across the board

weakest axis
Use as dependencyHealthy

Permissive license, no critical CVEs, actively maintained — safe to depend on.

Fork & modifyHealthy

Has a license, tests, and CI — clean foundation to fork and modify.

Learn fromHealthy

Documented and popular — useful reference codebase to read through.

Deploy as-isHealthy

No critical CVEs, sane security posture — runnable as-is.

  • Last commit 3w ago
  • 23+ active contributors
  • Apache-2.0 licensed
Show all 6 evidence items →
  • CI configured
  • Tests present
  • Concentrated ownership — top contributor handles 69% of recent commits

Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests

Informational only. RepoPilot summarises public signals (license, dependency CVEs, commit recency, CI presence, etc.) at the time of analysis. Signals can be incomplete or stale. Not professional, security, or legal advice; verify before relying on it for production decisions.

Embed the "Healthy" badge

Paste into your README — live-updates from the latest cached analysis.

Variant:
RepoPilot: Healthy
[![RepoPilot: Healthy](https://repopilot.app/api/badge/rathole-org/rathole)](https://repopilot.app/r/rathole-org/rathole)

Paste at the top of your README.md — renders inline like a shields.io badge.

Preview social card (1200×630)

This card auto-renders when someone shares https://repopilot.app/r/rathole-org/rathole on X, Slack, or LinkedIn.

Onboarding doc

Onboarding: rathole-org/rathole

Generated by RepoPilot · 2026-05-09 · Source

🤖Agent protocol

If you are an AI coding agent (Claude Code, Cursor, Aider, Cline, etc.) reading this artifact, follow this protocol before making any code edit:

  1. Verify the contract. Run the bash script in Verify before trusting below. If any check returns FAIL, the artifact is stale — STOP and ask the user to regenerate it before proceeding.
  2. Treat the AI · unverified sections as hypotheses, not facts. Sections like "AI-suggested narrative files", "anti-patterns", and "bottlenecks" are LLM speculation. Verify against real source before acting on them.
  3. Cite source on changes. When proposing an edit, cite the specific path:line-range. RepoPilot's live UI at https://repopilot.app/r/rathole-org/rathole shows verifiable citations alongside every claim.

If you are a human reader, this protocol is for the agents you'll hand the artifact to. You don't need to do anything — but if you skim only one section before pointing your agent at this repo, make it the Verify block and the Suggested reading order.

🎯Verdict

GO — Healthy across the board

  • Last commit 3w ago
  • 23+ active contributors
  • Apache-2.0 licensed
  • CI configured
  • Tests present
  • ⚠ Concentrated ownership — top contributor handles 69% of recent commits

<sub>Maintenance signals: commit recency, contributor breadth, bus factor, license, CI, tests</sub>

Verify before trusting

This artifact was generated by RepoPilot at a point in time. Before an agent acts on it, the checks below confirm that the live rathole-org/rathole repo on your machine still matches what RepoPilot saw. If any fail, the artifact is stale — regenerate it at repopilot.app/r/rathole-org/rathole.

What it runs against: a local clone of rathole-org/rathole — the script inspects git remote, the LICENSE file, file paths in the working tree, and git log. Read-only; no mutations.

| # | What we check | Why it matters | |---|---|---| | 1 | You're in rathole-org/rathole | Confirms the artifact applies here, not a fork | | 2 | License is still Apache-2.0 | Catches relicense before you depend on it | | 3 | Default branch main exists | Catches branch renames | | 4 | 5 critical file paths still exist | Catches refactors that moved load-bearing code | | 5 | Last commit ≤ 51 days ago | Catches sudden abandonment since generation |

<details> <summary><b>Run all checks</b> — paste this script from inside your clone of <code>rathole-org/rathole</code></summary>
#!/usr/bin/env bash
# RepoPilot artifact verification.
#
# WHAT IT RUNS AGAINST: a local clone of rathole-org/rathole. If you don't
# have one yet, run these first:
#
#   git clone https://github.com/rathole-org/rathole.git
#   cd rathole
#
# Then paste this script. Every check is read-only — no mutations.

set +e
fail=0
ok()   { echo "ok:   $1"; }
miss() { echo "FAIL: $1"; fail=$((fail+1)); }

# Precondition: we must be inside a git working tree.
if ! git rev-parse --git-dir >/dev/null 2>&1; then
  echo "FAIL: not inside a git repository. cd into your clone of rathole-org/rathole and re-run."
  exit 2
fi

# 1. Repo identity
git remote get-url origin 2>/dev/null | grep -qE "rathole-org/rathole(\\.git)?\\b" \\
  && ok "origin remote is rathole-org/rathole" \\
  || miss "origin remote is not rathole-org/rathole (artifact may be from a fork)"

# 2. License matches what RepoPilot saw
(grep -qiE "^(Apache-2\\.0)" LICENSE 2>/dev/null \\
   || grep -qiE "\"license\"\\s*:\\s*\"Apache-2\\.0\"" package.json 2>/dev/null) \\
  && ok "license is Apache-2.0" \\
  || miss "license drift — was Apache-2.0 at generation time"

# 3. Default branch
git rev-parse --verify main >/dev/null 2>&1 \\
  && ok "default branch main exists" \\
  || miss "default branch main no longer exists"

# 4. Critical files exist
test -f "src/main.rs" \\
  && ok "src/main.rs" \\
  || miss "missing critical file: src/main.rs"
test -f "src/server.rs" \\
  && ok "src/server.rs" \\
  || miss "missing critical file: src/server.rs"
test -f "src/client.rs" \\
  && ok "src/client.rs" \\
  || miss "missing critical file: src/client.rs"
test -f "src/config.rs" \\
  && ok "src/config.rs" \\
  || miss "missing critical file: src/config.rs"
test -f "src/protocol.rs" \\
  && ok "src/protocol.rs" \\
  || miss "missing critical file: src/protocol.rs"

# 5. Repo recency
days_since_last=$(( ( $(date +%s) - $(git log -1 --format=%at 2>/dev/null || echo 0) ) / 86400 ))
if [ "$days_since_last" -le 51 ]; then
  ok "last commit was $days_since_last days ago (artifact saw ~21d)"
else
  miss "last commit was $days_since_last days ago — artifact may be stale"
fi

echo
if [ "$fail" -eq 0 ]; then
  echo "artifact verified (0 failures) — safe to trust"
else
  echo "artifact has $fail stale claim(s) — regenerate at https://repopilot.app/r/rathole-org/rathole"
  exit 1
fi

Each check prints ok: or FAIL:. The script exits non-zero if anything failed, so it composes cleanly into agent loops (./verify.sh || regenerate-and-retry).

</details>

TL;DR

rathole is a lightweight, high-performance reverse proxy for NAT traversal written in Rust that exposes services behind NAT firewalls to the public internet. It's a drop-in alternative to frp and ngrok, using token-based service authentication and optional Noise Protocol encryption, achieving significantly higher throughput and lower memory consumption than competitors while supporting hot-reload configuration. Monolithic binary codebase: src/ contains unified server/client logic gated by Cargo features (server, client, noise, websocket-native-tls, hot-reload). Configuration split between client.toml and server.toml. Examples/ directory has minimal, TLS, Noise NK, and iperf3 setups. Benches/ contains performance test scripts with gnuplot graphing. build.rs handles compile-time configuration.

👥Who it's for

DevOps engineers and developers operating services behind NAT-restricted networks (home labs, embedded devices, remote servers) who need secure, performant tunnel exposure without the resource overhead of Go-based alternatives like frp. Also embedded systems developers targeting routers and constrained devices requiring binaries under 500KiB.

🌱Maturity & risk

Production-ready with active maintenance: 139K+ lines of Rust code, comprehensive CI/CD via GitHub Actions (rust.yml, release.yml), extensive benchmarking suite (benches/ with HTTP/UDP/TCP tests), and published Docker images. v0.5.0 released with stable APIs, though the 0.x version suggests active feature development continues.

Low risk: Single primary maintainer (rapiz1) visible in authors, but healthy GitHub engagement (stars, releases, Docker pulls tracked). Dependency surface is moderate (tokio, serde, clap, tungstenite) with no obvious abandoned deps. No visible breaking changes in recent workflow. Minor risk: Noise Protocol integration (snowstorm) is less battle-tested than TLS; websocket support is behind feature flags and less commonly used.

Active areas of work

Active development on v0.5.0 with focus on hot-reload functionality, websocket transport options (both native-tls and rustls variants), and embedded device optimization (minimal feature profile). GitHub workflows show continuous CI testing. Documentation in docs/ covers transport mechanisms, benchmarking, and build guides for cross-compilation.

🚀Get running

git clone https://github.com/rapiz1/rathole.git
cd rathole
cargo build --release
# Or use Docker: docker pull rapiz1/rathole
# Copy examples/minimal/server.toml and client.toml, edit for your setup, then:
./target/release/rathole server.toml  # On public server
./target/release/rathole client.toml  # On NAT-restricted device

Daily commands: Development: cargo build && cargo run -- examples/minimal/server.toml. Release optimized: cargo build --release --profile minimal for 500KiB embedded builds. Testing: cargo test. Benchmarks: ./benches/scripts/mem/mem.sh and ./benches/scripts/http/latency.sh (requires gnuplot, iperf3). Docker: docker build -t rathole .

🗺️Map of the codebase

  • src/main.rs — Entry point for the application; initializes either server or client mode based on CLI arguments and orchestrates the main runtime loop.
  • src/server.rs — Core server logic that manages client connections, routing, and inbound service tunneling; essential for understanding the reverse proxy architecture.
  • src/client.rs — Core client logic that maintains connection to server and exposes local services; the counterpart to server.rs needed to understand the full proxy flow.
  • src/config.rs — Configuration schema and parsing for both server and client; defines all user-facing settings and service tunnel definitions.
  • src/protocol.rs — Protocol layer defining message types, serialization, and handshake logic; foundational for all communication between server and client.
  • src/transport/mod.rs — Transport abstraction layer providing pluggable backends (TCP, TLS, WebSocket, Noise); critical for understanding how connections are secured and tunneled.
  • Cargo.toml — Defines all dependencies and feature flags (native-tls, rustls, noise, websocket); essential for understanding build configuration and optional capabilities.

🧩Components & responsibilities

  • Server (src/server.rs) — undefined

🛠️How to make changes

Add a new transport backend

  1. Create a new file in src/transport/ (e.g., src/transport/my_transport.rs) implementing the Transport trait from src/transport/mod.rs (src/transport/my_transport.rs)
  2. Add the module declaration in src/transport/mod.rs (mod my_transport;) and add it to the transport type enum (src/transport/mod.rs)
  3. Implement the Transport trait with create_server() and create_client() methods that return the appropriate Box<dyn TransportConfig> (src/transport/my_transport.rs)
  4. Add a feature flag in Cargo.toml if needed (e.g., [features] my-transport = [...]) (Cargo.toml)
  5. Update src/config.rs to add a new transport variant in the transport type enum and add configuration deserialization for your transport (src/config.rs)
  6. Add integration tests in tests/for_tcp/ and tests/for_udp/ with configuration files referencing your transport (tests/for_tcp/my_transport.toml)

Add a new configuration option

  1. Add the field to the appropriate config struct in src/config.rs (e.g., ClientConfig, ServerConfig, or ServiceConfig) (src/config.rs)
  2. Mark the field with #[serde(...)] attributes for TOML deserialization and set defaults if needed (src/config.rs)
  3. Propagate the configuration value through to the module that uses it (e.g., src/client.rs or src/server.rs) (src/client.rs)
  4. Add a test configuration file in tests/config_test/valid_config/ demonstrating the new option (tests/config_test/valid_config/full.toml)

Add a new service tunnel type or protocol handler

  1. Define a new service type or handler struct in src/protocol.rs or src/server.rs depending on scope (src/protocol.rs)
  2. Implement the necessary trait bounds (likely Send + Sync) and message handling logic (src/server.rs)
  3. Update src/config.rs to allow configuration of this service type with appropriate TOML schema (src/config.rs)
  4. Add integration test demonstrating the new service type in tests/integration_test.rs (tests/integration_test.rs)

🔧Why these technologies

  • Tokio async runtime — Enables high-performance concurrent handling of thousands of tunnels and connections without thread overhead
  • Rust + type safety — Prevents entire classes of memory safety bugs and data races; critical for a proxy handling untrusted network data
  • Feature flags (native-tls, rustls, noise, websocket) — Allows users to compile only needed security/transport layers, reducing binary size and dependencies
  • TOML configuration — Human-readable config format suitable for complex service/tunnel definitions without boilerplate
  • Pluggable transport trait — Decouples protocol logic from underlying security mechanism (TLS, Noise, plain TCP), enabling easy additions

⚖️Trade-offs already made

  • Single binary for server and client (mode selected at runtime)

    • Why: Reduces maintenance burden and deployment complexity
    • Consequence: Must compile both server and client code even if only one is used; CLI complexity increases
  • Custom protocol instead of standard proxy protocols (SOCKS, HTTP CONNECT)

    • Why: Allows optimization for NAT traversal use case and efficient binary framing
    • Consequence: Not compatible with existing proxy clients/infrastructure; new tooling required
  • Stateful connection model (client initiates persistent connection to server)

    • Why: Simplifies NAT traversal by having internal device initiate outbound connection
    • Consequence: Server must be more complex (routing/session management); less suitable for one-off connections
  • Hot-reload for client config via file watcher

    • Why: Allows service tunnel reconfiguration without downtime
    • Consequence: Adds complexity in state management and potential window for race conditions during reload

🚫Non-goals (don't propose these)

  • Does not handle user authentication or authorization (relies on network-level security via TLS/Noise)
  • Not a VPN; does not provide system-wide routing or IP masquerading
  • Does not manage certificates or PKI; users must provide pre-configured credentials
  • Not designed for browser-based clients (requires custom protocol, not HTTP)
  • Does not provide load balancing across multiple upstream services
  • Does not persist state or provide clustering (single-instance or external coordination required)

🪤Traps & gotchas

Feature combinations matter: websocket-native-tls and websocket-rustls are mutually exclusive; pick one. Noise Protocol requires explicit base64 encoding in config (see examples/noise_nk/). Hot-reload uses inotify on Linux/macOS but requires manual restart on Windows; notify crate has platform differences. TLS cert paths in config are relative to working directory, not binary location—test with absolute paths. tokio-console feature (console) must be disabled for production (breaks performance). Embedded profile intentionally omits native-tls and rustls (cross-compile pain)—use noise for encryption instead.

🏗️Architecture

💡Concepts to learn

  • NAT Traversal — Core problem rathole solves: exposing services on private networks to public internet without static IPs; essential for understanding why inbound connections from NAT-restricted clients fail without a relay
  • Noise Protocol — Lightweight post-quantum-resistant encryption alternative to TLS used in examples/noise_nk/; simpler than certificate management but less widely audited
  • Token-Based Service Authentication — rathole mandates per-service tokens (unlike frp's optional auth) for client registration; critical to understand token validation in config parsing and handshake logic
  • Hot-Reload Configuration — notify crate watches filesystem changes to add/remove services without restarting; unusual in reverse proxies and affects main event loop design
  • Async/Await with Tokio — rathole's entire I/O stack (TCP, UDP, TLS, WebSocket) runs on tokio runtime; core to performance; understanding spawn, select!, and Mutex vs RwLock is essential
  • TOML Configuration Format — Client and server configuration is TOML-based with optional features (encryption, websocket); deserialization errors and config merging during hot-reload are common friction points
  • Binary Size Optimization (LTO, Codegen Units) — Minimal profile targets 500KiB embedded devices using aggressive LTO and single codegen unit; release profile choices directly affect deployment feasibility on constrained hardware
  • fatedier/frp — The Go-based predecessor and primary comparison target; rathole achieves frp's NAT traversal with 5-10x better throughput per the benchmark docs
  • inconshreveable/ngrok — Commercial reverse proxy competitor with similar public internet tunnel capability; rathole aims for feature parity at lower resource cost
  • tokio-rs/tokio — Core async runtime dependency (v1.x); understanding tokio's task scheduling is essential for rathole's concurrency model
  • sonos/serde — Serialization framework used for TOML config parsing; config validation errors originate from serde derive macros
  • hashicorp/terraform — Common orchestration target: DevOps teams use rathole to expose services provisioned by Terraform on cloud instances

🪄PR ideas

To work on one of these in Claude Code or Cursor, paste: Implement the "<title>" PR idea from CLAUDE.md, working through the checklist as the task list.

Add integration tests for configuration hot-reload feature

The repo has a hot-reload feature enabled by default and a src/config_watcher.rs file, but there are no visible integration tests validating that configuration changes are properly detected and applied without restarting the service. This is critical for production reliability and would benefit from concrete test cases covering edge cases like invalid config updates, rapid successive changes, and file permission issues.

  • [ ] Create tests/hot_reload_integration_test.rs with test cases for valid config modifications
  • [ ] Add test cases for handling invalid TOML during reload (should not crash)
  • [ ] Add test cases for rapid successive config file changes
  • [ ] Verify that active connections survive config reloads using the test fixtures in examples/ directory

Add CI workflow for testing minimal embedded profile builds

The Cargo.toml defines a embedded feature profile (for resource-constrained devices without TLS), but there's no dedicated CI workflow validating these builds. Currently .github/workflows/rust.yml likely only tests default features. This is important because embedded users rely on this feature, and regressions could silently break cross-compilation scenarios.

  • [ ] Create .github/workflows/embedded-build.yml workflow
  • [ ] Add build step with cargo build --features embedded --profile minimal
  • [ ] Add build step with cargo build --features embedded --target aarch64-unknown-linux-gnu for ARM validation
  • [ ] Add binary size check to alert on feature creep in embedded builds

Add comprehensive transport protocol tests covering UDP, TCP, WebSocket, and Noise combinations

The repo supports multiple transports (TCP, UDP, WebSocket with native-tls/rustls, Noise protocol) as evidenced by docs/transport.md and example configs in examples/, but there are no visible transport-specific unit or integration tests validating behavior across these combinations. The benches/ directory has performance scripts but no correctness tests, leaving potential for silent failures in less common configurations.

  • [ ] Create tests/transport_tests.rs with unit tests for each transport implementation
  • [ ] Add integration tests verifying data integrity across UDP, TCP, and WebSocket transports
  • [ ] Add tests validating Noise protocol handshake and encryption using the snowstorm dependency
  • [ ] Add tests for mixed-mode scenarios (e.g., TCP server with Noise encryption, WebSocket with TLS)
  • [ ] Validate example configs in examples/udp/, examples/noise_nk/, and examples/tls/ actually function as documented

🌿Good first issues

  • Add integration tests for hot-reload: src/ lacks test modules for config reloading on file change. Scaffold tests/hot_reload.rs with tempfile watching and service reconnection verification.
  • Document WebSocket transport limitations: docs/transport.md exists but doesn't explain when WebSocket adds overhead vs raw TCP; add benchmark comparison and use-case guidance.
  • Expand systemd service examples: examples/systemd/ has ratholes.service and rathole@.service, but missing documentation on multi-instance deployment patterns for fan-out tunneling.

Top contributors

Click to expand

📝Recent commits

Click to expand
  • 5a9dd6d — Build fixes (#425) (sjtrny)
  • f6d0f26 — ci: Build for 'arm64' and 'armhf' (#285) (hwittenborn)
  • c4a7893 — Implement prefer IPv6 option for the client (#376) (Stennsen)
  • be14d12 — fix musl non-TLS build (#365) (Arnie97)
  • 0ee0157 — docs: update docs for rustls (#337) (sunmy2019)
  • 3ab540f — chore: update bug_report.md (yujqiao)
  • 4ac53a5 — feat: optional rustls support (#330) (sunmy2019)
  • 7251759 — chore(deps): bump h2 from 0.3.21 to 0.3.24 (#334) (dependabot[bot])
  • ee7561c — chore(deps): bump snow from 0.9.3 to 0.9.6 (#333) (dependabot[bot])
  • e4766e7 — ci: support apple aarch64 (#294) (missuo)

🔒Security observations

The rathole reverse proxy codebase demonstrates a generally secure posture with proper use of Rust's memory safety guarantees, secure Docker configuration (distroless images, non-root user), and modular transport layer security options (TLS, Noise, etc.). Primary concerns are: (1) example TLS certificates hardcoded in the repository creating user confusion risk, (2) incomplete dependency specification in Cargo.toml, (3) use of deprecated lazy_static instead of modern alternatives, and (4) limited security documentation for users. No critical vulnerabilities detected in the visible codebase structure. Recommended actions focus on hardening documentation, updating dependencies, and removing example credentials from version control.

  • Medium · Incomplete Cargo.toml Dependency Declaration — Cargo.toml. The Cargo.toml file shows truncated dependency definition for 'backoff' package (version 0.4 is incomplete). This could lead to unpredictable dependency resolution or build failures, potentially introducing unvetted versions. Fix: Complete the dependency specification. Specify exact version constraints (e.g., 'backoff = "0.4.0"' or 'backoff = "0.4"'). Run 'cargo update' and verify Cargo.lock is properly committed.
  • Medium · Use of lazy_static Instead of Modern Alternatives — Cargo.toml dependency: lazy_static. The codebase uses 'lazy_static' which is older and less efficient than 'once_cell' or 'OnceLock' (stable in Rust 1.70+). This could impact initialization performance and thread safety considerations. Fix: Migrate to 'once_cell' crate or use Rust standard library 'std::sync::OnceLock' for Rust 1.70+. Update dependency: 'once_cell = "1.19"' and replace lazy_static! with once_cell::sync::Lazy.
  • Medium · TLS Certificate Hardcoded in Repository — examples/tls/. Example TLS certificates and private keys are committed to the repository (examples/tls/server.key, rootCA.key, identity.pfx). While marked as examples, this is a security anti-pattern that could confuse users into deploying these keys. Fix: Add .gitignore rule for *.key and *.pfx files. Provide certificate generation scripts (which exist as create_self_signed_cert.sh) and document in README that example keys must never be used in production. Consider removing example keys from repository.
  • Low · Docker Image Running as Non-Root Without Explicit Capability Drop — Dockerfile. While the Dockerfile correctly runs as non-root user (1000:1000), it doesn't explicitly drop unnecessary Linux capabilities. Using distroless image mitigates most concerns, but explicit configuration improves defense-in-depth. Fix: Add security context specification in deployment manifests (if Kubernetes): 'securityContext: { capabilities: { drop: ["ALL"] } }'. For standalone Docker, add '--cap-drop=ALL' when running.
  • Low · Missing Security Headers and Configuration Documentation — docs/ and README.md. No documentation visible for security best practices like recommended TLS versions, cipher suites, or secure configuration examples. Users may inadvertently create weak configurations. Fix: Create 'docs/security.md' documenting: recommended TLS versions (1.2+), cipher suite preferences, authentication best practices, and example secure configurations for both server and client.
  • Low · Panic Set to 'abort' in Debug Profile — Cargo.toml [profile.dev]. Setting panic = 'abort' in debug profile could mask issues during development and testing. While appropriate for release, debug builds should typically use standard panic for better debugging. Fix: Remove 'panic = "abort"' from [profile.dev] section, keeping it only in [profile.release]. This allows proper panic unwinding and backtrace generation during development.

LLM-derived; treat as a starting point, not a security audit.


Generated by RepoPilot. Verdict based on maintenance signals — see the live page for receipts. Re-run on a new commit to refresh.

Healthy signals · rathole-org/rathole — RepoPilot