WireWAN is a vibe-coded private WAN via WireGuard creation and management tool fully built, mostly, by LLMs.
  • Python 63.9%
  • TypeScript 34.4%
  • Shell 0.7%
  • Dockerfile 0.5%
  • CSS 0.3%
  • Other 0.2%
Find a file
Codin Moldovanu bb4724fe71 ux(peer-create): explain client-roamer access semantics + HOSTS vs ACCESSES
When a CLIENT-type peer is selected, surface a callout explaining:
- the roamer automatically gets access to every routed LAN in the mesh
  (because WireGuard hands all the other peers' subnets as AllowedIPs in
  the generated client config)
- how to LIMIT that access: switch the WAN to hub-spoke topology (only
  see hubs) or add forward-chain firewall rules on the gateway peers
- crucially: don't put the LANs you want to reach into "Local Networks"
  below — that field means "this peer HOSTS the CIDR", not "this peer
  can REACH the CIDR".  Adding it wrong is exactly the misconfig that
  bricked L18/Sergiu/Nestibus on 2026-05-21.

Also rewrites the Local Networks section copy to make the HOSTS-vs-
ACCESSES distinction the lead, not a footnote.  Adds a per-type warning
shown only when the selected type is CLIENT: "a laptop almost never
hosts a LAN; leave this empty."

Pairs with backend commit b45726a, which blocks peer-subnet overlaps at
save time.  Backend protects data integrity; this frontend change tries
to prevent the user from typing the wrong thing in the first place.
2026-05-21 22:27:40 +03:00
backend fix(peers): block peer-subnet overlap at save time (was WARNING-only) 2026-05-21 22:25:29 +03:00
data deploy: bootstrap state for sagittarius (PRIVATE — c3n only) 2026-05-04 20:36:50 +03:00
frontend ux(peer-create): explain client-roamer access semantics + HOSTS vs ACCESSES 2026-05-21 22:27:40 +03:00
.env deploy: bootstrap state for sagittarius (PRIVATE — c3n only) 2026-05-04 20:36:50 +03:00
.gitignore Fix MikroTik port default, wire up configuration history and revert 2026-04-18 17:07:57 +03:00
DEPLOYMENT.md deploy: docker-compose for sagittarius / tn-npm + DEPLOYMENT.md 2026-05-04 20:36:33 +03:00
docker-compose.yml deploy: docker-compose for sagittarius / tn-npm + DEPLOYMENT.md 2026-05-04 20:36:33 +03:00
LICENSE Initial release: WireWAN - WireGuard WAN Overlay Network Manager 2025-11-22 00:31:43 +02:00
README.md deploy: docker-compose for sagittarius / tn-npm + DEPLOYMENT.md 2026-05-04 20:36:33 +03:00
run.sh Initial release: WireWAN - WireGuard WAN Overlay Network Manager 2025-11-22 00:31:43 +02:00
SPEC.md Initial release: WireWAN - WireGuard WAN Overlay Network Manager 2025-11-22 00:31:43 +02:00

WireWAN

WireGuard WAN Overlay Network Manager

Vibe Coded Built with Claude

Python FastAPI React TypeScript WireGuard License


WireWAN is a web app for creating and managing WireGuard-based overlay networks across multiple locations, with automatic configuration deployment to MikroTik routers via their native API. It handles IP allocation, conflict detection, NAT translation, topology-aware config generation, and config staleness tracking — so you can focus on which sites should be connected rather than which AllowedIPs goes where.


Table of Contents


What it does

Feature Detail
Centralized dashboard One place for all peers, subnets, services, and deployment jobs
Automatic IP allocation Tunnel IPs assigned from a configurable range; no manual spreadsheets
Topology-aware config generation Mesh gives everyone full visibility; hub-spoke restricts spokes to only seeing hubs
CGNAT support Peers with no public endpoint work fine — they initiate connections outbound and use PersistentKeepalive
Conflict detection Warns when subnets overlap; can auto-assign NAT translations to resolve without renumbering
Config staleness tracking When topology changes (peer added/removed, subnet changed), non-MikroTik peers are flagged as needing a config refresh
MikroTik auto-deploy Push configs directly to RouterOS 7+ via the native binary API (port 8728/8729)
MikroTik auto-redeploy MikroTik peers with auto-deploy enabled are automatically redeployed when the topology changes
Non-destructive updates Every resource WireWAN creates on a router is tagged with a comment prefix; unmanaged resources are never touched
Session-preserving peer updates Peer entries are diffed and updated in-place — WireGuard sessions survive redeployments
Route conflict demotion Pre-existing static routes that shadow WireWAN routes are automatically demoted to distance=11
Deployment history + revert Every successful deploy is stored; revert re-applies the previous config
Published services Expose a local IP:port across the overlay on a shared services subnet

