hit counter
🌐
Log in Start free
wg0 — private WireGuard networking

Private networking for
homes, offices, and field teams

wg0 is a managed WireGuard control plane. Reach your home or office LAN from anywhere, optionally route non-LAN traffic through your own VPN provider, connect satellite sites together, and automate it all from the dashboard, API, or your AI agent — all without touching any settings on your network's firewall.

✓ No credit card ✓ Free plan forever ✓ Online in ~60 seconds ✓ Your traffic never touches our servers
Or contact sales for enterprise plans with custom limits, delegated admin, and priority support.
Detecting your OS…
How it works
Step 1

Create a network

Register an account, create a named network. Choose overlay or native LAN mode.

Step 2

Generate a token

Single-use or reusable. Token is scoped to one network. Share it with devices.

Step 3

Run the connector

One command. The connector generates keys, enrolls, and brings up WireGuard.

That's it

Devices connect

Direct peer-to-peer WireGuard tunnels. The brain never sees your traffic.

What wg0 does
⊞

Native LAN access

Reach printers, cameras, NAS, and line-of-business systems on your office LAN from anywhere — no agent on the target device. Remote laptops get real IPs on your existing 192.168.x.x subnet.

⇄

Bring your own exit

Upload your Mullvad, Proton, Azire, or custom WireGuard config to a host, and every route-all client on that host egresses through it. Dashboard shows upstream health in real time.

⬢

Multi-site meshes

Connect two, six, or twenty locations with their own LANs. Each site runs a lightweight host on any Linux or macOS machine. No MPLS, no SASE contract, no shipping VPN appliances.

📱

Mobile in 30 seconds

Use the managed Android app for dashboard-driven VPN mode, live peer status, and optional telemetry, or scan a QR code with the stock WireGuard app for a manual iPhone or fallback Android peer. Your phone joins the mesh instantly and shows up in the dashboard.

∿

Zero firewall changes

No port forwarding, no static IPs, no router configuration. Our discover sidecar handles NAT traversal automatically — even behind CGNAT, double-NAT, and cellular carriers. Install the connector and it just works.

◌

AI-agent ready

Public OpenAPI spec, a rendered API reference, llms.txt, and a Model Context Protocol server. Operate your networks conversationally from Claude Desktop, Claude Code, or any MCP-aware agent. See how →

Who it's for
Six plans, no surprises
Free
$0/forever
  • 10 devices · 3 networks
  • 1 native-LAN · 1 host
  • 1 API token · 7d history
  • Community support
Start free
Basic
$10/mo
  • 20 devices · 10 networks
  • 3 native-LAN · 3 hosts
  • BYO Exit included
  • 3 API tokens · 30d history
Start with Basic
Team Starter
$20/mo
  • 30 devices · 25 networks
  • 5 users · 10 hosts
  • BYO Exit included
  • 10 tokens · 90d · priority email
Start a team
Large Teams
$150/mo
  • 200 devices · 100 networks
  • 100 users · ∞ hosts
  • BYO Exit included
  • 50 tokens · 180d · priority email
Start at scale
Enterprise
From $500/mo
  • 2,000 devices · 1,000 networks
  • 1,000 users · ∞ hosts
  • SSO + audit (coming)
  • Dedicated support + on-premise
Contact sales
Want the full feature comparison and FAQ? See the pricing page → · Need on-premise or custom limits? Talk to sales.

Get started in under a minute.

Create an account, create a network, click + Add Device. Copy-paste one command on each machine you want to connect. No credit card required.

Peer to Peer. Made Simple. Seamless WireGuard Meshing wg0.io Mesh Networks
Choose your scenario
⬡

Private Device Mesh

Connect all your devices to each other — laptop, server, phone, any OS, anywhere in the world.

  • Each device gets a private 10.64.x.x IP
  • Direct peer-to-peer tunnels, no traffic through the brain
  • Brain syncs peer maps every 30 s via heartbeat
View setup →
⌂

LAN Bridge

Access your home or office network from anywhere — reach NAS, printers, cameras by their local IP.

  • One host node on your LAN advertises the subnet
  • Remote clients are routed through the tunnel
  • Overlay addresses — no changes to your LAN
View setup →
◉

Native LAN Placement

Remote devices get a real IP on your home network — physically indistinguishable from being there.

  • Remote laptop gets 192.168.1.x — a real LAN IP
  • No separate overlay subnet to remember
  • LAN devices can reach the remote client directly
