systems

Vigil: Producing the Receipts When Your ISP Won't

Today I’m open-sourcing Vigil, a continuous network reliability monitor with a desktop UI. It runs in your system tray, pings the things you actually care about (your router, anycast DNS, the call-quality endpoints behind Teams, Zoom, and Outlook, public STUN servers) every 2.5 seconds. It detects outages live, and it produces the evidence you hand to a person who keeps insisting “the network is fine.”

I live in a managed building. The building has a contract with one ISP. I don’t get to pick.

That trade-off was fine until the network started failing in ways that affected work. Slack huddles drop on certain afternoons. Zoom calls degrade past the 40-minute mark. Some weekday evenings, screen-sharing freezes for ten seconds and recovers itself. The pattern is intermittent, never the entire day, never every meeting, which is exactly the failure mode that’s hardest to escalate.

The property manager’s first response was “have you tried restarting your modem?” The ISP’s first response, after I called, was that the line tested clean and “the issue must be inside the unit.” When a network is unreliable and you don’t control the underlying infrastructure, the burden of proof lands on the person who’s mad. I needed a written record showing the failures were theirs, on their schedule, against targets they can’t dismiss. So I built one.

Vigil is named after the Vigiles: Rome’s night watch. All of my products follow a Roman theme. The Vigiles patrolled the city after dark, watching for fires and break-ins, sounding the alarm when something was wrong. They didn’t put the fires out themselves; their job was to notice and to make a record. That’s the role I needed: a process that watches the network, in the background, and produces the receipt when something goes wrong.

What It Does

Vigil runs as a desktop app in the system tray. The dashboard is the page you see if you pop the window open during the workday:

Vigil dashboard: live RTT chart and per-target status tiles

Every 2.5 seconds, Vigil runs thirteen probes in parallel. The router, auto-detected via the OS routing table, gets pinged over ICMP to prove the LAN is healthy. Google DNS and Cloudflare DNS get pinged over ICMP and UDP/53 to prove the anycast resolvers are reachable. The Teams, Zoom, and Outlook endpoints get checked over ICMP and TCP/443, because proving the actual call-quality services are up matters more than proving generic infrastructure is up. Public STUN servers get checked over UDP, which is what tells you NAT traversal still works, and NAT traversal is what real-time voice and video actually need.

Three consecutive failures of the same target trigger an outage row with a precise start timestamp. The next successful probe closes it with a precise end timestamp. The Outages page is the artifact that ends arguments:

Vigil outages timeline: start/end timestamps, duration, error breakdown

A History page lets you query any time range, pivot per-target, and see uptime / p50 / p95 / p99 stats:

Vigil history page: RTT trend, range picker, per-target percentile stats

The Report Generator outputs CSV, JSON, or HTML. The HTML report is the one that matters: a self-contained file you can email to your ISP that includes uptime percentages, p50/p95/p99 latency, error-code breakdowns, and a timeline of every outage.

How It Works

The architecture splits work between a thin Rust shell and a fat Go sidecar:

┌─────────────────────┐     stdio JSON-line      ┌──────────────────────┐
│   Tauri 2.x shell   │◀────────────────────────▶│   Go sidecar         │
│   (Rust, ~150 LOC)  │                          │  (probe loop, DB,    │
│   tray, window,     │                          │   aggregator,        │
│   updater, IPC      │                          │   outage detector)   │
└──────────┬──────────┘                          └──────────┬───────────┘
           │                                                │
           │ Tauri events                                   │ Ent ORM
           ▼                                                ▼
┌─────────────────────┐                          ┌──────────────────────┐
│  React + Tamagui    │                          │  SQLite              │
│  (dashboard, hist., │                          │  (modernc.org/sqlite,│
│   outages, report)  │                          │   pure Go, no cgo)   │
└─────────────────────┘                          └──────────────────────┘

The shell exists for things only the OS can do: system tray, window lifecycle, auto-updater. Everything else lives in Go. ICMP uses pro-bing (unprivileged ICMP sockets), Wi-Fi info on Linux uses mdlayher/wifi (pure Go netlink, no shell-out to iwconfig), and the database uses modernc.org/sqlite so the binary cross-compiles cleanly without cgo. The split keeps the Rust surface area small enough that adding macOS, Windows, and Linux support didn’t multiply the work.

Keeping raw samples forever doesn’t work. That’s fine for a week, but run it for three months and your SQLite file is several gigabytes, and the History page takes ten seconds to render a year-long view because it’s pulling 35,000 rows. Vigil rolls up to coarser tiers on a timer instead: raw samples every 2.5 seconds for 7 days (the dashboard’s last-15-min view and outage detection), 1-minute rollups for 14 days (fills the 1h-6h chart band), 5-minute rollups for 90 days (the 6h-7d band, a balance of precision and DB size), and 1-hour rollups kept forever for the long view across months. The 1-minute tier exists for chart legibility: a 6-hour view at 5-minute resolution is 72 points, raw is about 8,600, and recharts stays smooth and readable somewhere in the 60-360 point range. Outages themselves live in their own table and are never aggregated or pruned. The historical record is the whole point.

