Operations

This page covers running p2proxy as a long-lived service.

systemd

A minimal unit file:

# /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>
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.target

Enable and start:

sudo systemctl daemon-reload
sudo systemctl enable --now p2proxy
journalctl -u p2proxy -f

DynamicUser=yes runs the daemon as a transient unprivileged user. StateDirectory=p2proxy gives it /var/lib/p2proxy for the libp2p keypair.

If you need to bind a SOCKS5 listener below port 1024, either pick a high port (recommended) or grant the binary the capability:

sudo setcap 'cap_net_bind_service=+ep' "$(which p2proxy)"

Docker

The official image is ghcr.io/bitpingapp/p2proxy:latest. NO_UI=true is set by default so the daemon doesn’t try to draw a TUI without a TTY.

docker run -d --name p2proxy \
  -p 1080:1080 \
  -p 45445:45445/udp \
  -e BITPING_API_KEY=<your-key> \
  -v "$PWD/Config.yaml:/app/Config.yaml:ro" \
  ghcr.io/bitpingapp/p2proxy:latest

You need to expose:

  • Every per-server SOCKS5 port (-p 1080:1080, etc.) so clients on the host or network can reach it.
  • The libp2p UDP port (-p 45445:45445/udp). Not strictly required, but improves direct-vs-relay connectivity.

docker-compose

A reference docker-compose.yml ships in the repo. Typical shape:

services:
  p2proxy:
    image: ghcr.io/bitpingapp/p2proxy:latest
    restart: unless-stopped
    environment:
      BITPING_API_KEY: $BITPING_API_KEY
    ports:
      - "1080:1080"
      - "45445:45445/udp"
      - "9091:9091" # metrics
    volumes:
      - ./Config.yaml:/app/Config.yaml:ro

Prometheus metrics

p2proxy exposes Prometheus metrics on http://localhost:9091/metrics by default. Useful series:

SeriesTypeNotes
p2proxy_peer_connections{state=...}gaugePeer connections by state.
p2proxy_socks5_requests_total{outcome=...}counterPartitioned by success / error.
p2proxy_bandwidth_bytes_total{direction=...}counterBytes routed, both directions.
p2proxy_stream_pool_size{server=..., state=...}gaugePool size by state (idle / in-use).

A minimal scrape config:

scrape_configs:
  - job_name: p2proxy
    static_configs:
      - targets: ["localhost:9091"]

For dashboards, the metrics you’ll look at most often are pool idle size (should hover near min_idle) and the request counter split by outcome. A rising error rate is the leading indicator of peer trouble.

Upgrading

  • Homebrew: brew upgrade --cask BitpingApp/tap/p2proxy
  • Debian/Ubuntu/RHEL/Alpine: download the new package from the releases page and reinstall.
  • Docker: pull the new tag and recreate the container — docker compose pull && docker compose up -d.
  • From source: git pull && cargo build --release -p p2proxy.

Restart the daemon to pick up the new binary. Config.yaml is read at startup, so config changes also require a restart.

Keypair / peer identity

p2proxy persists its libp2p keypair to ~/.config/p2proxy/node_keypair.bin (or $StateDirectory/node_keypair.bin under systemd). Deleting this file changes your peer ID; the daemon regenerates one on next start. You usually don’t need to touch this.

Next

© 2026 Bitping Pty. Ltd.