Skip to main content

frp-jump

Connect Linux boxes (including Wiren Board controllers) to each other over the internet using the SSH/TCP clients you already have — no manual port juggling, and no caring whether the link ended up peer-to-peer or relayed through your server.

  • Tunneling is frp (frpc/frps), driven through an abstract TunnelDriver/RelayDriver interface — see docs/architecture.md.
  • P2P with relay fallback is frp's own xtcp + fallbackTo feature: a device pair first tries a direct (hole-punched) connection, and transparently falls back to relaying through your server if that doesn't complete within a timeout.
  • Identity: a person is identified by an SSH public key (users add-key), not an email/password account. There is no WebUI — admin actions are frp-jump-server commands run over SSH to the box.
  • Encryption: a private CA (run by your server) issues an mTLS cert to every enrolled device, so nothing unenrolled can reach the relay at all. A per-pair secret gates every connection, and the tunneled traffic itself — not just the control channel — is encrypted too.
  • Self-service: once a person's first device is enrolled, they manage everything else themselves from the CLI (adding devices, connecting to each other), scoped to devices they own. Disabling a device or deleting a grant takes effect on its next sync, not instantly.
  • UX goal: after setup, ssh <name> just works with your stock ssh client, whether the path underneath is p2p or relayed.

Installing the CLI

Two supported ways to install, plus a manual fallback. Either one gets you the same frp-jump-client/frp-jump-server CLIs; pick whichever fits how you manage the box.

Option A: pip

Published on PyPI as frp-jump, Python 3.12+ required. It splits into two lean pieces sharing one package, so a device install doesn't pull in the server's dependencies:

python3 -m venv .venv    # Debian/Ubuntu: `apt install python3-venv` first

# on the server box:
.venv/bin/pip install 'frp-jump[server]'   # pulls in fastapi/uvicorn/sqlmodel
.venv/bin/frp-jump-server ...

# on every device you want to connect (including headless/IoT ones):
.venv/bin/pip install frp-jump             # lean: no server-only deps
.venv/bin/frp-jump-client ...

Put .venv/bin on PATH, or call the binaries by their full path.

Option B: apt repository (Debian/Ubuntu, including Wiren Board)

A self-contained .deb (own Python 3.12 runtime, no venv/pip involved) via a signed apt repo hosted on GitHub Pages — add it once, then apt upgrade picks up new releases like any other package:

sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://aadegtyarev.github.io/frp-jump/frp-jump-archive-keyring.asc \
  | sudo gpg --dearmor -o /etc/apt/keyrings/frp-jump.gpg
echo "deb [signed-by=/etc/apt/keyrings/frp-jump.gpg] https://aadegtyarev.github.io/frp-jump stable main" \
  | sudo tee /etc/apt/sources.list.d/frp-jump.list

sudo apt update
sudo apt install frp-jump-client

Only frp-jump-client is packaged as a .deb today — run the server side via pip.

Fallback: download a .deb directly

Each GitHub Release also attaches the .deb for each architecture (amd64/arm64/armhf) individually — useful for an air-gapped box, or if you'd rather not add the apt repo. You're then on your own for upgrades (repeat the download each release):

wget https://github.com/aadegtyarev/frp-jump/releases/download/vX.Y.Z/frp-jump-client_X.Y.Z_arm64.deb
sudo apt install ./frp-jump-client_X.Y.Z_arm64.deb

Every command has full --help text with runnable examples — start there if anything below is unclear (frp-jump-client <command> --help).

Quick start

On the server (a box with a public IP/domain):

sudo frp-jump-server install-service --relay-public-addr tunnel.example.com

That one command creates a dedicated, unprivileged frp-jump system user, bootstraps the CA/database under its own /var/lib/frp-jump, and installs

  • starts a hardened systemd unit — see "systemd" below. To do it by hand instead:
export FRP_JUMP_RELAY_PUBLIC_ADDR=tunnel.example.com   # or a bare IP
frp-jump-server init
frp-jump-server run          # foreground; wrap with systemd for real use

In another shell (or over SSH, any time later), register yourself:

frp-jump-server users add-key ~/.ssh/id_ed25519.pub --label me

Now enroll your first device — key-based needs no token, once your key is registered; token-based works for a device you don't want to hand a personal key to:

frp-jump-client enroll https://tunnel.example.com ~/.ssh/id_ed25519 --name laptop

# or, token-based:
frp-jump-server enroll-tokens create --user me --name wb01
frp-jump-client enroll https://tunnel.example.com <token-from-above>

Run as root, enroll also installs and starts the systemd service right away; otherwise it tells you to run one of these next:

frp-jump-client install-service                                  # system-wide, needs root
frp-jump-client install-service --user                            # your own account, no root needed
frp-jump-client install-service --system-user frp-jump-client     # isolated account (created for you)

From there, everything is self-service — no more admin action needed for that person, ever, even to add a tenth device or reconfigure who talks to whom:

frp-jump-client devices add-token               # mint a token to chain-enroll
                                                 # another of your own devices
frp-jump-client devices list                    # devices you own
frp-jump-client connect wb01:22                 # tunnel to port 22 on "wb01"
                                                 # (22/2222 are well-known ssh
                                                 # ports; anything else is a
                                                 # plain tcp tunnel, or --ssh)
frp-jump-client status                          # what's exposed/consumed,
                                                 # local addresses once synced
frp-jump-client doctor                          # something seems wrong? run
                                                 # this first
frp-jump-client disconnect wb01                 # tear it back down
frp-jump-client devices disable old-laptop      # lost it? block its connections
frp-jump-client devices delete old-laptop       # gone for good, frees the name