Quick Start

Prerequisites

  • Python 3.10+ (managed via UV)
  • Node.js 18+

Run locally

git clone https://github.com/yourusername/wirewan.git
cd wirewan

./run.sh setup      # install Python and Node deps

# Terminal 1
./run.sh backend

# Terminal 2
./run.sh frontend

Open http://localhost:3000. Register an account (the first user becomes admin).

Live deployment

A reference deployment runs at wirewan.tn.c3n.ro on sagittarius (192.168.95.140). The full bootstrap, NPM proxy config, systemd unit, and backup setup is in DEPLOYMENT.md. State (DB + secrets) ships on the deploy branch on c3n only — that branch is never pushed to origin (GitHub) because it carries plaintext encryption keys and the SQLite DB with encrypted-but-still-sensitive credentials.

Docker

# Generate required secrets
python -c "import secrets; print('ENCRYPTION_KEY=' + secrets.token_urlsafe(32))"
python -c "import secrets; print('ENCRYPTION_SALT=' + secrets.token_urlsafe(16))"
python -c "import secrets; print('SECRET_KEY=' + secrets.token_urlsafe(32))"

# Add them to .env, then:
docker-compose up -d

Required environment variables

Variable Purpose Generate with
ENCRYPTION_KEY Encrypts private keys and MikroTik passwords at rest python -c "import secrets; print(secrets.token_urlsafe(32))"
ENCRYPTION_SALT Salt for key derivation python -c "import secrets; print(secrets.token_urlsafe(16))"
SECRET_KEY Signs JWT tokens python -c "import secrets; print(secrets.token_urlsafe(32))"

Add these to backend/.env. Never change them after storing data — encrypted values become unrecoverable.


Scenarios

Scenario: Friend group / homelab mesh

You want to connect your home server, a friend's VPS, and another friend's desktop into a shared private network where you can all reach each other's stuff.

Setup:

  1. Create a WAN network — WAN Networks → Create WAN. Accept the defaults (10.0.0.0/24 tunnel range, 10.0.5.0/24 shared services). Topology: Mesh.

  2. Add your home server (public endpoint, e.g., a VPS or port-forwarded router):

    • Type: Server
    • Endpoint: yourserver.duckdns.org or 203.0.113.10:51820
    • Local subnets: add your home LAN, e.g., 192.168.1.0/24 (mark Routed if you want friends to reach your LAN)
    • Get Config → download .conf → apply with sudo wg-quick up ./yourserver.conf
  3. Add friend A (has a VPS with a public IP):

    • Type: Server
    • Endpoint: friend-a-vps.example.com:51820
    • Friend A downloads their .conf and applies it the same way
  4. Add friend B (laptop behind CGNAT, no port forward — see next scenario):

    • Type: Client
    • Endpoint: leave empty
    • Friend B downloads their .conf and applies it
  5. Every time a new peer joins, the WAN list shows a yellow "Config stale" badge on existing non-MikroTik peers. Those peers need to re-download and re-apply their .conf to learn about the new member.

Result: Everyone's tunnel IPs are in 10.0.0.x. If routed subnets are configured, peers can also reach each other's LANs directly.


Scenario: CGNAT clients (no port forward)

Many ISPs, mobile carriers, and shared hosting providers use CGNAT — your device gets a private WAN IP and has no way to receive inbound connections. This is fine with WireGuard and WireWAN handles it explicitly.