View setup →
⌨
CLI & API Reference
curl flows · enrollment by role · wg0 status · troubleshooting
▼

Authentication

Register an account, retrieve a JWT, and store it securely. All subsequent API calls pass the token as a Bearer header.

Register a new account
# POST /api/v1/auth/register curl -s -X POST https://connect.wg0.io/api/v1/auth/register \ -H "Content-Type: application/json" \ -d '{"email":"[email protected]","password":"str0ng-pass!"}' | jq . # Response { "access_token": "eyJhbGci...", "token_type": "bearer" }
Login and store token securely
# POST /api/v1/auth/login mkdir -p ~/.wg0 && chmod 700 ~/.wg0 curl -s -X POST https://connect.wg0.io/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{"email":"[email protected]","password":"str0ng-pass!"}' \ | jq -r '.access_token' > ~/.wg0/token chmod 600 ~/.wg0/token # Load in future shell sessions export TOKEN=$(cat ~/.wg0/token)
Token lifetime: JWTs are valid for 30 days. Re-run the login command to refresh. The token is a standard signed JWT — keep it as secure as a password.

Networks

Create and manage overlay or native networks. Each network gets its own enrollment tokens and peer map.

Create an overlay network (mesh / LAN bridge)
# POST /api/v1/networks — overlay type (default) curl -s -X POST https://connect.wg0.io/api/v1/networks \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{"name":"home-lab"}' | jq . # Response { "id": "a1b2c3d4-...", "name": "home-lab", "overlay_subnet": "10.64.0.0/24", "created_at": "2026-04-09T12:00:00Z" }
Create a native LAN placement network
# Native mode — remote clients get real LAN IPs curl -s -X POST https://connect.wg0.io/api/v1/networks \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{ "name": "home-native", "network_type": "native", "native_gateway_subnet": "192.168.1.0/24", "native_host_tunnel_ip": "192.168.1.2", "native_client_start": "192.168.1.200", "native_client_end": "192.168.1.220" }' | jq .
List your networks
curl -s https://connect.wg0.io/api/v1/networks \ -H "Authorization: Bearer $TOKEN" | jq .
Generate an enrollment token
# Reusable token scoped to one network curl -s -X POST https://connect.wg0.io/api/v1/enroll/generate-token \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{"network_id":"a1b2c3d4-...","is_reusable":true}' | jq -r '.token_value' # Single-use token (safer for shared scripts) curl -s -X POST https://connect.wg0.io/api/v1/enroll/generate-token \ -H "Content-Type: application/json" \ -H "Authorization: Bearer $TOKEN" \ -d '{"network_id":"a1b2c3d4-...","is_reusable":false}' | jq -r '.token_value'

Enrollment by Role

The connector handles all WireGuard key generation, configuration, and service installation. Role and subnet are set at enrollment time.

Client — mesh or LAN bridge (any OS)
# Linux / macOS — basic client enroll (mesh or bridge) sudo ./connector.sh enroll $TOKEN https://connect.wg0.io # macOS connector variant sudo ./connector-macos.sh enroll $TOKEN https://connect.wg0.io # Windows (native app bundle) Download the Windows app from /downloads.html#windows-native, extract it, run wg0.exe as Administrator, paste the token, and click Enroll. [wg0] Generating WireGuard keypair... [wg0] Enrolled! Overlay IP: 10.64.0.2 | Node ID: b2c3d4e5-... [wg0] Interface wg0 is up. [wg0] Heartbeat installed (every 30s). [wg0] wg0 connector setup complete.
Host — LAN bridge (advertises a subnet)
# Linux — host enrolls and advertises 192.168.1.0/24 to all peers sudo ./connector.sh enroll $TOKEN https://connect.wg0.io home-gw host 192.168.1.0/24 # macOS sudo ./connector-macos.sh enroll $TOKEN https://connect.wg0.io home-gw host 192.168.1.0/24 [wg0] Enrolled! Overlay IP: 10.64.0.1 | Role: host [wg0] Advertising: 192.168.1.0/24 to all peers [wg0] Interface wg0 is up. Heartbeat installed.
Host — native LAN placement
# Native host: machine must be physically on the LAN # Assigns host tunnel IP from native_host_tunnel_ip in network config sudo ./connector.sh enroll $TOKEN https://connect.wg0.io home-host host # macOS (handles pfctl NAT and utun mapping automatically) sudo ./connector-macos.sh enroll $TOKEN https://connect.wg0.io home-host host [wg0] Enrolled! Assigned IP: 192.168.1.2 | Network type: native [wg0] pfctl NAT rule active — clients masquerade as host on LAN [wg0] Interface wg0 is up. Heartbeat installed.
Native mode note: The host node must be the machine physically connected to the LAN you want to expose. Clients will masquerade behind the host's LAN IP — your router sees their traffic as coming from the host.

