Error trackers fail in the one way nobody is watching for: quietly. Sentry's SDKs are built never to take down the application they are in, so when the server stops answering they give up on each event after a short try and move on. Your issues list stops growing. Nothing pages. A week later someone notices the release that "had no errors" was never reporting at all. Self-hosted Sentry adds a deadline to this: its Kafka retention is 24 hours by default, so events that reached the server while the consumers were down are gone after a day.
This guide sets up external monitoring for a self-hosted Sentry, with notes for GlitchTip, the lighter Sentry-compatible alternative. The point is not to replace the stack's internal Docker healthchecks; it is to have something outside the deployment that notices when the whole thing, or its front door, is gone. Status checks are free on CronAlert; keyword assertions and heartbeats are Pro ($5/month) and are marked.
What the stack looks like from outside
A stock self-hosted install is a docker-compose deployment: nginx on port 9000 in front of the web service, plus relay (ingest), Kafka, snuba-api and its consumers, ClickHouse, PostgreSQL, Redis (Valkey), symbolicator, the task workers, an smtp container, and, in recent releases, an uptime-checker. Nearly all of it is internal. From the internet you can reach exactly two things through your reverse proxy: the web UI and the ingest API that SDKs post to.
| Surface | Path | Reachable externally | Monitor with |
|---|---|---|---|
| Web health | /_health/ | yes | HTTP monitor, expect 200 |
| Web UI | /auth/login/ | yes | HTTP monitor + keyword (Pro) |
| Ingest | /api/<project>/envelope/ | yes, POST with body | Heartbeat canary (Pro) |
| Relay health | /api/relay/healthcheck/ready/ | no (not proxied by stock nginx) | Heartbeat from inside (Pro) |
| Snuba, Symbolicator, workers | internal | no | Heartbeat from inside (Pro) |
| Backups | scripts/backup.sh | no | Heartbeat (Pro) |
Step 1: the /_health/ monitor
Sentry's self-hosted documentation suggests configuring a load balancer health check against /_health/, which returns a 200 if Sentry is up or a 500 with the list of problems. The web container's own Docker healthcheck hits the same URL (http://127.0.0.1:9000/_health/), so it is the officially sanctioned signal. It needs no authentication.
- Add an HTTP monitor on
https://sentry.example.com/_health/with the trailing slash (Django redirects without it, and the monitor would follow the redirect anyway, but be exact). - Method
GET, expected status200, timeout10seconds. - Use the public hostname your DSNs use, so the check exercises the same proxy and certificate the SDKs do.
Adding ?full to the URL asks Sentry to run its full set of status checks rather than the quick one; on installs where that works it surfaces the same warnings you see in the admin UI, including "Background workers haven't checked in recently" with the size of the task backlog. Try it with curl -i first; if it returns a 500 on an install you consider healthy, read the problems it lists before deciding whether to monitor it.
The check interval comes from your plan, every 3 minutes on Free or every minute on Pro, and alerts go to your team's channels: email, Slack, push, Discord, Telegram, PagerDuty, or a webhook. Do not route this alert through a system that itself depends on Sentry.
Step 2: the login page (Pro)
/_health/ is served by the Django application. A reverse proxy returning its own 200 error page would fail it, but a cached or misrouted response could not be ruled out from status alone. Add a monitor on https://sentry.example.com/auth/login/ with the assertion contains Sentry, or a more specific string from your login page's HTML such as the organisation slug. It fails if the proxy serves something else, if static assets are missing after a bad upgrade, or if the web tier is up but the application is not rendering. Keyword monitoring covers the match types; false negatives explains why a status-only check on a web app is rarely enough.
If you front Sentry with single sign-on at the proxy, use the service-token or basic-auth headers from the custom-headers guide, or the monitor will follow the redirect to your SSO login and stay green.
Step 3: an ingest canary (Pro)
The thing you actually care about is whether an SDK can get an event in. Ingest is a POST to /api/<project_id>/envelope/ authenticated by the DSN's public key, and HTTP monitors send no request body, so this is a job for a cron script and a heartbeat:
# every 10 minutes on any machine outside the Sentry host
*/10 * * * * SENTRY_DSN=https://[email protected]/CANARY_PROJECT_ID \
timeout 30 sentry-cli send-event -m "cronalert canary" --level info \
&& curl -fsS --max-time 10 --retry 3 https://cronalert.com/api/heartbeat/YOUR-TOKEN Create a dedicated canary project in Sentry so the events do not pollute a real one, and a heartbeat monitor with an expected interval of 10 minutes and a grace of 10 to 15 minutes. sentry-cli send-event exits non-zero when the server refuses or cannot be reached; the timeout 30 catches a hang, which is what an overloaded Relay looks like. If Relay cannot fetch project configuration (a known failure when the task workers fall behind), if Kafka is unhealthy, or if nginx is up and everything behind it is down, the send fails, the ping is skipped, and the heartbeat alerts. Cron job heartbeats is the general guide and grace periods explains the window.
Close the loop from the other side: in the canary project, create a Sentry alert rule that fires when the project receives no events for 30 minutes. Now Sentry tells you when the cron machine is broken, and CronAlert tells you when Sentry is. Note that the canary proves an event was accepted at ingest, not that it was processed into an issue; the alert rule covers the processing half.
Step 4: Relay, Snuba, and the workers from inside (Pro)
Relay has two health endpoints, documented at GET /api/relay/healthcheck/live/ (Relay is running and listening) and GET /api/relay/healthcheck/ready/ (Relay is authenticated with the upstream and operating normally); both return 200 with {"is_healthy": true} when fine. Snuba's API has /health on its port 1218, Symbolicator has a healthcheck subcommand, and the consumers and task workers use file-based checks. None of these is reachable from the internet in the stock setup, and you should not expose them. Check them from the host:
# /etc/cron.d/sentry-internal-health, every 5 minutes on the Sentry host
*/5 * * * * root cd /opt/sentry/self-hosted \
&& docker compose exec -T relay curl -fsS --max-time 5 http://127.0.0.1:3000/api/relay/healthcheck/ready/ >/dev/null \
&& docker compose exec -T snuba-api curl -fsS --max-time 5 http://127.0.0.1:1218/health >/dev/null \
&& curl -fsS --max-time 10 --retry 3 https://cronalert.com/api/heartbeat/YOUR-TOKEN Heartbeat interval 5 minutes, grace 10. If your Relay image lacks curl, run the check from the host against the container's published port, or use wget -qO-. For the Kafka consumers, the fastest honest signal is the ingest canary in step 3 plus Sentry's own status check for worker backlog; a consumer-lag heartbeat is possible with kafka-consumer-groups.sh --describe but is beyond most installs' appetite. Monitoring Docker self-hosted services covers the pattern of internal checks reporting via heartbeat, and message queue monitoring covers the Kafka side if you want it.
Step 5: the certificate
Every SDK validates the TLS certificate on your Sentry hostname, and most will not report events to a host with an expired one. SSL monitoring is included in every HTTP monitor on every plan, so the /_health/ monitor already alerts before the certificate on sentry.example.com expires. If you run Relay on a separate ingest hostname, give it its own monitor so its certificate is covered too. Let's Encrypt renewal monitoring covers the case where the renewal job is the thing that breaks.
Step 6: backups (Pro)
Self-hosted Sentry ships ./scripts/backup.sh, which produces a partial JSON backup: low-volume data such as users, organisations, projects, and server configuration, deliberately excluding events and issues. The documentation's full-backup route is volume-level, copying sentry-data, sentry-postgres, sentry-redis, sentry-kafka, sentry-clickhouse, and sentry-symbolicator, with the caveat that it is not the officially supported flow. Whichever you run, make it report:
# /etc/cron.daily/sentry-backup
set -euo pipefail
cd /opt/sentry/self-hosted
./scripts/backup.sh
# backup.sh writes into the sentry/ directory next to the config; ship it off the host:
rclone copy sentry/backup.json remote:sentry/$(date +%F)/
curl -fsS --max-time 10 --retry 3 https://cronalert.com/api/heartbeat/YOUR-TOKEN Heartbeat interval 1 day, grace a few hours. set -e makes any failing step abort before the curl, so a missed ping is a failed backup. Restores must go onto the same Sentry version on a fresh install, and restoring a partial backup deletes existing rows in those tables, so test restores on a scratch VM. Monitoring database backups goes deeper.
GlitchTip
GlitchTip is the lightweight option: PostgreSQL, a single web service (or separate web and worker), and optional Valkey/Redis, with the web service on port 8000 behind your proxy. It accepts Sentry SDKs through the same DSN and envelope endpoints, so everything in this guide applies with smaller numbers: monitor the login page with a keyword, run the same sentry-cli send-event canary against a GlitchTip DSN, and put the pg_dump in a heartbeat. GlitchTip answers /_health/ as well; confirm with curl -i on your install before relying on it. Its built-in uptime monitoring has the same limitation as Sentry's: it cannot watch itself.
Why not Sentry Crons or the uptime checker?
Sentry Crons monitors scheduled jobs through check-ins that travel the same ingest path as events, and recent self-hosted releases include an uptime-checker service for Sentry's uptime monitoring product. Both live inside the deployment they would be watching. When the host dies, nginx is misconfigured, or Kafka fills the disk, they stop too, and the alert that would have told you never leaves the box. Use them for your applications; keep one monitor for Sentry that does not depend on Sentry. The same argument applies to Uptime Kuma and Prometheus Alertmanager.
Reading an alert
| Alert | Likely cause | First thing to check |
|---|---|---|
/_health/ timeout or connection error | Host, Docker, or nginx down | docker compose ps; host disk and memory |
/_health/ 502 | nginx up, web container down or restarting (5-minute start period after upgrades) | docker compose logs web |
/_health/ 500 | Sentry up but reporting problems | The response body lists them; often Postgres, Redis, or worker backlog |
| Login keyword missing | Proxy serving another site, static assets failed to build | Open the page; docker compose logs nginx |
Ingest canary overdue, /_health/ green | Relay, Kafka, or consumers broken while the web UI works | docker compose logs relay; Kafka disk; worker backlog warning in the admin UI |
| Internal-health heartbeat overdue | Relay not ready (upstream auth or project config) or Snuba down | Run the cron line by hand |
| Certificate alert | Renewal stopped | Proxy or certbot logs |
| Backup heartbeat overdue | Cron not running, script failed, destination full | Run backup.sh by hand |
Frequently asked questions
Does /_health/ need authentication?
No. It exists for load balancers and the container healthcheck, both of which run unauthenticated.
Will a one-minute check load the server?
Not noticeably. /_health/ is a lightweight view, the login page is a template render, and the canary sends one tiny event every ten minutes. The internal checks run inside the Docker network.
Should the canary send a real exception or a message?
A message at info level, so it never triggers your real alert rules and is easy to filter. The point is that ingest accepted it, not what it says.
Can I monitor SaaS sentry.io the same way?
You can run the ingest canary against a sentry.io DSN to catch outages or quota exhaustion on your account, and third-party dependency monitoring covers watching vendors generally. The /_health/ and internal checks are for self-hosted only.
Which parts need a paid plan?
The /_health/ monitor and certificate monitoring are free. The login keyword check and the three heartbeats (ingest canary, internal health, backup) need Pro at $5/month, which is still less than the cheapest Sentry SaaS tier.
Get started
One free monitor on /_health/ ends the silent-failure problem; the ingest canary proves events are getting in. Create a free CronAlert account and add the health monitor now. Related reading: monitoring self-hosted GitLab, monitoring Loki, Tempo, and Alloy, uptime monitoring vs APM vs logging, monitoring behind a reverse proxy, and background worker monitoring.