A peer-to-peer SOCKS5 proxy daemon built on libp2p. It routes outbound traffic through the Bitping network of distributed peer nodes instead of through a single centralised proxy provider.
- SOCKS5 on a local port — point any application (browser,
curl, Playwright, a whole WireGuard tunnel) at it. - Filtered peer selection — pick exits by minimum bandwidth, country, city, ISP, ASN, network type (proxy / mobile / hosting), or a specific peer ID.
- Stable egress IPs — pin an ordered list of peer IDs, or let sticky mode remember the discovered exit across restarts and reconnects.
- Live TUI for status (peers, sessions, bandwidth, the rotation pool);
--no-uifor headless / systemd / Docker. - Prometheus metrics for observability.
Architecture (ports & adapters): see ARCHITECTURE.md.
Prebuilt archives are published on the releases page for macOS and Linux × x86_64 and aarch64.
tar -xJf p2proxy-<version>-<os>-<arch>.tar.xz
cd p2proxy-<version>-<os>-<arch>
./p2proxy --helpgit clone https://github.com/BitpingApp/p2proxy.git
cd p2proxy
cargo build --release -p p2proxy
./target/release/p2proxy --helpRequires a recent stable Rust toolchain (rustup install stable).
Native packages (Homebrew,
deb/rpm/apk) and a published Docker image are planned; for now use the tarball or build from source. ADockerfileanddocker-compose.ymlare included for self-hosting your own image.
-
Get a Bitping API key at https://bitping.com (dashboard → API key).
-
Provide it — either in
Config.yaml(bitping_api_key: …) or via the environment (the env var wins):export BITPING_API_KEY=<your-key>
-
Write a
Config.yaml(the bundled one is a working starting point):servers: - protocol: Socks5 port: 1080 min_bandwidth: 50Mbps country: NL # optional; omit for any country
-
Run it:
p2proxy --config Config.yaml
The TUI comes up and picks a peer (usually within a second or two). Point your client at
socks5://localhost:1080:curl --socks5-hostname localhost:1080 https://ifconfig.me
The IP you get back should be the peer's, not yours.
Use
--socks5-hostname, not--socks5. Plain--socks5makes curl resolve the destination locally and hand the proxy a raw IP — usually a CDN edge near you, which often doesn't route from the peer's side and fails TLS withSSL_ERROR_SYSCALL.--socks5-hostname(SOCKS5h) lets the peer do the DNS, so it reaches a CDN edge near it. Same idea elsewhere: usesocks5h://URLs in Pythonrequests, Playwright proxy config, etc.To eyeball the exit + check for leaks in a browser, the repo's
just p2proxy-browserlaunches Firefox through the proxy with remote DNS and WebRTC disabled.
Config.yaml is YAML; every key except servers and bitping_api_key has a default. Environment variables override file values.
| Key | Type | Default | Description |
|---|---|---|---|
bitping_api_key |
string | — (required) | Your Bitping API key. May instead be supplied via the BITPING_API_KEY env var (which overrides this). |
servers |
list | — (required) | One or more proxy endpoints (below). |
listen_addrs |
list of host:port |
0.0.0.0:0 and [::]:0 (any port) |
Addresses the libp2p stack listens on (TCP + QUIC are bound per entry). Takes precedence over port. |
port |
u16 | — | Shorthand that fixes the libp2p port on the default listen addresses (e.g. 45445). Ignored when listen_addrs is set. |
log_level |
string | info |
Default log level when RUST_LOG is unset (trace/debug/info/warn/error, or a full directive). RUST_LOG overrides it. |
metrics_port |
u16 | 9091 |
Port for the Prometheus endpoint (bound on 0.0.0.0). |
bootstrap_address |
multiaddr | /dnsaddr/boot2.bitping.com |
Bootstrap hub. Override for staging / a self-hosted hub. |
grpc_url |
url | https://grpc.bitping.com |
Bitping auth service endpoint. |
keypair_path |
path | node_keypair.bin |
Where the node's libp2p identity is persisted (CWD-relative). |
Logging follows
RUST_LOGwhen set (e.g.RUST_LOG=p2proxy=debug); otherwise thelog_levelconfig key above (defaultinfo).
| Key | Type | Default | Description |
|---|---|---|---|
protocol |
enum | — | Socks5 (the only protocol today). |
port |
u16 | — | Local TCP port the SOCKS5 listener binds. |
min_bandwidth |
string | 50Mbps |
Minimum advertised peer bandwidth. Format <N>{bps,Kbps,Mbps,Gbps}. |
country |
string | — | Country filter. Accepts Alpha-2 (AU), Alpha-3 (AUS), or name (Australia) — normalised to Alpha-2. Omit for any country. |
city |
string | — | City filter — a bare city name (Tiel) or City; Region; Country. Omit for any city. |
isp |
string | — | ISP filter — a regex matched against the node's ISP (e.g. Comcast). |
asn |
u32 | — | Autonomous System Number filter (e.g. 1136). |
proxy |
enum | allow |
Proxy-node policy: allow (any), deny (no proxies), or require (only proxies). |
mobile |
enum | allow |
Mobile-network policy: allow, deny, or require. |
hosting |
enum | allow |
Hosting/datacenter policy: allow, deny (residential only), or require (datacenter only). |
destination_peers |
list | — | Ordered pinned-peer preference list (see below). |
fallback_to_discovery |
bool | false |
When all pinned peers are offline: false keeps retrying the list; true falls back to country/bandwidth discovery. |
sticky |
bool | true |
Remember the discovered exit in sticky_peers.json and reuse it across restarts. Ignored when destination_peers is set. |
sticky_reconnect |
enum | with-backoff |
On exit-peer disconnect: with-backoff fights to reconnect the same peer (stored direct address, then a hub-resolved relay circuit) before rotating; fail-fast rotates immediately. |
pool |
object | — | Per-server stream tuning (below). |
Both give stable egress IPs — the exit your traffic appears from changes only when the node actually becomes unreachable.
Pinned peers (destination_peers) is an ordered preference list:
servers:
- protocol: Socks5
port: 1080
destination_peers:
- 12D3KooWPrimaryPeerId... # always tried first
- 12D3KooWBackupPeerId... # failover while the primary is unreachable- Bare peer ids are resolved to the peer's current route through the hub on every (re)connect, so a pin survives the peer moving between hubs. A full multiaddr ending in
/p2p/<id>is also dialed verbatim. - Hard pin by default: when every listed peer is offline, p2proxy keeps retrying and surfaces an error rather than silently routing through an arbitrary node. Set
fallback_to_discovery: trueto prefer availability over identity.
Sticky peers (sticky: true, default for unpinned servers) gives stable IPs without naming peers up front: the first discovery matching your selection filters (country/city/isp/asn/min_bandwidth/proxy/mobile/hosting) is remembered in sticky_peers.json (next to node_keypair.bin) and reused across restarts and reconnects, with a small standby pool for fast failover. Changing any of a server's filters or its port invalidates the remembered set automatically.
The old single
destination_peerkey was removed — usedestination_peers(a list). Restarts no longer rotate the exit IP by default; setsticky: falsefor a fresh peer each run.
A given Config.yaml's servers never conflict (the store keys by listen port), but two p2proxy instances must run from different working directories — each owns its own node_keypair.bin and sticky_peers.json.
This is not a connection pool — streams are not kept warm; every SOCKS5 connection opens a fresh libp2p stream.
| Key | Type | Default | Description |
|---|---|---|---|
max_total |
usize | 30 |
Max concurrent stream opens to a single peer; also bounds how many remembered exits the sticky pool keeps. |
open_timeout_secs |
u64 | 20 |
Give up opening a stream after this long. |
| Flag / env | Default | Description |
|---|---|---|
--config <path> / -c, P2PROXY_CONFIG |
Config.yaml |
Path to the config file. |
--no-ui, NO_UI |
off | Run headless without the TUI. |
BITPING_API_KEY |
— | API key (overrides bitping_api_key in the file). |
RUST_LOG |
info |
Tracing filter. |
p2proxy --no-ui --config /etc/p2proxy/Config.yaml# /etc/systemd/system/p2proxy.service
[Unit]
Description=Bitping p2proxy
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
Environment=BITPING_API_KEY=<your-key>
WorkingDirectory=/var/lib/p2proxy
ExecStart=/usr/local/bin/p2proxy --no-ui --config /etc/p2proxy/Config.yaml
Restart=on-failure
RestartSec=5
DynamicUser=yes
StateDirectory=p2proxy
[Install]
WantedBy=multi-user.targetWorkingDirectory/StateDirectory matter: node_keypair.bin and sticky_peers.json are written relative to the CWD.
Prometheus metrics are exposed on 0.0.0.0:<metrics_port> (default :9091). Selected series:
p2proxy_peers_connected— gauge of connected libp2p peers.p2proxy_sessions_active— gauge of in-flight SOCKS sessions.p2proxy_socks_connections_total,p2proxy_sessions_initialized_total— accepted / fully-established sessions.p2proxy_upload_bytes_total,p2proxy_download_bytes_total— bytes relayed each direction.p2proxy_session_errors_total{stage}— session failures by stage (handshake/request/peer-connection/data-transfer).p2proxy_stream_opened_total,p2proxy_stream_acquire_failed_total,p2proxy_stream_acquire_duration_seconds— peer-stream opens.p2proxy_ping_rtt_seconds,p2proxy_ping_failures_total— peer liveness.p2proxy_socks_jit_discovery_total,p2proxy_socks_rejected_no_peer_total— discovery pressure.
scrape_configs:
- job_name: p2proxy
static_configs:
- targets: ["localhost:9091"]Every release includes a _SHA256SUMS file:
curl -LO https://github.com/BitpingApp/p2proxy/releases/download/v<version>/p2proxy-<version>_SHA256SUMS
sha256sum -c p2proxy-<version>_SHA256SUMSPolyForm Shield 1.0.0 — run p2proxy freely; don't repackage it as a competing product. See LICENSE for the exact terms.
- Bugs / features: GitHub Issues.
- Security: see SECURITY.md — email
security@bitping.comrather than filing a public issue. - Questions: bitping.com.