Mobile / QR provisioning

One-scan setup for phones, tablets, and any device that runs a stock WireGuard app. The brain generates a keypair server-side and returns a complete wg0.conf — the dashboard turns it into a QR code.

Recommended: the dashboard flow
# On the dashboard at https://login.wg0.io: # 1. Networks → click your network to expand it # 2. Click "+ Provision Device" # 3. Pick iOS or Android, give it a name, click "Generate Config" # 4. On the phone, open WireGuard → + → "Create from QR code" # 5. Scan. The phone is online.
API — provision a device (server-side keygen)
# POST /api/v1/networks/:id/provision — JWT required # Generates a keypair on the brain, enrolls the node, returns the # complete wg0.conf with PrivateKey already filled in. curl -s -X POST https://connect.wg0.io/api/v1/networks/NETWORK_ID/provision \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "node_name": "my-phone", "os_type": "ios", "role": "client" }' | jq . # Response shape { "node_id": "50a6d46d-...", "overlay_ip": "10.77.77.200", "network_type": "native", "public_key": "SaPo2zlvnfQLjj...", "wg_config": "[Interface]\nPrivateKey = ...\nAddress = 10.77.77.200/32\n...", "peers": [...] }
Save the config + generate a QR on the CLI
# Extract the wg_config and save it as a .conf file curl -s -X POST https://connect.wg0.io/api/v1/networks/NETWORK_ID/provision \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"node_name":"my-phone","os_type":"ios"}' \ | jq -r .wg_config > phone.conf # Render a terminal QR (needs `qrencode` from your package manager) qrencode -t ANSIUTF8 < phone.conf # Or make a PNG you can paste into email: qrencode -t PNG -o phone.png < phone.conf
Trust model: the brain momentarily sees the private key at provision time — it is NOT stored. If you need stronger isolation (brain never sees the private key), use POST /api/v1/enroll/register and supply your own public_key field.
Important: stock WireGuard mobile apps do one-time import only. They won't participate in the heartbeat/control loop, so they won't receive live peer-map updates (new peers joining, route-all toggles, same-LAN detection). Use the managed wg0 Android app when you want dashboard-driven behavior on Android. For iPhone or any manual peer, use the QR flow here. For desktop devices, use the native app or connector flow.

Node Management

List, inspect, and remove nodes from the API. To cleanly unenroll a device, run the unenroll command on the device itself first.

List all nodes
curl -s https://connect.wg0.io/api/v1/nodes \ -H "Authorization: Bearer $TOKEN" | jq . # Filter online nodes curl -s https://connect.wg0.io/api/v1/nodes \ -H "Authorization: Bearer $TOKEN" | jq '[.[] | select(.is_online==true)]'
View traffic for a node (last 24 hours)
curl -s "https://connect.wg0.io/api/v1/nodes/NODE_ID/traffic?hours=24" \ -H "Authorization: Bearer $TOKEN" | jq . # Response shape { "node_id": "b2c3d4e5-...", "total_tx_bytes": 12345678, "total_rx_bytes": 98765432, "buckets": [ { "ts": "2026-04-09T11:00:00Z", "tx_bytes": 1024, "rx_bytes": 4096 } ] }
Remove a node (API)
# Removes the node from the brain — peers will stop receiving its config curl -s -X DELETE https://connect.wg0.io/api/v1/nodes/NODE_ID \ -H "Authorization: Bearer $TOKEN"
Update brain URL (after domain change or migration)
# Run on enrolled devices to point them at a new brain URL # Migrates legacy service names, updates heartbeat, installs latest CLI sudo ./connector-macos.sh update https://connect.wg0.io # macOS sudo ./connector.sh update https://connect.wg0.io # Linux
Check local enrollment info (no API call needed)
# These files are on the enrolled device cat /etc/wireguard/wg0/node_id # your node UUID cat /etc/wireguard/wg0/brain_url # brain this node reports to grep Address /etc/wireguard/wg0.conf # your overlay/tunnel IP cat /etc/wireguard/wg0/public.key # WireGuard public key
Unenroll a device (on the device)
# Run on the device — tears down WireGuard, removes keys, removes heartbeat # Brain URL is auto-detected from stored config; or pass it explicitly sudo ./connector-macos.sh unenroll # macOS (auto-detect) sudo ./connector.sh unenroll # Linux (auto-detect) sudo ./connector.sh unenroll https://connect.wg0.io # explicit brain URL .\connector-windows.ps1 unenroll # Windows (as Administrator)
Before unenrolling: Run wg0 version to see your node ID and brain URL. The enrollment token is not stored locally — you'll need to generate a new one from the dashboard to re-enroll.
Update node settings (PATCH)
# PATCH /api/v1/nodes/:id — JWT + owner-checked # Allowed fields: node_name, route_all_traffic curl -s -X PATCH https://connect.wg0.io/api/v1/nodes/NODE_ID \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"route_all_traffic": true}' | jq . # Guardrails: route_all_traffic=true is rejected (422) for: # - non-client nodes (only clients can route through a host) # - non-native networks (no designated exit host) # Rename a node: curl -s -X PATCH https://connect.wg0.io/api/v1/nodes/NODE_ID \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"node_name": "scott-laptop"}' | jq .
Derived status fields on GET /nodes
# Each node in the response carries a derived on_host_lan boolean: # true = this client is currently on the same public IP as its native # host, so the tunnel is bypassed (direct LAN routing) # The dashboard renders these with a teal "on LAN" badge. curl -s https://connect.wg0.io/api/v1/nodes \ -H "Authorization: Bearer $TOKEN" \ | jq '.[] | {name: .node_name, role, overlay_ip, on_host_lan, route_all_traffic}'