Once synced (each side's agent polls every agent_poll_interval_seconds, default 2s — override per-run with frp-jump-client run --poll-interval N):

ssh wb01                     # just works -- `connect`'s local profile name
                              # doubles as the ssh_config Host alias

An admin can see everyone's devices and connections (including a compact relayed-traffic figure) without touching anyone's box:

frp-jump-server users list
frp-jump-server users show me
frp-jump-server devices list
frp-jump-server devices disable wb01 --user me    # force-disconnect + block,
                                                   # reversible with `enable`

Configuration

Everything configurable lives in one place: src/frp_jump/common/settings.py — set via FRP_JUMP_<FIELD> environment variables, or a TOML file ($FRP_JUMP_CONFIG_FILE, else the first of ./frp-jump.toml, ~/.config/frp-jump/config.toml, /etc/frp-jump/config.toml that exists). Env vars win over the file.

The only setting with no sane default is relay_public_addr — the address other devices dial to reach your relay; frp-jump-server init/run refuse to start without it.

Ports and firewalls

On the server, open these inbound (both plain TCP, no exotic protocols):

Port Setting What it's for
8443/tcp api_port The agent-facing HTTPS API (enroll, heartbeat, desired-state, self-service)
7000/tcp relay_bind_port frps control channel, dialed by every enrolled device's frpc

frps_admin_port (7500) is bound to 127.0.0.1 only and never needs a firewall rule.

On every client device, allow outbound to those same two ports on your relay. p2p (xtcp) hole-punching also needs outbound UDP to work — a firewall that blocks it (or restricts frpc's ephemeral source ports) won't break anything outright, but every connection will silently fall back to relay instead of going peer-to-peer, since the relay fallback only needs the already-open TCP control channel above. The consumer's own locally-bound port (agent_local_port_range_*, default 40000-40999) is only ever bound to 127.0.0.1, so it never needs a firewall rule either.

systemd

Client — frp-jump-client install-service [--user | --system-user NAME] generates and enables the unit for you (see "Quick start" above). Which mode to pick:

  • A device that consumes an SSH grant needs the agent running as the actual human (so it can maintain their real ~/.ssh/config) — use --user, or point FRP_JUMP_SSH_CONFIG_PATH at that person's config explicitly.
  • A device that only exposes services (e.g. a Wiren Board controller), or consumes without needing ssh <name> to just work, is fine as a system service or under --system-user — an isolated, least-privilege account created for you.

Reach for packaging/systemd/ directly only for a manual or packaged (.deb) install.

Server — frp-jump-server install-service [--system-user NAME] [--relay-public-addr ADDR] is the equivalent one-shot setup: creates the system account, bootstraps the CA/database, writes /etc/frp-jump/<name>.env, and installs a hardened unit (ProtectSystem=strict, NoNewPrivileges=yes, one writable data directory) — matching packaging/systemd/frp-jump-server.service if you'd rather set it up by hand.

Development

uv sync
uv run pytest tests/unit -q        # fast, no network
uv run ruff check .
uv run pytest tests/integration -m integration -q   # downloads real frp binaries; loopback only

The integration test proves the whole chain works over loopback (real frp binaries, real mTLS, real xtcp-timeout-then-stcp-fallback), but it can't prove real NAT hole-punching across two separate networks — check that manually on real devices.

Layout

src/frp_jump/
  common/     PKI (private CA), opaque tokens, settings, DB models
  driver/     TunnelDriver/RelayDriver abstraction; driver/frp/ = the frp
              implementation (config rendering, binary download+checksum,
              process supervision)
  server/     control-plane: registry (CRUD), the agent-facing API,
              bootstrap (`server init`) -- no WebUI, admin is CLI-only
  agent/      runs on every device: enroll (token or SSH key), the sync
              loop, local profiles, ssh_config management, systemd
              install (`agent/service_install.py`)
  cli/        `frp-jump-server ...` / `frp-jump-client ...`

Contributing

See CONTRIBUTING.md. Changes are tracked in CHANGELOG.md.

License

MIT

Release files for frp-jump 0.3.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for frp-jump 0.3.4
File Size Uploaded
frp_jump-0.3.4.tar.gz 67.0 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for frp-jump 0.3.4
File Interpreter ABI Platform
frp_jump-0.3.4-py3-none-any.whl Python 3 none any Details

Total release size: 148.0 kB

Release files / frp_jump-0.3.4.tar.gz

Download URL frp_jump-0.3.4.tar.gz
Size 67.0 kB
Tags Source
SHA-256 checksum
How to use checksums
118140a2084c25395fcb413a8e6aca2712cb568de73364fac9879ca2c1203fb7
BLAKE2b-256 checksum
How to use checksums
9ec59430670a92e38d1c4630f78e576f4747cec0859b452d6a25afe0e61e61f3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release files / frp_jump-0.3.4-py3-none-any.whl

Download URL frp_jump-0.3.4-py3-none-any.whl
Size 80.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e1d75a234fc5b9f74ee839fa576235be9ba0e248d4b0700c9442c7081aef80c1
BLAKE2b-256 checksum
How to use checksums
1cf0cd8581b4e9a0f833333472c319a2403511fadd14137b9d31d61d7517867c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 26, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.12

2 release files

0.3.11

2 release files

0.3.10

2 release files

0.3.9

2 release files

0.3.8

2 release files

0.3.7

2 release files

0.3.6

2 release files

0.3.5

2 release files

This release

0.3.4 This release

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.1

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page