How it works:

  • Leave the Endpoint field empty when adding the peer. WireWAN marks it as CGNAT mode.
  • The generated config omits the Endpoint = line for this peer in other peers' configs. Other peers can't initiate connections to it, but when the CGNAT peer comes online it connects outbound to peers that have public endpoints.
  • PersistentKeepalive = 25 is always set in the config so the tunnel stays alive through NAT even when the CGNAT peer isn't sending traffic.

Requirements:

  • At least one peer in the network must have a publicly reachable endpoint. CGNAT clients connect to that peer first; from there, WireGuard routes traffic to other peers.
  • The CGNAT peer needs a working internet connection and UDP outbound (almost always allowed).

In practice:

If you have a VPS or a router with a public IP, CGNAT clients work exactly like full peers: they can reach all other peers' tunnel IPs and routed subnets, and other peers can reach them back through the established tunnel. The only limitation is that other peers can't wake up a CGNAT peer that's completely offline — the CGNAT peer must initiate the connection.

Config staleness: CGNAT peers get a "Config stale" badge in the peer list when other peers join/leave. They need to re-download and re-apply config just like any other non-MikroTik peer.


Scenario: Hub-and-spoke

You have a central site (office, datacenter, VPS) that all other sites connect through. You don't want a full mesh — spokes should not have direct tunnels to each other.

Setup:

  1. Create a WAN network with Topology: Hub-and-Spoke.

  2. Add your central site as a peer with type Hub:

    • Must have a public endpoint (all spokes connect to it)
    • Add the central LAN as a routed subnet if needed
  3. Add spoke sites with any type (MikroTik, Server, Client, etc.):

    • Each spoke's generated config only contains a [Peer] section for the hub, not for other spokes
    • Traffic between spokes is routed through the hub

What this means for routing:

  • Spoke A can reach spoke B's LAN, but traffic goes: Spoke A → Hub → Spoke B
  • The hub peer's config contains [Peer] sections for every other peer (it sees everyone)
  • Spoke configs only contain the hub as a peer

When to use hub-spoke vs mesh:

  • Hub-spoke: >10 peers, or you want centralized traffic inspection/logging, or most peers are CGNAT and need to roam through a stable relay
  • Mesh: small networks (<10 peers), lower latency between sites, no single point of failure

Scenario: Overlapping home LANs

A common problem: you and two friends all have 192.168.1.0/24 at home. If you try to route all three into the overlay, packets don't know which 192.168.1.x to go to.

Options:

Option 1: Don't route the LAN (easiest)

When adding a subnet, uncheck "Routed" (or check "Service-only"). The subnet is recorded but not advertised to other peers. Peers can only reach each other's tunnel IPs (10.0.0.x). This is fine if you just want peer-to-peer connectivity without LAN access.

Option 2: Auto-NAT (recommended for shared-LAN overlap)

When adding a subnet, check "Auto-NAT if conflict". WireWAN detects the overlap and automatically assigns a non-conflicting translated range (e.g., your 192.168.1.0/24 appears to other peers as 172.16.0.0/24). You don't change anything on your local network — the translation only exists in the overlay config.

  • Your devices keep their 192.168.1.x addresses
  • Other peers route to your LAN via 172.16.0.0/24 (the NAT translation)
  • Traffic is NAT'd transparently as it crosses the overlay

Option 3: Manual NAT

On the subnet detail, click "Auto-NAT" on an existing subnet to assign a translation after the fact.

Option 4: Renumber

If you control the network, change one LAN to a less common range (10.20.x.x, 172.16.x.x, etc.) before adding it. This avoids NAT entirely.


How the overlay works

WireWAN creates a separate overlay network on top of your existing networks. It doesn't change default routes or existing LAN routing — it only adds routes for the overlay ranges.

Three IP address spaces:

Range Purpose Default
Tunnel range WireGuard endpoint IPs — one per peer 10.0.0.0/24
Shared services range IPs for services published across the overlay 10.0.5.0/24
Local subnets Each peer's own LAN(s); routed selectively Varies

What gets added to each peer's device:

  • A WireGuard interface (e.g., wg-wan-overlay) with the peer's tunnel IP
  • Routes for other peers' tunnel IPs and any routed subnets, at distance=10
  • A route for the shared services range
  • No changes to the default gateway or existing LAN routes

Isolation: The overlay is completely separate from your existing internet routing. Adding WireWAN doesn't change how your devices reach the internet or each other on the local LAN.


