Access your home server from anywhere. Share services with friends. No cloud, no account, no SaaS dependency.
peer-up connects your devices through firewalls and CGNAT using encrypted P2P tunnels with SSH-style authentication. One binary, zero configuration servers, works behind any NAT.
| Date | What's New |
|---|---|
| 2026-02-18 | Private DHT - Peer discovery now runs on /peerup/kad/1.0.0, fully isolated from the public IPFS network |
| 2026-02-23 | Relay pairing - peerup relay pair generates pairing codes, peerup join accepts them. SAS verification, reachability grades (A-F) |
| 2026-02-17 | Daemon mode - Background service with Unix socket API, cookie auth, and 15 REST endpoints |
| 2026-02-17 | Network tools - P2P ping, traceroute, and name resolution (standalone or via daemon) |
| 2026-02-16 | Service management - peerup service add/remove/enable/disable from the CLI |
| 2026-02-16 | Config self-healing - Archive, rollback, and commit-confirmed pattern for safe remote changes |
| 2026-02-16 | AutoNAT v2 - Per-address reachability detection with nonce verification |
| 2026-02-16 | Headless pairing - --non-interactive flag for scripted invite/join workflows |
| 2026-02-15 | Structured logging - log/slog throughout, sentinel errors, build version embedding |
| Use Case | Command |
|---|---|
| SSH to your home machine behind CGNAT | peerup proxy home ssh 2222 → ssh -p 2222 localhost |
| Remote desktop through NAT | peerup proxy home xrdp 13389 → connect to localhost:13389 |
| Share Jellyfin with a friend | peerup invite on your side, peerup join <code> on theirs |
| AI inference on a friend's GPU | peerup proxy friend ollama 11434 → curl localhost:11434 |
| Any TCP service, zero port forwarding | peerup proxy <peer> <service> <local-port> |
| Check connectivity | peerup ping home or peerup traceroute home |
peer-up works with two machines and zero network effect - useful from day one.
If someone shared an invite code with you:
# Install (or build from source: go build -o peerup ./cmd/peerup)
peerup join <invite-code> --name laptopThat's it. You're connected and mutually authorized.
1. Set up both machines:
go build -o peerup ./cmd/peerup
peerup init2. Pair them (on the first machine):
peerup invite --name home
# Shows invite code + QR code, waits for the other side...3. Join (on the second machine):
peerup join <invite-code> --name laptop4. Use it:
# On the server - start the daemon with services exposed
peerup daemon
# On the client - connect to a service
peerup proxy home ssh 2222
ssh -p 2222 user@localhostIf a relay admin shared a pairing code:
peerup join <pairing-code> --name laptop
# Connects to relay, discovers other peers, auto-authorizes everyone
# Shows SAS verification fingerprints for each peerThe relay admin generates codes with peerup relay pair --count 3 (for 3 peers). Each person joins with one command. Everyone in the group is mutually authorized and verified.
Relay server: All machines connect through a relay for NAT traversal. See relay-server/README.md for deploying your own. Run
peerup relay serveto start a relay.
peer-up was created to solve one problem: reaching a service on a home server from outside the network without depending on anyone else's infrastructure.
Existing solutions require either a cloud account, a third-party VPN, or port forwarding - which CGNAT frequently makes impossible. They all share the same flaw: your connectivity depends on someone else's servers and their permission to keep it running.
peer-up uses a different model. Devices connect outbound to a lightweight relay for initial setup, then upgrade to direct peer-to-peer when possible. No accounts, no central identity server, no revocable subscriptions. Your keys stay on your machine, configuration lives in one YAML file, and you can run your own relay for zero external dependency.
Your devices are behind firewalls and NAT that block inbound connections. This affects:
- Satellite ISPs with Carrier-Grade NAT (CGNAT)
- Mobile networks (4G/5G), almost universally behind CGNAT
- Many broadband providers worldwide applying CGNAT to conserve IPv4 addresses
- University and corporate networks with strict firewalls
- Double-NAT setups - router behind router
Traditional solutions require either port forwarding (impossible with CGNAT), a VPN service (another dependency), or a cloud intermediary (defeats self-hosting). peer-up solves this with a lightweight relay that both sides connect to outbound, then upgrades to a direct connection when possible.
| Feature | Description |
|---|---|
| NAT Traversal | Circuit relay v2 + DCUtR hole-punching. Works behind CGNAT, symmetric NAT, double-NAT |
| SSH-Style Auth | authorized_keys peer allowlist - only explicitly trusted peers can connect |
| 60-Second Pairing | peerup invite + peerup join - exchanges keys, adds auth, maps names automatically |
| TCP Service Proxy | Forward any TCP port through P2P tunnels (SSH, XRDP, HTTP, databases, AI inference) |
| Daemon Mode | Background service with Unix socket API, cookie auth, hot-reload of auth keys |
| Config Self-Healing | Last-known-good archive, rollback, and commit-confirmed pattern for safe remote changes |
| Private DHT | Kademlia peer discovery on /peerup/kad/1.0.0 - isolated from public networks |
| Friendly Names | Map names to peer IDs in config - home, laptop, gpu-server instead of raw peer IDs |
| Reusable Library | pkg/p2pnet - import into your own Go projects for P2P networking |
| Single Binary | One peerup binary with 16 subcommands. No runtime dependencies |
| Cross-Platform | Go cross-compiles to Linux, macOS, Windows, ARM, and more |
| systemd + launchd | Service files included for both Linux and macOS |
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ Client │───────▶│ Relay Server │◀────────│ Server │
│ (Phone) │ outbound (VPS) outbound │ (Linux/Mac) │
└──────────┘ └──────────────┘ └──────────────┘
│
Both connect OUTBOUND
Relay bridges the connection
DCUtR upgrades to direct P2P
- Server runs
peerup daemonbehind CGNAT, connects outbound to a relay and reserves a slot - Client runs
peerup proxy, connects outbound to the same relay and reaches the server through a circuit address - DCUtR (Direct Connection Upgrade through Relay) attempts hole-punching. If successful, traffic flows directly without the relay
Peer discovery uses a private Kademlia DHT - the relay server acts as bootstrap peer. Authentication is enforced at both the connection level (ConnectionGater) and the protocol level.
For the full architecture: docs/ARCHITECTURE.md
| Command | Description |
|---|---|
peerup daemon |
Start the daemon (P2P host + Unix socket control API) |
peerup daemon status [--json] |
Query running daemon status |
peerup daemon stop |
Graceful shutdown |
peerup daemon ping <target> [-c N] [--json] |
Ping a peer via daemon |
peerup daemon services [--json] |
List exposed services via daemon |
peerup daemon peers [--all] [--json] |
List connected peers (peerup-only by default) |
peerup daemon connect --peer <p> --service <s> --listen <addr> |
Create a TCP proxy via daemon |
peerup daemon disconnect <id> |
Tear down a proxy |
| Command | Description |
|---|---|
peerup ping <target> [-c N] [--interval 1s] [--json] |
P2P ping with stats |
peerup traceroute <target> [--json] |
P2P traceroute through relay hops |
peerup resolve <name> [--json] |
Resolve a name to peer ID and addresses |
peerup proxy <target> <service> <local-port> |
Forward a local TCP port to a remote service |
| Command | Description |
|---|---|
peerup whoami |
Show your peer ID |
peerup auth add <peer-id> [--comment "..."] |
Authorize a peer |
peerup auth list |
List authorized peers |
peerup auth remove <peer-id> |
Revoke a peer |
peerup auth validate |
Validate authorized_keys format |
| Command | Description |
|---|---|
peerup init |
Interactive setup wizard (config, keys, authorized_keys) |
peerup config validate |
Validate config file |
peerup config show |
Show resolved configuration |
peerup config rollback |
Restore last-known-good config |
peerup config apply <file> [--confirm-timeout 5m] |
Apply config with auto-revert safety net |
peerup config confirm |
Confirm applied config (cancels auto-revert) |
peerup relay add/list/remove |
Manage relay server addresses |
peerup service add/remove/enable/disable/list |
Manage exposed services |
| Command | Description |
|---|---|
peerup invite [--name "home"] [--non-interactive] |
Generate invite code + QR, wait for join |
peerup join <code> [--name "laptop"] [--non-interactive] |
Accept invite or relay pairing code, auto-configure |
peerup relay pair [--count N] [--ttl 1h] |
Generate relay pairing codes (relay admin only) |
peerup verify <peer> |
Verify peer identity via SAS fingerprint (4-emoji + numeric) |
peerup status |
Show local config, identity, authorized peers (verified/unverified), services, names |
peerup version |
Show version, commit, build date, Go version |
The <target> in network commands accepts either a peer ID or a name from the names: section of your config. All commands support --config <path>.
The daemon runs peerup daemon as a long-lived background process. It starts the full P2P host, exposes configured services, and opens a Unix socket API for management.
Key features:
- Unix socket at
~/.config/peerup/peerup.sock(no TCP exposure) - Cookie-based auth (
~/.config/peerup/.daemon-cookie) - 32-byte random token, rotated per restart - Hot-reload of authorized_keys via
daemonauth endpoints - 15 REST endpoints for status, peers, services, auth, proxies, ping, traceroute, resolve, paths
Example:
# Start the daemon
peerup daemon
# In another terminal - query status
peerup daemon status
# Create a proxy through the daemon
peerup daemon connect --peer home --service ssh --listen localhost:2222For the full API reference: docs/DAEMON-API.md
--config <path>flag (explicit)./peerup.yaml(current directory)~/.config/peerup/config.yaml(standard location, created bypeerup init)/etc/peerup/config.yaml(system-wide)
identity:
key_file: "identity.key"
network:
listen_addresses:
- "/ip4/0.0.0.0/tcp/0"
- "/ip4/0.0.0.0/udp/0/quic-v1"
force_private_reachability: false # true for servers behind CGNAT
relay:
addresses:
- "/ip4/YOUR_VPS_IP/tcp/7777/p2p/YOUR_RELAY_PEER_ID"
security:
authorized_keys_file: "authorized_keys"
enable_connection_gating: true
# services: # Uncomment to expose services (server only)
# ssh:
# enabled: true
# local_address: "localhost:22"
names: {} # Map friendly names to peer IDs
# home: "12D3KooW..."Full sample configs: configs/
A service file is provided at deploy/peerup-daemon.service:
sudo cp deploy/peerup-daemon.service /etc/systemd/system/peerup.service
# Edit ExecStart path and --config as needed
sudo systemctl daemon-reload
sudo systemctl enable --now peerupBoth peerup daemon and peerup relay serve send sd_notify signals (READY=1, WATCHDOG=1, STOPPING=1).
A plist is provided at deploy/com.peerup.daemon.plist.
See relay-server/README.md for the full VPS deployment guide (user creation, SSH hardening, firewall, systemd, health checks).
A Makefile is provided for common operations:
make build # Build with version embedding and optimizations
make test # Run all tests with race detection
make clean # Remove build artifacts
make install # Build, install to /usr/local/bin, and set up system service
make install-service # Install and enable systemd (Linux) or launchd (macOS) service
make restart-service # Restart the service after a rebuild
make uninstall # Remove service and binary
make website # Start Hugo development server for peerup.dev
make help # Show all available targetsLocal checks: make check runs commands from a .checks file (gitignored, one command per line). make push runs checks before pushing. Create your own .checks with any validation commands you need:
# Example .checks file
echo "Running lint..."
go vet ./...You can also build directly with Go:
# Build peerup
go build -o peerup ./cmd/peerup
# Build with version info
go build -ldflags "-X main.version=0.1.0 \
-X main.commit=$(git rev-parse --short HEAD) \
-X main.buildDate=$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
-o peerup ./cmd/peerup
# Cross-compile for Linux
GOOS=linux GOARCH=amd64 go build -o peerup ./cmd/peerup
# Run tests
go test -race -count=1 ./...The pkg/p2pnet package is an importable Go library for building P2P applications:
import "github.com/satindergrewal/peer-up/pkg/p2pnet"
// Create a P2P network
net, _ := p2pnet.New(&p2pnet.Config{
KeyFile: "myapp.key",
EnableRelay: true,
RelayAddrs: []string{"/ip4/.../tcp/7777/p2p/..."},
})
// Expose a local service
net.ExposeService("api", "localhost:8080", nil)
// Connect to a peer's service
conn, _ := net.ConnectToService(peerID, "api")
// Name resolution
net.LoadNames(map[string]string{"home": "12D3KooW..."})
peerID, _ := net.ResolveName("home")cmd/
├── peerup/ # Single binary with subcommands
│ ├── main.go # Command dispatch (16 subcommands)
│ ├── cmd_daemon.go # Daemon mode (start, stop, status, ping, peers, ...)
│ ├── cmd_proxy.go # TCP proxy client
│ ├── cmd_ping.go # Standalone P2P ping
│ ├── cmd_traceroute.go # P2P traceroute
│ ├── cmd_resolve.go # Name resolution
│ ├── cmd_init.go # Interactive setup wizard
│ ├── cmd_invite.go # Generate invite code + QR + P2P handshake
│ ├── cmd_join.go # Accept invite, auto-configure
│ ├── cmd_auth.go # Auth add/list/remove/validate
│ ├── cmd_relay.go # Relay add/list/remove (client config)
│ ├── cmd_relay_serve.go # Relay server: serve/authorize/info/config
│ ├── cmd_config.go # Config validate/show/rollback/apply/confirm
│ ├── cmd_service.go # Service add/remove/enable/disable/list
│ ├── cmd_status.go # Local status display
│ ├── cmd_whoami.go # Show peer ID
│ ├── serve_common.go # Shared P2P runtime (used by daemon + standalone tools)
│ ├── config_template.go # Config YAML template
│ ├── flag_helpers.go # CLI flag reordering for natural usage
│ └── relay_input.go # Flexible relay address parsing
pkg/p2pnet/ # Importable P2P networking library
├── network.go # Core: host setup, relay, DHT, name resolution
├── service.go # Service registry
├── proxy.go # Bidirectional TCP↔Stream proxy with half-close
├── naming.go # Local name resolution (name → peer ID)
├── identity.go # Identity helpers
├── ping.go # PingPeer() with streaming results
├── traceroute.go # P2P traceroute
└── errors.go # Sentinel errors
internal/
├── config/ # YAML configuration + self-healing
│ ├── config.go # Config structs
│ ├── loader.go # Auto-discovery, path resolution, validation
│ ├── archive.go # Last-known-good archive/rollback
│ ├── confirm.go # Commit-confirmed pattern
│ └── errors.go
├── auth/ # Connection gating + authorized_keys
│ ├── gater.go # ConnectionGater (blocks unauthorized at network level)
│ ├── authorized_keys.go # File parser
│ ├── manage.go # AddPeer/RemovePeer/ListPeers
│ └── errors.go
├── daemon/ # Daemon API server + client library
│ ├── server.go # Unix socket HTTP server with cookie auth
│ ├── handlers.go # 15 REST endpoint handlers
│ ├── client.go # Go client (auto-reads cookie, Unix transport)
│ ├── types.go # Request/response types
│ └── errors.go
├── identity/ # Ed25519 identity management
├── invite/ # Invite code encoding (binary → base32 + dash groups)
├── validate/ # Input validation (service names, DNS-label format)
├── watchdog/ # Health monitoring + systemd sd_notify (pure Go)
├── qr/ # QR code generation (zero dependencies)
└── termcolor/ # Terminal color output
relay-server/ # Deployment artifacts
├── setup.sh # Full VPS setup (build, permissions, systemd, health)
├── relay-server.service # systemd unit file
└── README.md # VPS deployment guide
deploy/ # Client service files
├── peerup-daemon.service # systemd unit for peerup daemon
└── com.peerup.daemon.plist # launchd plist for macOS
configs/ # Sample configuration files
├── peerup.sample.yaml
├── relay-server.sample.yaml
└── authorized_keys.sample
docs/ # Documentation
├── ARCHITECTURE.md # Full architecture deep dive
├── DAEMON-API.md # Daemon REST API reference
├── FAQ.md # Frequently asked questions
├── NETWORK-TOOLS.md # Ping, traceroute, resolve guide
├── ROADMAP.md # Multi-phase implementation plan
├── TESTING.md # Test strategy and coverage
└── ENGINEERING-JOURNAL.md # Architecture decision records (ADRs)
Two layers of defense:
- ConnectionGater (network level) - Blocks unauthorized peers during the connection handshake, before any data is exchanged
- Protocol handler (application level) - Secondary authorization check before processing requests
Fail-safe defaults:
- Connection gating enabled + no authorized_keys file → refuses to start
- Empty authorized_keys → warns loudly (allows for initial setup)
- All outbound connections allowed (required for DHT and relay)
- All unauthorized inbound connections blocked
File permissions:
chmod 600 *.key # Private keys: owner read/write only
chmod 600 authorized_keys # Peer allowlist: owner read/write only
chmod 644 *.yaml # Configs: readable
For security details, relay hardening, and threat model: docs/FAQ.md
| Issue | Solution |
|---|---|
no config file found |
Run peerup init or use --config <path> |
Cannot resolve target |
Add name mapping to names: in config |
DENIED inbound connection |
Add peer ID to authorized_keys, restart daemon |
Invalid invite code |
Paste the full code as one argument (quote if spaces) |
Failed to connect to inviter |
Ensure peerup invite is still running |
No /p2p-circuit addresses |
Check force_private_reachability: true and relay address |
protocols not supported |
Relay server not running or unreachable |
| Bad config edit broke startup | peerup config rollback restores last-known-good |
| Remote config change went wrong | peerup config apply new.yaml --confirm-timeout 5m, then config confirm |
failed to sufficiently increase receive buffer size |
QUIC works but suboptimal - see UDP buffer tuning below |
| Daemon won't start (socket exists) | Stale socket from crash - daemon auto-detects and cleans up |
QUIC works with default buffers but performs better with increased limits:
# Linux (persistent)
echo "net.core.rmem_max=7500000" | sudo tee -a /etc/sysctl.d/99-quic.conf
echo "net.core.wmem_max=7500000" | sudo tee -a /etc/sysctl.d/99-quic.conf
sudo sysctl --systemThis is not a weekend hobby project. peer-up is built as critical infrastructure, the kind where failure has real consequences for real people: financial, psychological, and potentially physical.
Think of it like a bubble in outer space. If it breaks, the people inside don't get a second chance. That standard guides everything here - from code quality to deployment to security decisions.
peer-up is experimental software under active development. It is built with significant AI assistance (Claude) and, despite thorough testing, will contain bugs that neither automated tests nor manual testing have caught.
By using this software, you acknowledge:
- This is provided "as is" with no warranty of any kind (see LICENSE)
- The developers are not liable for any damages, losses, or consequences arising from its use
- Network tunnels may disconnect, services may become unreachable, and configurations may behave unexpectedly
- This is not a replacement for enterprise VPN, firewall, or security infrastructure
- You are responsible for evaluating whether peer-up is suitable for your use case
If you discover a bug, please open an issue. Every report makes the project more reliable for everyone.
peer-up is developed with significant AI assistance (Claude). All AI-generated code is reviewed, tested, and committed by a human maintainer. The architecture, vision, and engineering decisions are human-directed.
peer-up is a networking tool. It has no token, no coin, no blockchain dependency, and no plans to add one. If someone tells you otherwise, they're not affiliated with this project.
Issues and PRs are welcome.
Testing checklist:
-
go build ./...succeeds -
go vet ./...passes -
go test -race -count=1 ./...passes - Unauthorized peer is denied, authorized peer connects
- Service proxy works end-to-end
| Document | Description |
|---|---|
| ARCHITECTURE.md | Full architecture: relay circuit, DHT, proxy, auth system |
| DAEMON-API.md | Daemon REST API reference (15 endpoints) |
| FAQ.md | Security FAQ, relay hardening, troubleshooting |
| NETWORK-TOOLS.md | Ping, traceroute, resolve usage guide |
| ROADMAP.md | Multi-phase implementation plan |
| TESTING.md | Test strategy, coverage, integration tests |
| ENGINEERING-JOURNAL.md | Architecture decision records: why every design choice was made |
- go-libp2p v0.47.0
- go-libp2p-kad-dht v0.28.1
- go-multiaddr
- gopkg.in/yaml.v3 v3.0.1
MIT