Document periods when your router stays reachable but the internet is not - evidence for the conversation with your ISP.
Live demo dashboard: tommcd.github.io/wanwatch - a real home fibre line, monitored continuously, updated every 10 minutes. Green/red status is fetched live on each visit.
wanwatch is a tiny two-part toolkit born from a real fault-hunt: an
FTTH connection that silently dropped its WAN session dozens of times per
evening while every LED in the house glowed a healthy green. The ISP's
first-line script says "reboot the router". This produces the timestamped
evidence that ends that conversation.
wan_monitor.sh- a dependency-free bash sampler. Every 10 seconds it pings your router (LAN) and two public anycast hosts (WAN) and appends a CSV row on every state change, every sample during an outage, and a one-minute heartbeat. The key signal isUP,DOWN: the router answered while external targets did not - strong grounds for a WAN-side investigation, timestamped to within ~10-16 seconds (up to three 2-second probes plus a 10-second sleep per cycle).wanwatch- a Python CLI that turns the log into statistics, a losslessly condensed interval file you can attach to a complaint email, and an availability timeline PNG.
Runs on Linux, macOS, and Windows (via Git Bash). Windows gets full lifecycle management including a self-healing Task Scheduler watchdog.
Setting up the full continuous-monitoring system (sampler + public GitHub Pages dashboard + dead-man's-switch heartbeat)? See the runbook for step-by-step setup, verification, and teardown. The sections below cover the analysis CLI itself.
Requires uv and, on Windows, Git for Windows (for Git Bash).
git clone https://github.com/tommcd/wanwatch
cd wanwatch
uv sync # creates .venv and installs the CLI into it
uv run wanwatch --helpOr install the CLI on your PATH as a tool:
uv tool install .
wanwatch --help./scripts/wanctl start # background monitor, logs to ~/wan_log.csv
./scripts/wanctl status # PIDs + log freshness ("last sample: 12s ago")
./scripts/wanctl tail # watch the log live
./scripts/wanctl stop
./scripts/wanctl restartConfiguration is via environment variables (or edit the defaults at the
top of the scripts): WANWATCH_ROUTER (default 192.168.1.254) and
WANWATCH_LOG (default ~/wan_log.csv).
wanctl start alone dies with your terminal session. For a long
evidence-gathering campaign, install the Task Scheduler watchdog
(from an elevated Git Bash):
./scripts/wanctl task-install
./scripts/wanctl task-status
./scripts/wanctl task-uninstall # also needed before 'stop' sticksThis registers a task that starts the monitor at boot and re-tries every 5 minutes forever ("do not start a new instance" makes the retry a no-op while it's already running). Whatever kills the monitor - reboot, crash, an accidental Ctrl+C - it is back within 5 minutes, and the gap is visible in the log as a missing heartbeat. It runs S4U: no console window, survives logoff, no stored password.
Two things the watchdog cannot fix - do them once:
- Disable sleep on AC (Settings -> Power), including the lid-close action. A sleeping laptop samples nothing, and evening outages happen exactly when laptops get closed.
- Consider pausing Windows Update while gathering evidence, or set active hours, so it doesn't reboot you mid-incident.
On Linux, run it under systemd or cron instead, e.g.
@reboot /path/to/wan_monitor.sh plus
*/5 * * * * pgrep -f wan_monitor.sh || /path/to/wan_monitor.sh &.
timestamp,lan,wan,event
2026-07-10 21:28:49,UP,DOWN,state_change <- outage begins
2026-07-10 21:29:03,UP,DOWN,outage_sample <- still down (10s resolution)
2026-07-10 21:29:41,UP,UP,state_change <- recovered: 52s outage
2026-07-10 21:30:41,UP,UP,heartbeat <- proof-of-life, 1/min
2026-07-10 21:35:00,UP,-,monitor_started <- monitor (re)launched
| lan | wan | meaning |
|---|---|---|
| UP | UP | healthy |
| UP | DOWN | router answered, external targets did not - WAN-side event |
| DOWN | UP | router ignored a ping (usually an artefact: routers deprioritise ICMP to themselves under load) |
| DOWN | DOWN | monitor host lost the LAN (machine asleep, Wi-Fi blip) |
A hole between heartbeats means the monitor wasn't sampling - honest gaps, not silent ones.
uv run wanwatch analyze ~/wan_log.csvPrints coverage span, an explicit list of monitoring gaps, outage count with min/median/max durations, a per-day table, an hour-of-day histogram (evening clustering shows up instantly), and the full timestamped outage list. Deliberate downtime (e.g. you power-cycled things yourself) is excluded honestly, listed but not counted:
uv run wanwatch analyze ~/wan_log.csv \
--exclude "2026-07-17 21:00:00..2026-07-17 22:30:00"uv run wanwatch condense ~/wan_log.csv -o wan_condensed.csvCollapses runs of identical state into intervals
(start,end,duration_s,lan,wan,kind). A week of ~12,000 heartbeats
becomes a few dozen rows with zero information loss - every state change
and gap preserved to the second.
uv run wanwatch compact ~/wan_log.csv --condensed wan_condensed.csvSafely shrinks the live log: the raw file is atomically renamed aside, condensed into the archive, and deleted; the monitor's next append recreates a fresh raw file. This is race-free because the monitor re-opens the log for every append - an in-flight write lands either in the rotated file (captured) or the new one (captured). Intervals that span compactions are stitched back together. Afterwards, pass both files to the other commands:
uv run wanwatch analyze wan_condensed.csv ~/wan_log.csv(All commands accept raw and condensed files interchangeably and auto-detect the format.)
uv run wanwatch plot wan_condensed.csv ~/wan_log.csv -o wan_timeline.pngRenders an availability timeline: green up, red WAN outages, grey
unmonitored. Sub-minute outages are widened to a 2-minute minimum so they
remain visible at week scale (--min-width to change); the chart says so
in its title, and true durations always come from analyze.
Why grep for TTL= instead of trusting ping's exit code?
Windows ping exits 0 - "success" - when it receives any reply,
including "Destination host unreachable" generated by your own machine
when the router is off. Early versions of this tool logged UP for a
dead network because of exactly this. A genuine echo reply always
contains TTL=; unreachable messages never do.
Why two WAN hosts?
1.1.1.1 and 8.8.8.8 are independent anycast networks. Requiring
either (not both) to reply means a single provider's hiccup can't
masquerade as your line being down.
Why does my log show DOWN,UP?
The router ignored one ping while the internet still answered - typically
ICMP deprioritisation on the router's CPU, not a real LAN failure. It's
logged faithfully and analyze reports it separately from WAN outages.
How big does the log get?
About 150 KB/week at one-minute heartbeats. compact exists more for
tidiness than necessity.
MIT