MikroTik integration

WireWAN connects to MikroTik routers via the RouterOS native binary API (librouteros, port 8728 plaintext or 8729 TLS).

Initial setup for a new MikroTik peer

Adding a MikroTik peer is a two-phase process:

Phase 1 — manual one-time bootstrap (you run commands on the router once)

The WireGuard tunnel must exist before WireWAN can reach the router at its tunnel IP. Download the RouterOS script from WireWAN (Get Config → MikroTik Script) and paste it into the router terminal (Winbox, SSH, or WebFig). The script is idempotent and handles everything:

# Download the script from WireWAN, paste into router terminal:
# - Step 0: creates/updates the wirewan API user with the correct password
#            and sets the allowed-address to the WAN's tunnel IP range so
#            WireWAN can reach the API from any peer IP in the overlay
# - Step 1: creates the wg-wan-overlay WireGuard interface
# - Steps 2-3: adds WireGuard peer entries
# - Step 4: assigns the tunnel IP address
# - Steps 5-6: adds routes at distance=10; demotes any pre-existing conflicting routes
# - Steps 7-8: adds firewall rules to allow overlay traffic
# - Steps 9-10: adds NAT rules (no-masquerade + any published services)

Phase 2 — API deployment (WireWAN handles all future changes)

Once the tunnel is up, WireWAN can reach the router at its tunnel IP. From here, click Test Connection to confirm, then Deploy to push the initial config via API. All future topology changes can be deployed the same way — or automatically with auto-deploy enabled.

What to fill in when adding the peer in WireWAN:

Field What to put
Type MikroTik
Endpoint The router's public hostname/IP and WireGuard port, e.g., myrouter.dyndns.org:51820
Management IP The peer's tunnel IP (e.g., 10.0.0.3) — see below
API Port 8728 (default) or 8729 for TLS
Username wirewan (default) or whatever username you choose
Password A strong password — WireWAN stores it encrypted and embeds it in the RSC script
Local subnets The router's LAN CIDRs you want routed across the overlay

Management IP — use the LAN gateway IP, never the tunnel IP

This is the single most common setup mistake. The Management IP is the address WireWAN uses to reach the router's API. It needs to point at something that survives the tunnel going down — otherwise WireWAN locks itself out the first time a deploy disrupts the tunnel.

  • Use the peer's LAN gateway IP (e.g., 192.168.1.1) — once the overlay routes the LAN subnet via the tunnel, WireWAN reaches the API by routing through the tunnel to the LAN-side address. The address itself doesn't move when the tunnel hiccups.
  • Or use the public IP / DDNS hostname — if the router exposes its API publicly (be careful with /ip service api address= restrictions).
  • Never use the tunnel IP (e.g., 172.42.0.3) — the tunnel is exactly what WireWAN uses to manage the peer, so any deploy that wobbles the tunnel (peer-key rotation, listen-port change, firewall rebuild) takes the management connection down with it. The only way back in is on-site or via another tunnel.

WireWAN now refuses to save a peer whose mikrotik_management_ip equals the peer's tunnel_ip or sits inside the WAN's tunnel_ip_range. If you need to bootstrap a router that isn't yet on the overlay, deploy the initial RSC manually first, then add it to WireWAN with the LAN IP.

What WireWAN does on the router

Every resource WireWAN creates on a router is tagged with a comment WAN-Overlay-Manager:<id>. This means:

  • Unmanaged resources are never touched — existing WireGuard interfaces, routes, firewall rules, and users outside of the WAN-Overlay-Manager: namespace are left alone
  • Updates are non-destructive — WireGuard peer entries are diffed by comment and updated in-place rather than deleted and recreated, so established sessions survive redeployments
  • Cleanup is precise — when a peer is removed from WireWAN, only its tagged resources are deleted from each router

Steps performed on deployment:

  1. Create/update the wg-wan-overlay WireGuard interface (skipped if listen-port and private-key are unchanged)
  2. Reconcile WireGuard peer entries: update changed peers in-place, add new ones, remove deleted ones — sessions for unchanged peers are preserved
  3. Configure the tunnel IP address on the interface
  4. Add routes (distance=10) for all remote subnets and the tunnel range
  5. Demote any pre-existing conflicting static routes (see below)
  6. Add firewall filter rules: accept input and forward on the overlay interface
  7. Add NAT rules: no-masquerade for overlay traffic, plus dstnat rules for any published services
  8. Ensure the API user's allowed-address includes the WAN's tunnel range

