2026-07-05 SigNoz Alerting Deploy - Graceful-Stop on Atlas, ntfy on Athena
Shipped the SigNoz alerting work from 2026-07-04 SigNoz Alert Notification Gap and ntfy Decision and 2026-07-05 SigNoz ClickHouse Replication Queue Root Cause and Graceful-Stop Fix to main and deployed it across both hosts.
What shipped
- Rebased
signoz-alertingontomain(no conflicts: main’s 7 intervening commits were the athena profile split, touching none of the 4 alerting files), fast-forwarded and pushed. Two commits: the graceful-stop fix and the ntfy module + ADR 0019. - Follow-up commit
a3ce5dbenables ntfy in athena’s curatedhome-server.nixprofile (also fixed a statixinheritlint in ntfy.nix that the full pre-commit scan surfaced).
Atlas (graceful-stop fix)
hm switch on atlas regenerated the compose files and recreated ClickHouse + ZooKeeper once (gracefully; queue was at 0 beforehand). Verified: StopTimeout now 300s / 120s, replication queue stayed at 0, all containers healthy. otel-collector recreated via the new scoped --force-recreate --no-deps path rather than the old blanket recreate.
Athena (ntfy notification server)
- Curated profile imports
ntfy.nix+ enables it; fronted over tailnet HTTPS by Tailscale Serve athttps://athena.tail0cef90.ts.net:443 -> 127.0.0.1:2586. Running as launchd agentsh.ntfy.server, ntfy 2.25.0. - Provisioned deny-all auth per ADR 0019:
signozuser (write-only) +phoneuser (read-only) on topicsignoz-alerts. Token minted. Secrets stored in a chmod-600 file on athena (~/.local/share/ntfy/provisioned-secrets); deliberately kept off the session transcript (the auto-mode classifier correctly blocked echoing them). - Repointed SigNoz’s
local-placeholderwebhook channel (id019dbfe8-...) from the deadhost.docker.internal:9999tohttps://athena.tail0cef90.ts.net/signoz-alerts?template=alertmanagerwith basic auth, via the SigNoz API on atlas (sourcing the password in-process).send_resolved=true.
Verification
- Docker-container reachability was the real risk (SigNoz runs in Docker on atlas, ntfy on athena over the tailnet). Confirmed the
signozcontainer resolves MagicDNS and reacheshttps://athena.tail0cef90.ts.net/v1/healthwith valid TLS. - SigNoz channel-test delivered end-to-end: ntfy received the
alertmanager-templated message (🚨 Alert: Test Alert (local-placeholder)/Severity: critical), confirmed by polling as the read-onlyphoneuser.
Remaining / optional
- Phone app (manual, user): install/log into the ntfy iOS app as
phone(creds in athena’s secrets file), subscribe tosignoz-alerts. iPhone was offline (last seen ~22h) at deploy time. - Priority mapping (optional): the built-in
alertmanagertemplate leavespriority: null, so criticals won’t escalate on iOS. A customprograms.ntfy.templatesentry mappingseverity→ ntfy priority would fix that.
Related
- 2026-07-04 SigNoz Alert Notification Gap and ntfy Decision
- 2026-07-05 SigNoz ClickHouse Replication Queue Root Cause and Graceful-Stop Fix
- ADR 0019 (docs/decisions/0019-signoz-alert-notification-channel.md)
Follow-up: topic rotation + alertmanager hot-reload gotcha
Checking the ntfy iOS app surfaced that the topic signoz-alerts was guessable. On our server the content is safe (deny-all auth), but the upstream-base-url=https://ntfy.sh APNs relay forwards empty wake-up polls carrying the topic name to the public server, leaking alert-timing metadata. Rotated to an unguessable random topic:
ntfy accessgrants moved to the new topic for both users; oldsignoz-alertsexplicitly denied. New topic stored in athena’sprovisioned-secretsfile, kept out of git (repo is private, but defense-in-depth). Fixed the now-stale topic comment inhome-server.nix(commit06bbc2a).- Verified: authenticated publish to the new topic → 200, anonymous → 403.
Gotcha found in the process (and a wrong turn I later corrected): SigNoz’s test endpoints are misleading. POST /api/v1/channels/{id}/test is a no-op — returns 200 but publishes nothing — which I initially misread as evidence that the running alertmanager config was stale and needed a restart. POST /api/v1/testChannel does deliver, but it uses the config in the POSTed body, so it only proves url/auth/template/network, not what the alertmanager has loaded. Neither proves real-alert delivery.
I did docker restart signoz (a safe way to force the alertmanager to reload receivers from the channel store), then verified definitively with a disposable always-firing threshold_rule on ClickHouseAsyncMetrics_ReplicasSumQueueSize (above -1, at_least_once, 1m frequency, channel local-placeholder): it went firing within ~2 min and delivered the templated 🚨 Alert: zzz-delivery-verification-temporary to the rotated topic — proving real alerts route through the loaded config to ntfy. Deleted the temp alert after. Verification rule recorded in reference_ntfy_alert_sink_athena: never trust the test endpoints; confirm with a real disposable alert firing.