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 →
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 →
Setup Walkthrough
Commands for:
Your LAN configuration
⚠ The host IP and client range must be outside your DHCP server's scope to prevent address conflicts. Fill these in before generating your token.
Step 1 — Get your enrollment token
Enter your credentials below. We'll register your account (or log you in), create the network, and generate a token automatically.
or paste an existing token
wg0 — walkthrough
Add this device
We'll generate a WireGuard config for this device and turn it into a QR code. Open the WireGuard app on your phone, tap + → Create from QR code, and point the camera at the image.
Scan from WireGuard app. The QR contains the full
config — your private key is generated on the server and shown only
once. Save the .conf
file now if you want a backup.
⌨
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.
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.
# 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.
# 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).
# 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
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.
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