What I’ve Found Running It

I’m running Vigil at home daily, and a few patterns have already shown up in the data that I wouldn’t have been able to prove otherwise. (The specific numbers here come from my own setup; what you’ll see depends on your ISP and building.)

p95 latency to the Zoom edge climbs from around 25ms to around 80ms most weeknights around 9 PM, then recovers around 10:30 PM. A generic Speedtest doesn’t catch this, because it’s a path-specific congestion issue, not a bandwidth one. ICMP to Teams comes back 99.5% reachable; TCP/443 to the same endpoint comes back 97.8%. That 1.7% gap is the signal that matters, since an ICMP-only tool like ping 8.8.8.8 will tell you the network is fine while the application underneath it isn’t.

When the router probe sits at 100% while every external target reads 0%, the failure is upstream of the modem, which is the kind of evidence that makes “it’s your Wi-Fi” a hard thing for a property manager to say with a straight face. Modem reboots have a fingerprint too: everything fails for about 90 seconds, the router probe comes back first, DNS recovers about 10 seconds after that, and everything else trails in last, which is useful if you want to check whether a claimed “overnight equipment reset” actually happened.

Wi-Fi signal turns out to be mostly innocent in my data: RSSI dips correlate weakly with outages, and most outages happen with a strong, stable signal, which pushes the conversation upstream of the access point, which is where the actual problem usually lives.

Most home network conversations end at “your Wi-Fi is bad.” A graph that shows the access point was healthy while the WAN-side probes were red moves the conversation past that.

That’s what happened when the ISP sent a technician. I handed over the HTML report. It showed the connection to the router was fine and the failures were upstream, either on the ISP’s side or at the destination (Teams and Zoom). The technician thanked me for it and said it helped them see where to look. That was the whole point of building it.

Where Vigil Fits, and Where It Doesn’t

Vigil isn’t trying to replace Speedtest or fast.com; those are still the right tool for confirming peak throughput, Vigil just tells you when the path to specific services is failing in between those one-shot checks. It’s complementary to mtr, ping, and traceroute too: Vigil records continuously, those tools are for digging into a specific failure once you know you have one. The closest commercial peer is probably PingPlotter, except Vigil is open source, cross-platform, and built for someone who isn’t an IT pro. If you already have infrastructure to host something, SmokePing does server-side latency monitoring well, and Vigil doesn’t try to compete there since it’s desktop-only and needs no server. And if you’re running carrier-grade NMS tools like PRTG or LibreNMS across many sites, that’s a different scale of problem entirely; Vigil is built for one user on one connection.

Vigil is young, and I want to be upfront about what it doesn’t do yet. There’s no multi-device aggregation: each install is independent, so you can’t merge data from your laptop and your partner’s laptop into one report. There’s no alerting beyond the UI; it detects outages and shows them, but it won’t text you, though tray notifications and a webhook are on the roadmap. There’s no remote access or cloud sync, it writes to your local SQLite and stays there. The probes prove reachability, not call quality, so a 99.9%-reachable Zoom endpoint with 200ms of jitter will still produce a bad call; synthetic WebRTC-style transactions are a longer-term goal. And Windows builds are unsigned, so SmartScreen flags them on first launch with the “Windows protected your PC” dialog (click More info, then Run anyway); macOS and Linux installs are clean, and Azure Trusted Signing is on the list.

None of that is a deal-breaker for what I built it for: one user, one connection, gathering evidence. It might be a deal-breaker for you. If your outage is constant, you don’t need evidence, you need to pick up the phone. If you need observability across many sites, use a real NMS like PRTG, LibreNMS, or Datadog Network Monitoring. If you’re debugging an application-layer issue, Vigil will tell you the network reached the service, not what happened to your specific API request once it got there. And if you want a server-side monitoring platform instead of a desktop one, SmokePing or Prometheus blackbox-exporter are the better fit.

Try It

Download the latest release from GitHub Releases for your OS:

  • macOS Apple Silicon: Vigil_<version>_aarch64.dmg
  • macOS Intel: Vigil_<version>_x64.dmg
  • Windows 10/11: Vigil_<version>_x64-setup.msi
  • Linux (Debian/Ubuntu): vigil_<version>_amd64.deb
  • Linux (everything else): vigil_<version>_amd64.AppImage

Subsequent releases auto-update via tauri-plugin-updater: signed manifests served from updates.romans.dev.

Run it for a week. Generate the HTML report from the History page. Send it to whoever needs to see it.

If you hit rough edges, GitHub Issues is the right place. If you want a probe type Vigil doesn’t support yet (HTTP synthetic transactions, BGP RIB lookups, IPv6-specific probes), Discussions is where to start.

The code is on GitHub. macOS builds are signed and notarized, so Gatekeeper opens them cleanly. Windows builds are still unsigned, so SmartScreen will warn you once. That’s the next thing I’m fixing.


Vigil is part of SID Technologies: small, focused tools, Roman names. The thread that runs through them: build the thing you needed last week, ship it open-source, write down what you learned.