- Python 63.9%
- TypeScript 34.4%
- Shell 0.7%
- Dockerfile 0.5%
- CSS 0.3%
- Other 0.2%
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. |
||
|---|---|---|
| backend | ||
| data | ||
| frontend | ||
| .env | ||
| .gitignore | ||
| DEPLOYMENT.md | ||
| docker-compose.yml | ||
| LICENSE | ||
| README.md | ||
| run.sh | ||
| SPEC.md | ||
WireWAN
WireGuard WAN Overlay Network Manager
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
- Quick Start
- Scenarios
- How the overlay works
- MikroTik integration
- Topology types
- Config staleness
- Conflict detection and NAT
- Security
- API
- Tech stack
- Troubleshooting
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:
-
Create a WAN network — WAN Networks → Create WAN. Accept the defaults (
10.0.0.0/24tunnel range,10.0.5.0/24shared services). Topology: Mesh. -
Add your home server (public endpoint, e.g., a VPS or port-forwarded router):
- Type: Server
- Endpoint:
yourserver.duckdns.orgor203.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 withsudo wg-quick up ./yourserver.conf
-
Add friend A (has a VPS with a public IP):
- Type: Server
- Endpoint:
friend-a-vps.example.com:51820 - Friend A downloads their
.confand applies it the same way
-
Add friend B (laptop behind CGNAT, no port forward — see next scenario):
- Type: Client
- Endpoint: leave empty
- Friend B downloads their
.confand applies it
-
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
.confto 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 = 25is 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:
-
Create a WAN network with Topology: Hub-and-Spoke.
-
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
-
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
- Each spoke's generated config only contains a
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.xaddresses - 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:
- Create/update the
wg-wan-overlayWireGuard interface (skipped if listen-port and private-key are unchanged) - Reconcile WireGuard peer entries: update changed peers in-place, add new ones, remove deleted ones — sessions for unchanged peers are preserved
- Configure the tunnel IP address on the interface
- Add routes (distance=10) for all remote subnets and the tunnel range
- Demote any pre-existing conflicting static routes (see below)
- Add firewall filter rules: accept input and forward on the overlay interface
- Add NAT rules: no-masquerade for overlay traffic, plus dstnat rules for any published services
- 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 < 10is automatically set todistance=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, orpublic_keyis 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:
-
endpointport must equallisten_portunlesspeer_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 withlast-handshake = neverand 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. -
mikrotik_management_ipcannot be in the WAN's tunnel range. See the section above — chicken-and-egg lockout on tunnel disruption. -
Every deploy emits an input-chain
accept udp dst-port=<listen_port>rule. MikroTiks running the RouterOS defaultchain=input action=drop in-interface-list=!LANrule 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 actuallisten_port, so peers running multiple WG interfaces on different ports stay isolated. The rule is taggedWAN-Overlay-Manager:allow-handshake-{peer.id}so verify-deployment picks it up. -
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 tolisten_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_refreshflag 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/12and 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=falseinbackend/.env - Set
CORS_ORIGINSto your actual domain - Put behind HTTPS reverse proxy (nginx, Caddy, Traefik)
- Enable
mikrotik_verify_certif 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:
-
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.
-
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 thewirewanuser'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/16For 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/16WireWAN 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 full192.168.0.0/16covers any typical home LAN without having to enumerate each one.
MikroTik connection times out or is refused
- Check the API service is enabled:
/ip service print— confirmapiorapi-sslis not disabled - 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.
- Confirm the WireGuard tunnel is up:
/interface wireguard printand check for recent handshakes on the relevant peer - 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
- Confirm both peers have applied their current configs (check for the "Config stale" badge)
- Check that at least one peer in each path has a reachable public endpoint
- Verify the WireGuard interface is up:
wg show(Linux) or/interface wireguard print(MikroTik) - Check recent handshakes:
wg show—latest handshakeshould be recent if the tunnel is live - 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:
endpointport mismatch withlisten_port. On each side run/interface/wireguard/peers/print detail. If peer A'sendpoint-portfor B doesn't match B's actuallisten-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, fixendpointin WireWAN to use the actual listen-port and redeploy.- 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 defaultdrop all not from LANrule. Redeploy from WireWAN — the rule is added automatically. - 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:
- Check that the peer entries on the router have the correct comment format (
WAN-Overlay-Manager:peer-<uuid>) - If peer entries were created manually without the comment, WireWAN doesn't know about them and will create duplicates
CGNAT peer can't connect
- Confirm at least one other peer has a public endpoint
- Verify UDP outbound isn't blocked by the CGNAT provider (rare but possible)
- Check
PersistentKeepalive = 25is in the config — it must be present for CGNAT to maintain state - If the peer has been offline a while, re-download and re-apply the config in case topology changed
Subnet conflict warnings
- Critical (overlaps tunnel/shared ranges): change the subnet CIDR or mark it as not routed
- 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.