Route-all VPN mode

Turn a native-LAN host into a full exit-node VPN for a specific client. Flip the toggle in the dashboard, the connector picks it up on its next 30-second heartbeat, and all internet traffic from that device starts routing through the host's network.

Enable from the dashboard
# On https://login.wg0.io: # Networks → expand your native-LAN network # → click a client node row to expand it # → Settings → toggle "Route all traffic through host" # A small VPN badge appears next to the node once the connector picks # up the new peer map (usually within 30s).
Enable via API
curl -s -X PATCH https://connect.wg0.io/api/v1/nodes/NODE_ID \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"route_all_traffic": true}' | jq . # Disable: curl -s -X PATCH https://connect.wg0.io/api/v1/nodes/NODE_ID \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"route_all_traffic": false}' | jq .
How it works under the hood
# 1. Brain heartbeat response: the host peer gets AllowedIPs=0.0.0.0/0 # (only for this specific client — each client's map is independent). # 2. Connector heartbeat detects 0.0.0.0/0 and enters route-all mode: # a) Extract the host's public endpoint from the peer response. # b) Add an EXCEPTION route for the endpoint via the physical # default gateway — keeps WireGuard UDP flowing off-tunnel. # c) Install two /1 routes via the WG interface (0.0.0.0/1 and # 128.0.0.0/1) — more specific than the existing /0, so they # win for all traffic EXCEPT the endpoint. # d) Persist state in ${KEY_DIR}/route_all_state so the next # heartbeat can cleanly tear down on toggle-off. # 3. Fail-safe: if discover has no observed endpoint for the host, # brain returns endpoint=null and the connector skips installing # the split-tunnel routes — keeps the client online until discover # catches up, avoiding a black hole. # 4. Route-all is also suppressed while on_same_lan is set: no point # forcing traffic through a tunnel that hairpin-NAT would break.
Currently supported on: Linux, macOS, the Windows native client (client mode), Docker, Docker-compat, and the managed Android app. Stock WireGuard peers and iOS manual imports still do one-time config import only, so the dashboard hides route-all controls for those provisioned peers to avoid black-holing the device.
Same-LAN detection: the brain compares the client's last-reported public IP against its native host's. When they match, the host peer's response carries on_same_lan: true, the connector skips installing the tunnel routes, and (on Linux/macOS) it aliases the overlay /32 on the physical default interface so other LAN devices can reach the client directly. The dashboard shows a teal "on LAN" indicator in both the customer and admin views.

Bring Your Own Exit HIDDEN FEATURE — WORKS TODAY

Use your home network like home, but browse out of London, LA, or wherever your commercial VPN subscription terminates. Your home LAN access and your chosen public exit identity, composed together — no new wg0 code required.