Route conflict demotion

RouterOS static routes default to distance=1. WireWAN adds routes at distance=10. If a pre-existing static route (e.g., from a separate VPN) targets the same destination with distance=1, it shadows WireWAN's route entirely — traffic never reaches the overlay.

WireWAN automatically detects and fixes this both via API and in the RSC script:

  • Any non-WireWAN static route that conflicts with a WireWAN-managed destination and has distance < 10 is automatically set to distance=11
  • This makes WireWAN's dist=10 route win, while keeping the old route as a fallback (dist=11)
  • Only static, non-dynamic routes are affected; WireWAN never touches dynamic routes

This happens on every deployment, so adding a new subnet on one peer automatically demotes any conflicting old routes on all MikroTik peers with auto-deploy enabled.

Session-preserving updates

A common issue with naive WireGuard config management: deleting and re-adding a peer entry destroys the established WireGuard session, forcing a new handshake. In a mesh with N peers this means every redeployment causes N simultaneous renegotiations.

WireWAN avoids this by diffing peer entries by their comment tag before applying changes:

  • Unchanged peers (same public-key, allowed-address, endpoint, keepalive) → skipped entirely
  • Changed peers → updated in-place via /interface wireguard peers set — session survives
  • New peers → added
  • Removed peers → deleted

Auto-deploy

