How to Self-Host Healthchecks in 2026: Cron Pings, Alerts and Database Backups
Self-host Healthchecks with correct ports, persistent storage, HTTPS, secrets, backups and upgrade checks. Learn how to fix when cron jobs ping an internal URL.
Self-hosting Healthchecks becomes interesting at the first redeploy, not the first docker run. If cron jobs ping an internal URL or email workers are not running, Docker can still report a perfectly healthy process. The deployment below is organized around observable behavior: send start, success and failure pings from a test job, then omit a scheduled ping and receive the missing-job alert.
The intended job of Healthchecks is explicit: dead-man monitoring for cron jobs and background tasks. That description tells us what must stay public, what should remain private and what a backup has to reconstruct.
Back up the state Healthchecks cannot recreate
The standard Healthchecks container has no required application-data mount. Its recovery set is still explicit: the application database and notification configuration. Do not create an empty volume merely to make the deployment look stateful; preserve the exact image reference and reviewed configuration instead.
Rebuild Healthchecks on a blank host and run the acceptance transaction. Recovery passes when checks, schedules, integrations and ping keys return and an intentionally missing ping raises the expected alert. Any connected database or collaboration service follows its own application-consistent backup plan, while the replaceable web container is recreated from code. The Git-to-production deployment guide describes that reproducible boundary.
Keep a checksum or digest for the known-good image and retest after updates. For a stateless service, a successful rebuild is the restore test; for external state, the Healthchecks runbook must link to the separate owner and recovery procedure.
Build a replaceable Healthchecks container
Use a command that exposes every important choice. This baseline binds Healthchecks to host loopback, adds the known data mounts and supplies the first required setting. Add the reviewed connection settings for Postgres and working email delivery for production alerts; use private names for private services.
docker run -d \
--name healthchecks \
--restart unless-stopped \
-p 127.0.0.1:8000:8000 \
-e SECRET_KEY=replace-with-a-long-random-value \
-e SITE_ROOT=https://app.example.com \
-e ALLOWED_HOSTS=app.example.com \
-e DB=postgres \
-e DB_HOST=postgres.internal \
-e DB_NAME=healthchecks \
-e DB_USER=healthchecks \
-e DB_PASSWORD=replace-with-a-strong-database-password \
healthchecks/healthchecks:latest
Replace floating tags with a tested version or digest. After startup, inspect docker logs --tail 200 healthchecks and confirm the process listens on 8000. Then execute the Healthchecks acceptance action; a root-page response cannot prove the full scenario succeeds: send start, success and failure pings from a test job, then omit a scheduled ping and receive the missing-job alert.
What Healthchecks depends on
Draw three boundaries around Healthchecks: ingress to port 8000, durable state and supporting requirements. The container is replaceable, but the other two need explicit owners. The network contract for Healthchecks is Postgres and working email delivery for production alerts. Keep private endpoints on internal DNS, permit only required outbound calls and give Healthchecks a scoped service credential.
The diagram is complete when a clean client can send start, success and failure pings from a test job, then omit a scheduled ping and receive the missing-job alert. Capture timing and resource data for number of checks, grace periods, notification fan-out, email delivery and database writes. If the transaction fails, the first boundary that does not behave as documented identifies whether to investigate routing, local capacity or a supporting service.
Route Healthchecks without lying about HTTPS
Choose the final Healthchecks hostname before users save callbacks or client settings, then set SITE_ROOT and ALLOWED_HOSTS to the external HTTPS address. The platform route should terminate TLS once and target private port 8000.
Run the acceptance transaction externally. If the client never reaches Healthchecks, use the SSL validation checklist for DNS and certificate checks. If the request reaches Healthchecks but cron jobs ping an internal URL or email workers are not running, stop changing proxy redirects and inspect the application-specific boundary instead.
Evidence to collect before Healthchecks goes live
Create a small, disposable Healthchecks fixture and keep it for every release. The fixture should exercise the real workflow: send start, success and failure pings from a test job, then omit a scheduled ping and receive the missing-job alert. Record the image digest, external hostname, dependency address and the expected result so a later operator can repeat the test without interpreting this guide.
Run the fixture three times. First, use the fresh deployment. Second, replace the container without touching durable state. Third, restore the backup into an empty environment. The third run passes only when checks, schedules, integrations and ping keys return and an intentionally missing ping raises the expected alert. During each run, capture latency and resource use around number of checks, grace periods, notification fan-out, email delivery and database writes; this becomes the baseline for alerts rather than an arbitrary CPU percentage.
Finally, test the negative path deliberately: temporarily deny the test identity access to Postgres and working email delivery for production alerts. Confirm that Healthchecks fails visibly without corrupting state, restore the correct condition and repeat the successful transaction. A release record containing those four outcomes is stronger evidence than screenshots of a dashboard or a one-time curl response.
Failure drills for Healthchecks
Observe the work Healthchecks performs: number of checks, grace periods, notification fan-out, email delivery and database writes. Set limits with headroom for that work and avoid a liveness probe that competes with it. The operator check should still attempt to send start, success and failure pings from a test job, then omit a scheduled ping and receive the missing-job alert on a schedule.
For updates, remember that application migrations and worker configuration must be upgraded together so the web page does not hide broken alert delivery. Deploy the candidate against a recovered copy and repeat the known test. If cron jobs ping an internal URL or email workers are not running, use runtime logs and the actual network request to find which assumption changed.
Choose the Healthchecks trust boundary
Close the bootstrap window as soon as the first trusted administrator exists. Healthchecks's concrete trap is using a generated secret that changes on every restart; the safer boundary is to use a stable SECRET_KEY, restrict project membership and treat ping URLs as credentials.
Generate SECRET_KEY once, keep it out of Git and preserve it with the recovery manifest because changing it can invalidate encrypted or signed application state. Private networking should carry dependency credentials, and roles inside Healthchecks should grant the smallest useful action. Keep sensitive request bodies and provider responses out of routine logs.
A Dockup deployment still needs an Healthchecks acceptance test
Dockup can own the replaceable platform pieces: route traffic to port 8000, issue the domain and certificate, inject secrets, attach persistent storage and connect Healthchecks to managed or privately attached services. It can do this on Dockup infrastructure or on a server you attach.
The Healthchecks acceptance work remains explicit. After the one-click deployment, set SITE_ROOT and ALLOWED_HOSTS to the external HTTPS address, connect and test Postgres and working email delivery for production alerts and run this scenario: send start, success and failure pings from a test job, then omit a scheduled ping and receive the missing-job alert. That division is intentional: Dockup removes repetitive infrastructure setup without pretending that application roles, provider credentials or restore policy choose themselves.
Frequently asked questions
What does Healthchecks need for a production deployment?
Route the Healthchecks container on port 8000 through one HTTPS origin. The supporting network requirement is Postgres and working email delivery for production alerts. Do not call Healthchecks ready until you can send start, success and failure pings from a test job, then omit a scheduled ping and receive the missing-job alert.
Which Healthchecks data belongs in a backup?
The standard Healthchecks image has no required application-data mount. Preserve its deployment configuration and back up any connected state separately; recovery passes when checks, schedules, integrations and ping keys return and an intentionally missing ping raises the expected alert.
Does Healthchecks require HTTPS behind a reverse proxy?
Use HTTPS for the public Healthchecks origin and keep port 8000 on the internal route. Apply the Healthchecks setting correctly: set SITE_ROOT and ALLOWED_HOSTS to the external HTTPS address. For Healthchecks, HTTPS protects credentials or user content in transit and keeps origin-sensitive client behavior consistent.
How should a Healthchecks upgrade be tested?
Restore current Healthchecks state into an isolated deployment, apply the candidate version and repeat its acceptance transaction. Pay particular attention because application migrations and worker configuration must be upgraded together so the web page does not hide broken alert delivery. Keep the previous Healthchecks image until its data-migration and rollback boundary are understood.
