{"id":49413835,"url":"https://github.com/pacnpal/wireguard-watchdog","last_synced_at":"2026-04-29T02:05:02.915Z","repository":{"id":354034152,"uuid":"1221870309","full_name":"pacnpal/wireguard-watchdog","owner":"pacnpal","description":"Unraid plugin: monitors a WireGuard tunnel by pinging a peer through the interface and bounces the tunnel via wg-quick on failure.","archived":false,"fork":false,"pushed_at":"2026-04-26T23:33:07.000Z","size":118,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-27T01:03:06.272Z","etag":null,"topics":["networking","slackware","unraid","unraid-plugin","vpn","watchdog","wireguard"],"latest_commit_sha":null,"homepage":"https://github.com/pacnpal/wireguard-watchdog","language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pacnpal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-26T19:34:09.000Z","updated_at":"2026-04-26T23:33:11.000Z","dependencies_parsed_at":"2026-04-27T01:00:33.260Z","dependency_job_id":null,"html_url":"https://github.com/pacnpal/wireguard-watchdog","commit_stats":null,"previous_names":["pacnpal/wireguard-watchdog"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/pacnpal/wireguard-watchdog","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pacnpal%2Fwireguard-watchdog","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pacnpal%2Fwireguard-watchdog/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pacnpal%2Fwireguard-watchdog/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pacnpal%2Fwireguard-watchdog/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pacnpal","download_url":"https://codeload.github.com/pacnpal/wireguard-watchdog/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pacnpal%2Fwireguard-watchdog/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32407176,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-28T19:38:08.556Z","status":"online","status_checked_at":"2026-04-29T02:00:06.602Z","response_time":110,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["networking","slackware","unraid","unraid-plugin","vpn","watchdog","wireguard"],"created_at":"2026-04-29T02:05:02.333Z","updated_at":"2026-04-29T02:05:02.904Z","avatar_url":"https://github.com/pacnpal.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"assets/logo.png\" alt=\"WireGuard Watchdog logo\" width=\"160\" height=\"160\"\u003e\n\n# WireGuard Watchdog\n\n**An Unraid plugin that keeps your WireGuard tunnel healthy.**\nPings a peer through the tunnel on a schedule; resets peer state with\n`wg syncconf` the moment the peer goes silent, falling back to a full\n`wg-quick down/up` only as a last resort when the soft recovery path\nfails.\n\n[![Latest release](https://img.shields.io/github/v/release/pacnpal/wireguard-watchdog?label=release\u0026color=88171a)](https://github.com/pacnpal/wireguard-watchdog/releases/latest)\n[![License: MIT](https://img.shields.io/github/license/pacnpal/wireguard-watchdog?color=blue)](LICENSE)\n[![Unraid 6.12+](https://img.shields.io/badge/Unraid-6.12%2B-f15a2c)](https://unraid.net/)\n[![Lint](https://img.shields.io/github/actions/workflow/status/pacnpal/wireguard-watchdog/lint.yml?branch=main\u0026label=lint)](https://github.com/pacnpal/wireguard-watchdog/actions/workflows/lint.yml)\n[![Release workflow](https://img.shields.io/github/actions/workflow/status/pacnpal/wireguard-watchdog/release.yml?label=release%20build)](https://github.com/pacnpal/wireguard-watchdog/actions/workflows/release.yml)\n[![Downloads](https://img.shields.io/github/downloads/pacnpal/wireguard-watchdog/total?color=2ee4a3)](https://github.com/pacnpal/wireguard-watchdog/releases)\n\n\u003c/div\u003e\n\n\u003e [!IMPORTANT]\n\u003e Requires Unraid's built-in **WireGuard** support (Settings → VPN\n\u003e Manager) and at least one configured tunnel (`wg0`, `wg1`, …). The\n\u003e watchdog uses `wg syncconf` (and, only as a last resort, `wg-quick`),\n\u003e the same tools Unraid uses internally — so the two coexist cleanly.\n\n## Why?\n\nWireGuard is silent when it fails. A peer going down or a NAT mapping\nexpiring leaves the tunnel \"up\" from the local side — `wg show` looks\nfine, but no traffic flows. The fix is always the same: bounce the\ntunnel. This plugin automates that bounce, gated behind a real\nliveness check (a ping through the interface, not just a check that\nthe daemon exists).\n\nUse it if you:\n- run a site-to-site tunnel and want unattended recovery from the\n  remote side rebooting or losing internet briefly,\n- depend on the tunnel for critical traffic (Docker containers, VMs)\n  and don't want to babysit it,\n- want a quick visual confirmation in the UI that the tunnel is\n  reachable right now.\n\n## Install\n\n1. Open the Unraid web UI → **Plugins** tab → **Install Plugin**.\n2. Paste the `.plg` URL:\n   ```\n   https://raw.githubusercontent.com/pacnpal/wireguard-watchdog/main/plugin/wg-watchdog.plg\n   ```\n3. Click **Install**. The plugin downloads its `.txz` from the matching\n   GitHub release and installs to `/usr/local/emhttp/plugins/wg-watchdog/`.\n4. Open **Tools → User Utilities → WireGuard Watchdog**, fill in the\n   form, set **Enabled = yes**, click **Apply**.\n\nThe plugin defaults to `Enabled=no` on first install. Nothing runs until\nyou explicitly enable it.\n\n## Configuration\n\n| Field             | Default                       | Notes |\n|-------------------|-------------------------------|-------|\n| Enabled           | `no`                          | Master toggle. `no` removes the cron entry. |\n| Tunnel interface  | `wg0`                         | Must be a configured WireGuard interface. |\n| Peer IP to ping   | `10.99.0.1`                   | Reachable through the tunnel. |\n| Check interval    | `60` seconds (min `20`)       | Below 60s: cron uses per-minute lines with `sleep` offsets. |\n| Verbose logging   | `no`                          | If `yes`, each successful ping is logged too. |\n| Log file          | `/var/log/wg-watchdog.log`    | Read-only display in the UI. |\n\n**Buttons:**\n- **Apply** — posts the form to Unraid's `/update.php`, which writes\n  `/boot/config/plugins/wg-watchdog/wg-watchdog.cfg` and runs\n  `scripts/install_cron.sh` (regenerates the cron file and calls\n  `update_cron`).\n- **Test Now** — runs `watchdog.sh --test` once and shows the output\n  inline. Honours your settings but ignores the Enabled toggle.\n- **View Log** — tails the last 200 lines of the configured log file.\n- **Clear Log** — truncates the log file (with confirmation prompt).\n\n\u003e _Screenshot placeholder: Tools → User Utilities → WireGuard Watchdog._\n\n## How it works\n\n- `scripts/watchdog.sh` is the only thing scheduled. It:\n  1. Sources `/boot/config/plugins/wg-watchdog/wg-watchdog.cfg`.\n  2. Holds an exclusive `flock` on `/var/lock/wg-watchdog.lock` so\n     overlapping cron firings can't trample each other.\n  3. Runs `ping -c 2 -W 3 -I $INTERFACE $PEER_IP`.\n  4. On failure (soft bounce): runs `wg-quick strip $INTERFACE` and\n     verifies the output is non-empty before touching any live state.\n     Only then does it drop every peer via\n     `wg set $INTERFACE peer \u003ckey\u003e remove` and re-apply the stripped\n     conf with `wg syncconf $INTERFACE /dev/stdin`. This ordering\n     matters: if strip fails, the live interface is left untouched\n     and we go straight to the hard fallback — never an \"interface\n     up but peers wiped\" half-state.\n  5. Hard bounce only if `wg-quick strip` or `wg syncconf` itself\n     fails (malformed or unreadable conf): `wg-quick down $INTERFACE`\n     → `sleep 2` → `wg-quick up $INTERFACE`. A successful\n     `wg syncconf` is taken as the source of truth — even if some\n     peers failed to pre-remove — because it means the running\n     interface matches the on-disk conf.\n  6. **The hard bounce is gated.** Before running it the watchdog\n     parses the conf and checks `wg show $INTERFACE fwmark`. If the\n     conf is \"redirect-prone\" — `AllowedIPs = 0.0.0.0/0` (or `::/0`)\n     without `Table = off` — AND no auto-routing fwmark is currently\n     set on the live interface, the hard bounce would newly install\n     `ip rule not fwmark \u003cT\u003e table \u003cT\u003e` and silently redirect all\n     unmarked host traffic (including any `--network host` Docker\n     container) through the tunnel. The watchdog refuses and exits 1\n     with an explanation; add `Table = off` to the conf to opt in.\n     If `$INTERFACE` is missing entirely, the precheck at the top\n     of the script logs `FAIL: interface ... does not exist` and\n     exits — the watchdog heals an existing tunnel, it does not\n     bring one up from cold.\n- `scripts/install_cron.sh` reads the cfg and writes\n  `/boot/config/plugins/wg-watchdog/wg-watchdog.cron`, then calls\n  `/usr/local/sbin/update_cron`. Unraid persists cron files from\n  `/boot/config/plugins/*/...cron` across reboots.\n- `event/started` re-syncs cron when the array starts.\n- `event/stopping` removes the active cron entry so no checks fire\n  during shutdown.\n\nThe plugin uses the same tools as Unraid's built-in WireGuard\n(`wg`, `wg-quick`). The soft-bounce path was chosen specifically to\navoid an `wg-quick down`/`up` side effect: when a conf has\n`AllowedIPs = 0.0.0.0/0` without `Table = off` (typical of imported\nVPN-provider configs), `wg-quick up` adds an `ip rule not fwmark\n51820 table 51820` rule that funnels every unmarked packet through the\ntunnel — which redirects host traffic, including any Docker container\nrunning with `--network host`. `wg syncconf` doesn't do that.\n\n## Build \u0026 release\n\nReleases are cut from the **Actions** tab → **Build and Release** →\n**Run workflow** ([release.yml](.github/workflows/release.yml)).\n\n`./build.sh` is for local testing only — the workflow builds and\nattaches the public release assets.\n\n## Test plan\n\nTested target: Unraid 7.2.x in a VM with a `wg0` tunnel configured in\n**Settings → VPN Manager** against a reachable peer.\n\n1. **Install**\n   - Build with `./build.sh`. Push to a test branch + create a release.\n   - Paste the .plg URL into Plugins → Install Plugin.\n   - Verify install log ends with the \"wg-watchdog … installed\" banner.\n   - Verify **Tools → User Utilities → WireGuard Watchdog** appears.\n\n2. **Defaults**\n   - Open the page. Confirm `Enabled = no`, `INTERFACE = wg0`,\n     `PEER_IP = 10.99.0.1`, `INTERVAL = 60`, log path shown.\n   - Confirm `/boot/config/plugins/wg-watchdog/wg-watchdog.cfg` exists.\n   - Confirm no cron file at `/etc/cron.d/wg-watchdog` (disabled state).\n\n3. **Apply / cron install**\n   - Set `Enabled = yes`, `PEER_IP` to the actual peer's tunnel IP,\n     `INTERVAL = 60`, click **Apply**.\n   - Confirm `/boot/config/plugins/wg-watchdog/wg-watchdog.cron` was\n     written and `/etc/cron.d/wg-watchdog` was created by `update_cron`.\n\n4. **Test Now (happy path)**\n   - Click **Test Now**.\n   - Expect output containing `OK: \u003cpeer\u003e reachable via wg0`.\n\n5. **Failure simulation (soft path)**\n   - With `wg0` up, change the peer's PublicKey on the remote side\n     (or block the peer's UDP port on the remote firewall) so the\n     handshake stops working but the local interface stays up.\n   - Wait one cron interval (or click **Test Now** to force).\n   - Expect log entry `FAIL: ... unreachable via wg0 -- bouncing tunnel`,\n     followed by `wg syncconf wg0: ok (peer state reset; routes preserved)`.\n   - Confirm `ip rule show` and `ip route show table all` are unchanged\n     vs. before the bounce.\n   - Restore the peer; expect a fresh handshake within ~5 s of the next\n     ping.\n\n6. **Failure simulation (hard path)**\n   - With `wg0` up, make `wg syncconf` fail by temporarily corrupting\n     the on-disk conf, e.g.: `mv /etc/wireguard/wg0.conf{,.bak} \u0026\u0026 \\\n     printf 'garbage\\n' \u003e /etc/wireguard/wg0.conf` (the interface\n     itself stays up; only `wg-quick strip` will fail).\n   - Click **Test Now** (or wait an interval).\n   - Expect `wg-quick strip wg0: failed (rc=...) -- skipping soft\n     bounce`, then `soft bounce did not recover; evaluating hard\n     fallback`, then `wg-quick down wg0: ...` and `wg-quick up wg0:\n     failed (rc=...)` — the hard bounce will also fail because the\n     conf is broken, which is the point of the test (we just want\n     to see the fallback fire).\n   - Confirm the live interface's peers are still intact via\n     `wg show wg0` — the strip-first ordering means we never\n     touched them when strip failed.\n   - Restore: `mv /etc/wireguard/wg0.conf.bak /etc/wireguard/wg0.conf`\n     and bring the tunnel back up via VPN Manager or `wg-quick up wg0`.\n\n7. **Interface-missing exit**\n   - From SSH: `wg-quick down wg0`.\n   - Click **Test Now**.\n   - Expect `FAIL: interface wg0 does not exist` and exit 1 — no\n     bounce, no `wg-quick up`. Restore via VPN Manager.\n\n8. **Lock contention**\n   - Set `INTERVAL = 20`, click **Apply**.\n   - Tail the log; with verbose enabled, confirm only one run executes\n     at a time even with overlapping firings.\n\n9. **Persistence**\n   - Reboot the server.\n   - After array start, confirm `/etc/cron.d/wg-watchdog` is back\n     (regenerated by `event/started`).\n   - Confirm log contains an `event: started` entry.\n\n10. **Disable**\n    - Set `Enabled = no`, **Apply**.\n    - Confirm both `/boot/config/.../wg-watchdog.cron` and\n      `/etc/cron.d/wg-watchdog` are gone.\n\n11. **Uninstall**\n   - Remove the plugin from the Plugins tab.\n   - Confirm `/boot/config/plugins/wg-watchdog/` and\n     `/usr/local/emhttp/plugins/wg-watchdog/` are gone.\n   - Confirm `/var/log/wg-watchdog.log` is **preserved**.\n\n## Troubleshooting\n\n| Symptom | Likely cause | Where to look |\n|---|---|---|\n| **Test Now** prints `FAIL: wg0 is not configured under Settings -\u003e VPN Manager` | The conf file `/etc/wireguard/wg0.conf` is missing — VPN Manager creates it when you add a tunnel. Either the interface name in the watchdog cfg is wrong, or no tunnel exists yet. | Settings → VPN Manager. Verbose mode lists the configured `*.conf` files. |\n| **Test Now** prints `FAIL: interface wg0 does not exist (configured but not active...)` | The conf exists but the tunnel is currently down. | Toggle the tunnel on under Settings → VPN Manager (or `wg-quick up wg0`). Verbose mode lists the active wg interfaces. |\n| Test passes, but cron never fires | Service disabled, or `update_cron` wasn't called after Apply. | `cat /etc/cron.d/wg-watchdog` should exist; `cat /boot/config/plugins/wg-watchdog/wg-watchdog.cfg` should show `SERVICE_ENABLED=\"yes\"`. |\n| Bounces happen but tunnel stays down | The peer is genuinely unreachable, or the soft bounce isn't enough and `wg-quick up` is failing. | Tail `/var/log/wg-watchdog.log` for `wg syncconf wg0: failed` followed by `wg-quick up wg0: failed`; run them manually to see the error. |\n| Log says `REFUSING hard bounce: ... has AllowedIPs=0.0.0.0/0 ... without 'Table = off'` | The soft bounce didn't recover and the conf would cause `wg-quick up` to install `ip rule not fwmark ... table ...`, which redirects host (and `--network host` Docker) traffic. The watchdog refuses to inflict that. | Add `Table = off` to `[Interface]` in `/etc/wireguard/wg0.conf` and manage routes yourself via PostUp/PostDown — or, if you genuinely want full-tunnel redirect, run `wg-quick up wg0` once manually so the auto-routing fwmark is in place; the watchdog will then allow the hard bounce. |\n| Log says `skipped: previous run still in progress` repeatedly | A check is taking longer than the interval (DNS hangs, network stalls). | Lengthen the interval, or set `VERBOSE=\"no\"` to suppress these messages. |\n| Log file fills the flash drive | Verbose left on for months. | Set Verbose=no, or rotate by truncating: `: \u003e /var/log/wg-watchdog.log`. |\n| **View Log** says \"log file not yet created\" | First boot or just installed; nothing's run yet. | Click **Test Now** once. |\n\nFor anything else, file an issue with the contents of\n`/boot/config/plugins/wg-watchdog/wg-watchdog.cfg` and the last ~50\nlines of `/var/log/wg-watchdog.log`.\n\n## Repo layout\n\n```\nwireguard-watchdog/\n├── README.md\n├── LICENSE\n├── build.sh\n├── wg-watchdog.plg.in           # template; build.sh fills @@VERSION@@/@@MD5@@/@@PKG@@\n├── .github/\n│   ├── ISSUE_TEMPLATE/{bug_report,feature_request}.yml\n│   └── workflows/{release,lint}.yml\n├── assets/\n│   ├── logo.svg                 # source vector\n│   ├── logo{,-128,-512}.png     # rasterised by render-png.py\n│   └── render-png.py\n├── source/                      # installs to /usr/local/emhttp/plugins/wg-watchdog/\n│   ├── default.cfg\n│   ├── wg-watchdog.page\n│   ├── include/{test,log,clear}.php\n│   ├── scripts/{watchdog,install_cron,remove_cron}.sh\n│   └── event/{started,stopping}\n└── dist/                        # produced by build.sh; not checked in\n    ├── wg-watchdog-\u003cversion\u003e-noarch-1.txz\n    └── wg-watchdog.plg\n```\n\n## Notes\n\n- The watchdog prefers `wg syncconf` (a strict re-application of the\n  on-disk conf to the running interface). The hard `wg-quick down/up`\n  fallback only runs when the soft path fails, and is itself gated\n  behind a redirect-prone-conf check: the script refuses to run a\n  hard bounce that would newly install wg-quick's auto-routing\n  (`ip rule not fwmark ... table ...`) on a host where it isn't\n  already in effect. This is the rigorous fix for the \"redirected\n  host / `--network host` Docker traffic through wg0\" report.\n- Tests live under `tests/`; run `bash tests/run.sh` locally. CI runs\n  them on every push and PR via `.github/workflows/lint.yml`.\n\n## License\n\n[MIT](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpacnpal%2Fwireguard-watchdog","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpacnpal%2Fwireguard-watchdog","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpacnpal%2Fwireguard-watchdog/lists"}