The one-sentence pitch
# If your host node is already connected to a commercial WireGuard # VPN (Mullvad, Proton, AzireVPN, etc.), any wg0 client with # route_all_traffic enabled ALREADY inherits that exit. # client → wg0 → host → Mullvad LA → internet # Result: the client can ping its home LAN AND browse from LA at # the same time. Zero additional wg0 code. Works in the current # release.
Step-by-step walkthrough
# 1. On your wg0 host node (the machine at home): # Download your Mullvad (or other provider) WireGuard config, # save it somewhere like ~/mullvad-la.conf # Bring the upstream tunnel up: sudo wg-quick up ~/mullvad-la.conf # Verify the host is now exiting via Mullvad: curl ifconfig.me # → 185.65.x.x (Mullvad LA exit IP) # 2. On the wg0 dashboard at https://login.wg0.io: # Networks → expand your native-LAN network → click a client # row → Settings → toggle "Route all traffic through host" # 3. Wait ~30 seconds for the client's next heartbeat. Then on # that client: curl ifconfig.me # → 185.65.x.x (same Mullvad LA exit IP as the host!) ping 10.0.0.2 # → Still reaches the host's LAN, still works
The core mental model: a provider's WireGuard config is one identity — one keypair, one peer record at the provider. A single upstream provider profile on the host can serve multiple downstream wg0 clients behind that host, subject to provider terms, host bandwidth, and host reliability. The provider sees one peer: your host.
What this gives you
✓ Access home LAN resources (NAS, printers, cameras, router admin) ✓ Internet egress from the provider's exit IP, not your home ISP ✓ One provider subscription covers many wg0 clients behind the host ✓ Turn it on or off per-client with a single dashboard toggle ✓ Works with any provider that supports stock WireGuard configs (Mullvad, Proton, AzireVPN, etc.)
What this is NOT yet
✗ Not managed — you upload and run the provider config on the host yourself. A managed "upload to portal, assign to host" flow is on the roadmap (Bring Your Own Exit Phase 1). ✗ Not multi-exit per host — all route-all clients on a given host share the host's single upstream tunnel. Picking different exits per-client on the same host is a later phase. ✗ Not direct client upstream — the client devices themselves don't run the provider tunnel. Only the host does. If you want a phone to exit via Mullvad while NOT going through your home host, that's a separate setup (use Mullvad directly on the phone).
The sharp edges: double-tunneling stacks WireGuard headers (~120 bytes of overhead) so your effective MTU drops. If you hit weird hangs on HTTPS sites, set your client's wg0 MTU to 1280. DNS queries may still go through your normal resolver — for true provider-level privacy, configure your client to use the provider's DNS. If the host's upstream tunnel drops, route-all clients will lose internet (fail-closed, by design — traffic is not silently falling back to the home ISP).
Shipped today: upload your provider WireGuard config to the dashboard, assign it to a host, and activate with one click. Real-time health badges (healthy / stale / down) show the upstream state updated every ~30 seconds. Read the BYO Exit walkthrough →

wg0 CLI

The wg0 command is installed automatically during enrollment. It lets you check tunnel health without knowing WireGuard internals.

Full status
wg0 status wg0 Status ──────────────────────────────────────────── Node ID : b2c3d4e5-e6f7-4abc-9012-... Tunnel IP : 10.64.0.2/32 Interface : utun5 (wg0) ← macOS shows real utun name WireGuard : running Heartbeat : running (every 30s) Last beat : 18s ago Peers (1) ──────────────────────────────────────────── Qx87foBkl2nPmR3tXy8d… Allowed : 10.64.0.1/32 Endpoint : 203.0.113.42:51820 TX : 1.2 MiB RX : 5.6 MiB Handshake: 22s ago
Machine-readable JSON output
wg0 status --json | jq . { "node_id": "b2c3d4e5-...", "overlay_ip": "10.64.0.2/32", "wireguard": "running", "heartbeat": "running", "last_heartbeat": "18s ago", "peer_count": 1, "peers": [...] }
CommandDescription
wg0 statusFull tunnel + peer + heartbeat summary
wg0 status --jsonSame output as structured JSON
wg0 peersCompact peer list with TX/RX and handshake age
wg0 logsTail the last 40 lines of the heartbeat log
wg0 versionShow node ID and OS
macOS note: WireGuard userspace uses a real utunN interface. The wg0 CLI automatically resolves the real name from /var/run/wireguard/wg0.name — you never need to know the utun number.
First-time enroll (new device)

Start here if the device has never been enrolled in wg0. You need an enrollment token from the dashboard first — log in, pick a network, click + Add Device, then choose Managed device. The dashboard will generate the token and show the exact commands for your platform.