When Auto-deploy configuration changes is enabled on a MikroTik peer, WireWAN automatically runs a full deployment to that router whenever the network topology changes (peer added/removed, subnet added/removed/changed, or another peer's endpoint/listen-port/public-key changes). This keeps the router in sync without manual intervention.

Auto-deploy fires on:

  • A peer is added or removed from the WAN
  • A peer's local subnets change (added, removed, or NAT settings updated)
  • An existing peer's endpoint, listen_port, tunnel_ip, or public_key is edited

Without auto-deploy, you have to redeploy each peer manually after any of those changes. Existing WireGuard sessions survive thanks to cryptokey-routing's current-endpoint auto-update on received packets — but a peer reboot before its redeploy reverts to the stored (now-stale) endpoint and the session won't re-establish.

Deploy guardrails

WireWAN refuses to save peer configurations that are known to silently break tunnels. These have all bitten production setups; the validators exist so they don't bite again:

  1. endpoint port must equal listen_port unless peer_metadata.has_port_forward = true. When the two diverge, other peers send handshakes to the endpoint port while the MikroTik listens on a different port — the encrypted packet either lands on the wrong WireGuard interface (if another WG happens to be on the endpoint port) or gets dropped at the firewall before it ever reaches the right one. Either way: silent failure with last-handshake = never and no log entry. The legitimate exception is a NAT box upstream that translates external port → internal port. Tick "Has port-forward upstream" on the peer detail page when that's your setup.

  2. mikrotik_management_ip cannot be in the WAN's tunnel range. See the section above — chicken-and-egg lockout on tunnel disruption.

  3. Every deploy emits an input-chain accept udp dst-port=<listen_port> rule. MikroTiks running the RouterOS default chain=input action=drop in-interface-list=!LAN rule otherwise silently drop every unsolicited WireGuard handshake initiation that doesn't match an existing conntrack reply tuple. As long as a session is active, conntrack lets traffic through; once it's torn down (deploy, reboot, idle timeout), the box that needs to receive the new handshake drops it before WireGuard ever sees the packet — and the session can never re-establish on its own. The rule is parameterized to the peer's actual listen_port, so peers running multiple WG interfaces on different ports stay isolated. The rule is tagged WAN-Overlay-Manager:allow-handshake-{peer.id} so verify-deployment picks it up.

  4. Post-deploy handshake probe. After API calls land, the deployer waits up to 35 seconds for at least one managed peer to show a fresh last-handshake. If none do, the deploy still completes (config landed) but logs a warning — Peer 'X' has no recent handshake after 35s post-deploy. Check the other side's endpoint/port and the in-between firewall — config deployed but the tunnel isn't establishing. This catches setups where the config is correct in isolation but the network path between two peers is blocked (e.g., one peer's home-router firewall is dropping inbound UDP to listen_port).

Peers without auto-deploy show a "Deploy" button in the UI when their config is out of date.


Topology types

Topology Peer visibility Use when
Mesh Every peer sees every other peer; direct tunnels between all Small networks, low latency matters
Hub-spoke Hub peers see everyone; spoke peers only see hub peers Large networks, centralized routing, relay for CGNAT peers
Hybrid Same as hub-spoke; hub peers see all, spokes only see hubs Intermediate between the two

Topology is enforced at config generation: spoke peers' configs only contain [Peer] sections for hub-type peers, so no direct tunnels to other spokes are established.

If a hub-spoke WAN has no peers with type "Hub", config generation falls back to full mesh so the network still works while you're setting it up.


Config staleness

When the network topology changes — a peer joins or leaves, or a subnet is added or removed — all non-MikroTik peers need updated configs.

How WireWAN handles this:

  • Non-MikroTik peers get a needs_config_refresh flag set automatically
  • The peer list shows a yellow "Config stale" badge on affected peers
  • The peer detail page shows a prominent banner with a "Get updated config" button
  • Downloading a fresh config clears the stale flag

For MikroTik peers:

  • Peers with auto-deploy enabled are redeployed automatically in the background
  • Peers without auto-deploy keep a deployment job you can trigger manually

Action required for non-MikroTik peers:

After any topology change, re-download and re-apply the config on each non-MikroTik device:

# Linux / macOS
sudo wg-quick down ./peer.conf
# (download fresh config from WireWAN)
sudo wg-quick up ./peer.conf

Or if using wg-quick as a systemd service:

sudo systemctl restart wg-quick@peer

Conflict detection and NAT

WireWAN checks for subnet conflicts whenever a peer or subnet is added.

Conflict types:

Type Severity Meaning
Tunnel IP range overlap Critical Peer's local subnet overlaps the WAN tunnel range — blocks routing
Shared services overlap Critical Peer's local subnet overlaps the shared services range — blocks routing
Peer subnet overlap Warning Two peers have the same or overlapping LAN range

Critical conflicts block peer creation — you must resolve them first (renumber, or mark the subnet as not routed).

Warning conflicts (peer-to-peer overlaps) don't block creation but they mean two peers would advertise the same routes, causing ambiguity. Options:

  • Auto-NAT: check "Auto-NAT if conflict" when adding the subnet. WireWAN finds a free range in 172.16.0.0/12 and assigns it as a NAT translation. The subnet appears under a different CIDR to other peers.
  • Auto-NAT on existing subnet: click the "Auto-NAT" button next to an existing subnet on the peer detail page.
  • Don't route: mark the subnet as service-only (not routed). It's recorded in WireWAN but not advertised.
  • Renumber: change the actual LAN range to something less common.

NAT translation mechanics: when NAT is enabled on a subnet, WireWAN updates the AllowedIPs in all other peers' configs to use the translated CIDR. The peer that owns the subnet doesn't need any changes — traffic from other peers destined for the translated range is rewritten by the WireGuard router before it reaches the LAN.


Security

WireWAN is designed for a single trusted operator or small trusted team managing their own infrastructure. It is not a multi-tenant SaaS product.

Implemented:

  • Fernet symmetric encryption for stored private keys and MikroTik passwords (PBKDF2 key derivation)
  • bcrypt password hashing
  • JWT authentication
  • Cryptographically secure X25519 WireGuard key generation
  • SQLAlchemy ORM (no raw SQL queries)
  • Audit log for all MikroTik API calls

Known trade-offs (intentional for single-operator use):

Item Notes
No rate limiting Add nginx/Caddy rate limiting if exposed to the internet
7-day JWT expiry Reduce ACCESS_TOKEN_EXPIRE_MINUTES in config if needed
localStorage token Acceptable for single-user; not suitable for shared public terminals
MikroTik cert verification off by default Most homelabs use self-signed certs; enable mikrotik_verify_cert per peer
First-registered user becomes admin Appropriate bootstrap for single-party deployment

Production checklist:

  • Set DEBUG=false in backend/.env
  • Set CORS_ORIGINS to your actual domain
  • Put behind HTTPS reverse proxy (nginx, Caddy, Traefik)
  • Enable mikrotik_verify_cert if your routers have proper TLS certs
  • Back up your encryption keys and database regularly
  • Consider PostgreSQL over SQLite for concurrent access

API

Interactive docs at http://localhost:8000/docs (Swagger UI).

Key endpoints

Method Path Description
POST /api/wan Create a WAN network
GET /api/wan/{id}/topology Topology graph data
GET /api/wan/{id}/conflicts All subnet conflicts in a WAN
POST /api/peers/wan/{wan_id} Add a peer
GET /api/peers/{id}/config?config_type=wireguard Download WireGuard .conf
GET /api/peers/{id}/config?config_type=mikrotik-script Download RouterOS script
POST /api/peers/{id}/subnets?cidr=…&auto_nat=true Add subnet with automatic NAT if conflict
PUT /api/peers/{id}/subnets/{subnet_id}?auto_nat=true Apply auto-NAT to existing subnet
POST /api/peers/{id}/mikrotik/test-connection Test MikroTik API reachability
GET /api/peers/{id}/mikrotik/preflight Pre-deploy conflict check on router
POST /api/peers/{id}/mikrotik/deploy?approve=true Deploy config to MikroTik
POST /api/peers/{id}/mikrotik/revert Revert to previous MikroTik config
POST /api/peers/{id}/mikrotik/clear Remove all WireWAN-managed resources from router
GET /api/peers/{id}/mikrotik/verify Check if router state matches desired config
GET /api/jobs List deployment jobs

Tech stack

Backend: FastAPI · SQLAlchemy 2.0 · Pydantic v2 · librouteros · cryptography · python-jose

Frontend: React 18 · TypeScript · Vite · TanStack Query · D3.js · Tailwind CSS

Infrastructure: Docker · SQLite (PostgreSQL supported) · UV (Python deps)


Troubleshooting

MikroTik connection fails with "Authentication failed" or "invalid user name or password"

This error occurs in two distinct cases — both look identical from WireWAN's perspective:

  1. Wrong password — the password in WireWAN doesn't match what's set on the router. Fix by updating the password on the router to match WireWAN, or update the stored password in WireWAN.

  2. Source IP not in user's allowed-address list — MikroTik returns the same error when the connecting IP is not permitted by the user's address restriction. This happens when WireWAN is connecting from a tunnel IP (e.g., 10.0.0.1) but the wirewan user's address restriction doesn't include the tunnel range.

    Fix by running this on the router (substitute your actual WAN tunnel range):

    /user set [find name=wirewan] address=<tunnel-range>,192.168.0.0/16
    /ip service set [find name=api] disabled=no address=<tunnel-range>,192.168.0.0/16
    

    For example, if your WAN tunnel range is 172.42.0.0/24:

    /user set [find name=wirewan] address=172.42.0.0/24,192.168.0.0/16
    /ip service set [find name=api] disabled=no address=172.42.0.0/24,192.168.0.0/16
    

    WireWAN handles this automatically on every deployment — Step 0 sets the allowed-address to the tunnel range + all peer LAN subnets + 192.168.0.0/16. The RSC script (Step 0) does the same. For the very first connection, before any deployment has run, set it manually as above.

    Why 192.168.0.0/16? The WireWAN server is often running on a machine behind one of the peer routers (e.g., 192.168.95.x), not directly on the overlay. Including the full 192.168.0.0/16 covers any typical home LAN without having to enumerate each one.

MikroTik connection times out or is refused

  1. Check the API service is enabled: /ip service print — confirm api or api-ssl is not disabled
  2. Confirm the management IP is the LAN gateway IP (or public IP), not the tunnel IP — see Management IP. If you originally set up with the tunnel IP and it stopped working after a deploy, that's the chicken-and-egg lockout the validator now blocks.
  3. Confirm the WireGuard tunnel is up: /interface wireguard print and check for recent handshakes on the relevant peer
  4. Confirm RouterOS version is 7.x: /system resource print

Note: WireWAN uses the native binary API (port 8728/8729), not the REST API (port 443/80).

Routes not working after deployment — overlay traffic going via wrong interface

This happens when a pre-existing static route with distance=1 (e.g., from a separate wireguard VPN) targets the same subnet as WireWAN's dist=10 route. The dist=1 route wins and traffic never reaches the overlay.

WireWAN automatically demotes these routes to dist=11 on every deployment. If you're seeing the issue on a fresh deploy, check /ip route print — WireWAN-managed routes have a WAN-Overlay-Manager: comment. Any conflicting route without that comment and with dist < 10 should be auto-demoted.

To fix manually on the router:

/ip route set [find dst-address=<subnet> dynamic=no comment!~"WAN-Overlay-Manager"] distance=11

Asymmetric routing — can ping one way but not the other

Symptom: host A can reach host B but B's replies don't come back (or go via a different path).

Usually caused by the same route shadowing issue above. Check that all peers in the path have WireWAN's route winning for the relevant subnets. The overlay uses a single interface (wg-wan-overlay) so both directions must go via the same tunnel.

Peers can't reach each other

  1. Confirm both peers have applied their current configs (check for the "Config stale" badge)
  2. Check that at least one peer in each path has a reachable public endpoint
  3. Verify the WireGuard interface is up: wg show (Linux) or /interface wireguard print (MikroTik)
  4. Check recent handshakes: wg showlatest handshake should be recent if the tunnel is live
  5. Confirm firewall isn't blocking WireGuard UDP port on either side

Direct session between two peers never establishes (rx=0, last-handshake=never)

Both sides have the right pubkeys, endpoints and allowed-address; both sides are firing keepalives (tx > 0); other pairs work fine. But this specific pair has zero received bytes and the WG log shows Handshake for peer did not complete after 5 seconds, retrying every ~5 seconds.

Diagnostic checklist:

  1. endpoint port mismatch with listen_port. On each side run /interface/wireguard/peers/print detail. If peer A's endpoint-port for B doesn't match B's actual listen-port, the encrypted handshake either lands on the wrong WireGuard interface (when B has another WG on that port) or hits a firewall input rule that drops it. Save validation now blocks this; for legacy data, fix endpoint in WireWAN to use the actual listen-port and redeploy.
  2. Receiving side doesn't have an input-accept rule for UDP/listen_port. Run /ip firewall filter print where comment~"allow-handshake" on both peers. If the rule is missing on either side, the receiving box drops unsolicited handshakes via the default drop all not from LAN rule. Redeploy from WireWAN — the rule is added automatically.
  3. The two peers can't actually reach each other on UDP. This is rare but happens with strict carrier firewalls or one side's home router blocking outbound UDP to specific destinations. From a third machine on each side's LAN, run nc -u <other side public ip> <listen_port> and see whether packets arrive. The post-deploy handshake probe surfaces this case as a warning in the deployment job's operations log.

WireGuard sessions drop on every redeployment

WireWAN diffs peer entries by their WAN-Overlay-Manager: comment and updates changed peers in-place. Sessions for unchanged peers survive redeployments. If sessions are still dropping:

  1. Check that the peer entries on the router have the correct comment format (WAN-Overlay-Manager:peer-<uuid>)
  2. If peer entries were created manually without the comment, WireWAN doesn't know about them and will create duplicates

CGNAT peer can't connect

  1. Confirm at least one other peer has a public endpoint
  2. Verify UDP outbound isn't blocked by the CGNAT provider (rare but possible)
  3. Check PersistentKeepalive = 25 is in the config — it must be present for CGNAT to maintain state
  4. If the peer has been offline a while, re-download and re-apply the config in case topology changed

Subnet conflict warnings

  1. Critical (overlaps tunnel/shared ranges): change the subnet CIDR or mark it as not routed
  2. Warning (overlaps another peer's LAN): use Auto-NAT (check the box when adding, or click "Auto-NAT" on an existing subnet), or mark as not routed

Config changes aren't taking effect on a device

Non-MikroTik peers must manually re-download and re-apply their config after any topology change. Look for the "Config stale" badge in the peer list or the amber banner on the peer detail page.


License

MIT — see LICENSE for details.