Troubleshooting
Most p2proxy issues fall into a handful of buckets. Find your symptom below; each row links to a deeper section if needed.
Troubleshooting matrix
| Symptom | Likely cause | First check | Fix |
|---|---|---|---|
| Daemon exits immediately with an auth error | BITPING_API_KEY not set or invalid |
echo $BITPING_API_KEY |
Re-export the key. Confirm it’s the one from the dashboard, not an old/rotated one. |
| Stuck on “waiting for peer” forever | Peer filter too strict — no peer matches | Try removing country and lowering min_bandwidth |
Loosen the filter, or confirm peer availability on bitping.com. |
curl says “Connection refused” on localhost:1080 |
p2proxy not listening on that port | ss -tlnp | grep 1080 |
Confirm the daemon is running; check logs with RUST_LOG=debug. |
| Random failures during long scrapes | Exit peer became unhealthy, failover took a moment | Watch RUST_LOG=debug for peer rotation messages |
Confirm peer availability; a pinned peer with no failover entry can’t rotate — add more destination_peers or use fallback_to_discovery. |
| Hangs behind a corporate firewall | libp2p UDP egress blocked | Try with libp2p UDP egress allowed | Allow UDP egress, or accept relay-only mode (slower). |
| Docker container immediately dies | TTY mismatch with the TUI | Check container logs | Ensure NO_UI=true is set (it is by default in the official image). |
| TUI looks garbled | Terminal doesn’t support 24-bit colour | echo $TERM |
Use --no-ui, or upgrade your terminal (iTerm2, WezTerm, modern gnome-terminal). |
Failed to bind port |
Another process already on that port | ss -tlnp | grep <port> |
Change the per-server port: in Config.yaml. |
Permission denied binding port < 1024 |
Linux capability needed | — | Bind to a port ≥ 1024, or sudo setcap 'cap_net_bind_service=+ep' $(which p2proxy). |
| Exit peer changed after a restart | sticky_peers.json missing, or the server’s filters changed |
cat sticky_peers.json |
Run from a stable working directory. Changing country/asn/min_bandwidth/port invalidates the sticky entry by design. |
/metrics endpoint not responding |
Port collision or wrong port | curl http://localhost:9091/metrics |
Confirm metrics_port; default is 9091. |
| WireGuard client never handshakes | Listener bound to loopback, or UDP not reaching it | ss -ulnp | grep 51820 |
Set host: 0.0.0.0 on the server, open the UDP port in your firewall, and publish it as /udp in Docker. |
| WireGuard handshake starts but never completes | Key or preshared-key mismatch | curl -s localhost:9091/metrics | grep p2proxy_wg_decap_errors_total |
The client’s [Peer] PublicKey must be the public half of the server’s private_key; PresharedKey must match exactly on both sides. |
| Tunnel is up, no traffic flows | Client CIDR missing from the peer’s allowed_ips |
Watch p2proxy_wg_spoofed_source_total and p2proxy_wg_egress_unrouted_total |
Give each peer its own /32 covering its [Interface] Address. See allowed_ips. |
| The whole machine loses internet when the tunnel comes up | The WireGuard client is on the same machine as p2proxy, and AllowedIPs = 0.0.0.0/0 routed p2proxy’s own upstream traffic into its own tunnel |
wg-quick down restores connectivity instantly |
Run p2proxy on a different machine, or narrow the client’s AllowedIPs. Full explanation: WireGuard gotchas. |
| Websites hang / stall mid-page over WireGuard | Path MTU smaller than the tunnel’s | Try mtu: 1380 |
Lower wireguard.mtu. The MSS is already clamped, so this only bites over another encapsulation. |
| QUIC, WebRTC or game traffic dead over WireGuard | Exit peer only speaks the TCP forwarder | curl -s localhost:9091/metrics | grep p2proxy_wg_udp_peer_incapable_total |
DNS still works (retried over TCP); other UDP is dropped. Pin to a current peer with destination_peers. |
| WireGuard throughput far below the peer’s link | Default per-flow TCP window vs. a long-RTT exit | Compare with a nearer country |
Raise wireguard.tcp_buffer_bytes (256 KiB default), watching RAM — it’s per flow. |
Useful debug tactics
Turn the logs up
Logging follows the RUST_LOG env var, which overrides the log_level config key:
RUST_LOG=debug p2proxy --no-ui --config Config.yaml
For really noisy traces, scope by module:
RUST_LOG=p2proxy=trace,libp2p=info p2proxy --config Config.yaml
Verify the SOCKS5 hop
Bypass your application logic and prove the daemon is reachable:
curl -v --socks5-hostname localhost:1080 https://ifconfig.me
If the IP you get back is the peer’s, p2proxy is healthy and the problem is in the calling application.
Watch the metrics
When something’s intermittently broken, the metrics endpoint usually tells the story:
curl -s http://localhost:9091/metrics | grep -E 'p2proxy_(peers_connected|sessions_active|socks_rejected|session_errors)'
A p2proxy_peers_connected value of 0 means peer selection isn’t finding matches — relax your filter.
For a WireGuard server, the tunnel side has its own series:
curl -s http://localhost:9091/metrics | grep -E 'p2proxy_wg_(flows_accepted|decap_errors|spoofed_source|egress_unrouted|udp_dropped|udp_peer_incapable)'
p2proxy_wg_flows_accepted_total stuck at 0 means no packet ever made it out of the tunnel — the problem is the handshake or allowed_ips, not the peer network.
Still stuck?
Head to Support for escalation paths. For WireGuard specifically, the gotchas section covers the traps that don’t look like bugs.