# macOS curl -fsSL https://wg0.io/connector-macos.sh -o connector-macos.sh \ && chmod +x connector-macos.sh sudo ./connector-macos.sh enroll <TOKEN> https://connect.wg0.io # Linux curl -fsSL https://wg0.io/connector.sh -o connector.sh \ && chmod +x connector.sh sudo ./connector.sh enroll <TOKEN> https://connect.wg0.io # Windows (native app bundle) # Download https://wg0.io/downloads/wg0-windows-portable.zip # Extract it, run wg0.exe as Administrator, paste <TOKEN>, then click Enroll.
Connector update subcommand (already enrolled)

Only for devices that are already enrolled. If the device has never successfully run enroll, use the first-time enroll block above instead — update assumes an existing node_id file and will fail with "this system does not appear to be enrolled" on a fresh device.

# Repoint an enrolled device at a new brain URL without re-enrolling. # Migrates legacy paths/services, rewrites the heartbeat script from # the latest template (route logic, split-tunnel state machine, # same-LAN alias, etc.), reloads the launchd/systemd unit, and runs # a test heartbeat. curl -fsSL https://wg0.io/connector-macos.sh -o connector-macos.sh \ && chmod +x connector-macos.sh sudo ./connector-macos.sh update https://connect.wg0.io # Linux equivalent: sudo ./connector.sh update https://connect.wg0.io
Manual install (if not enrolled yet)
# Linux / macOS curl -fsSL https://wg0.io/wg0-status.sh -o /usr/local/bin/wg0 \ && chmod +x /usr/local/bin/wg0 # Windows (preferred) — use the native app bundle from /downloads.html#windows-native # Advanced fallback only: Invoke-WebRequest "https://wg0.io/wg0-status.ps1" -OutFile wg0-status.ps1

Troubleshooting

Quick diagnostics for the most common issues.

WireGuard tunnel is down
# Check status first wg0 status # Linux — restart WireGuard and heartbeat sudo systemctl restart wg-quick@wg0 sudo systemctl restart wg0-heartbeat.timer # macOS — the watchdog auto-restarts within 60s, or force it: sudo launchctl kickstart -k system/io.wg0.wireguard # Windows native app (PowerShell as Administrator) Restart-Service "wg0 Service" Start-Process "$env:USERPROFILE\Downloads\wg0-windows-portable\wg0.exe" -Verb RunAs
Heartbeat not running
# Linux sudo systemctl status wg0-heartbeat.timer sudo systemctl enable --now wg0-heartbeat.timer # macOS sudo launchctl list | grep io.wg0.heartbeat sudo launchctl load /Library/LaunchDaemons/io.wg0.heartbeat.plist # Windows native app Get-Service "wg0 Service" Start-Process "$env:USERPROFILE\Downloads\wg0-windows-portable\wg0.exe" -Verb RunAs
Can't reach the LAN through a bridge host
# 1. Verify IP forwarding is enabled on the host sudo sysctl net.ipv4.ip_forward # Linux — should be 1 sudo sysctl net.inet.ip.forwarding # macOS — should be 1 # 2. Check the peer map includes the host node wg0 peers # run on the client # 3. Verify the host is advertising routes curl -s https://connect.wg0.io/api/v1/nodes \ -H "Authorization: Bearer $TOKEN" \ | jq '[.[] | select(.role=="host") | {name:.node_name, routes:.advertised_routes}]'
Peer not appearing after enrollment
# The heartbeat syncs peers every 30s. Check the last heartbeat time: wg0 logs # Confirm the node is registered in the brain: curl -s https://connect.wg0.io/api/v1/nodes \ -H "Authorization: Bearer $TOKEN" | jq '[.[] | {name:.node_name, online:.is_online}]' # Force an immediate heartbeat (Linux) sudo systemctl start wg0-heartbeat.service
macOS: wg show wg0 fails
# wireguard-go uses a real utunN interface — wg0 is a logical alias # wg0 handles this automatically, but if you need the raw interface: cat /var/run/wireguard/wg0.name # prints e.g. "utun5" sudo wg show utun5 # use the real name directly # If the name file is missing, WireGuard is down — restart it: sudo launchctl kickstart -k system/io.wg0.wireguard
© 2026 wg0 · WireGuard is a registered trademark of Jason A. Donenfeld Built on WireGuard · [email protected] · [email protected] Web Application Services Provided by ABSG.tech