LinuxmonitoringTested on real hardware

Self-Host changedetection.io With ntfy Alerts

Deploy changedetection.io in an LXC, add browserless watches with JSON/XPath filters, push changes to a dedicated ntfy topic via Apprise, and add a dead-man probe.

DistrosDebian 13, Debian 12
Shellbash
Updated
Script
bash
# Deploy changedetection.io (website change monitor) natively in a Debian LXC,
# browserless, with a dedicated ntfy notification topic.
# Full walkthrough: /blog/changedetection-watch-web-pages

# 1. System packages (Python 3.10+ venv toolchain).
apt-get update
apt-get install -y python3 python3-venv python3-pip

# 2. Dedicated user + venv install, pinned to a known release.
useradd --system --home-dir /var/lib/changedetection --create-home \
  --shell /usr/sbin/nologin changedetection || true
python3 -m venv /opt/changedetection
/opt/changedetection/bin/pip install -U pip
/opt/changedetection/bin/pip install changedetection.io==0.55.8
install -d -o changedetection -g changedetection /var/lib/changedetection/data

# 3. systemd unit. ProtectSystem=full keeps it from writing outside its data dir.
cat > /etc/systemd/system/changedetection.service <<'EOF'
[Unit]
Description=changedetection.io website change monitor
After=network.target

[Service]
User=changedetection
ExecStart=/opt/changedetection/bin/changedetection.io -d /var/lib/changedetection/data -p 5000
ProtectSystem=full
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now changedetection
# UI now on http://<this-host>:5000  (NO auth by default -- keep it on a trusted subnet)

# --- Configure in the UI (Filters & Triggers + Notifications) ---
# Add browserless watches (no JS browser needed) and precise filters:
#   Releases JSON API      -> filter  json:$.tag_name
#   Vendor downloads JSON  -> filter  json:$.<path.to.version>
#   Announcements RSS feed -> filter  xpath://item/title   (avoids lastBuildDate re-firing)
#   Static HTML page       -> a CSS/XPath selector for the region you care about
#
# Notifications: add an Apprise ntfy URL on each watch (or globally):
#   ntfy://homelab-releases                          (public ntfy.sh, dedicated topic)
#   ntfys://ntfy.homelab.lan/homelab-releases        (self-hosted ntfy server)
# Subscribe your phone's ntfy app to that topic.

# 4. Dead-man probe: point a Prometheus blackbox http_2xx probe at the UI and
#    alert if probe_success == 0 -- a dead watcher must page, not fail silently.

What this does

This deploys changedetection.io — an open-source website change monitor — natively in a Debian LXC under systemd, configured for browserless watches (JSON APIs, RSS/Atom feeds, static HTML) so it runs happily on a 1 GB container. Changes are pushed to a dedicated ntfy topic via the Apprise library, and a black-box probe keeps the watcher itself honest.

The full walkthrough — endpoint choice, the RSS lastBuildDate trap, and end-to-end notification testing — is in Watch Any Web Page for Changes With changedetection.io.

Prerequisites

  • A Debian LXC or small VM with Python 3.10+ and outbound internet (it fetches the pages you watch). 2 CPU / 1 GB RAM / 10 GB disk is enough for browserless watches.
  • An ntfy topic and the ntfy app on your phone (public ntfy.sh or a self-hosted server).
  • Optional: a Prometheus + blackbox setup for the dead-man probe.

Notes

  • Make these values your own before you rely on the result: choose your own ntfy topic in place of homelab-releases (pick something unguessable — public ntfy topics are readable by anyone who knows the name), replace ntfy.homelab.lan with your real ntfy server if self-hosting, and point watches at your own target URLs. If a value looks specific to one machine, it’s a placeholder to change, not a literal to copy.
  • No authentication by default. Anyone who can reach port 5000 can edit your watches. Keep it on a trusted LAN or reach it over Tailscale; never expose it directly to the internet.
  • Prefer browserless targets. JSON, RSS/Atom, and static HTML parse without the bundled headless browser, which is far lighter. Reserve the browser for pages that only render content via JavaScript. Filters live under Filters & Triggers and support XPath, JSONPath, jq, and CSS.
  • Filter RSS feeds or they alert forever. A feed’s lastBuildDate regenerates every fetch and counts as a change; filter to xpath://item/title so you only hear about real new items.
  • v0.55 stores config in changedetection.json (not the older url-watches.json), written lazily — restart the service to flush it before backing up. The API key lives at settings.application.api_access_token in that file and in Settings → API; read it in place, don’t move it around.
  • Use a dedicated notification topic. Keep release notices off the same channel as urgent pages (down services, failed backups) so the urgent channel stays meaningful.
  • Prove the whole chain. Add a throwaway watch on a page that changes every request, force a recheck, confirm the ping lands on your phone, then delete the test watch. Don’t assume any single link works.