Deployment guide

Self-Host Healthchecks: Cron Job Monitoring on a VPS

Deploy on a VPS Cloud →

Tutorial

Self-Host Healthchecks: Cron Job Monitoring on a VPS

Self-hosting6 min read4 steps

A cron job that stops running makes no noise. The nightly backup fails, log cleanup stops, a supplier sync breaks — and nothing warns you, because nothing runs. Healthchecks is an open-source (BSD-3 licensed) tool that flips the problem around: each job sends a ping when it finishes, and Healthchecks alerts you when that ping does not arrive. This guide installs it as a single Docker container on a VPS, puts it behind an HTTPS reverse proxy and wires up a first cron job.

Contents· Why monitor what must run, not just what responds1/10
  1. 01Why monitor what must run, not just what responds
  2. 02What Healthchecks brings
  3. 03Healthchecks compared with common approaches
  4. 04Prerequisites
  5. 05Install Healthchecks with Docker Compose
  6. 06Keep sign-up closed
  7. 07Wire up your cron jobs
  8. 08Receive the alerts
  9. 09Troubleshooting
  10. 10One-click install from the ServOrbit Marketplace

Why monitor what must run, not just what responds

An uptime monitor checks that a service responds. It cannot see what happens between two requests: the 3 a.m. backup, invoice sending, a certificate renewal, the export to your accountant. These jobs fail silently — a full disk, an expired password, a server rebooted without its timer — and you notice on the day you need them.

Cron's MAILTO variable barely helps: it only sends a message if the job runs and writes to its output, and says nothing when the job never starts. Healthchecks starts from absence: every job has an expected schedule, and silence beyond the grace time triggers the alert.

What Healthchecks brings

  • Ping-based monitoring — each job calls a unique URL; a missing ping triggers the alert.
  • Cron or period schedules — a cron expression with time zone, or a simple interval, plus a grace time.
  • Run duration — /start and /fail pings measure duration and report explicit failures.
  • Exit codes and logs — the exit code and the tail of the command output, viewable in the dashboard.
  • More than twenty integrations — email, webhook, Slack, Discord, Matrix, ntfy, Telegram, Gotify, PagerDuty, Opsgenie…
  • Team and API — separate projects, invited members, status badges and a REST API to create checks from your scripts.

Healthchecks compared with common approaches

Scroll the table

HealthchecksCron MAILTOUptime monitor
Alert if the job never startsYesNoNo
Alert if the job failsYes, exit code or /failOnly if it writes outputNo
Run durationMeasuredNoNo
Machines with no open portYes, a simple outbound HTTPS callYesNo, the target must be reachable
Alert channelsMore than twenty integrationsLocal emailDepends on the tool
Central historyYes, per checkNoYes, for services

Prerequisites

An Ubuntu 24.04 VPS with 1 GB of RAM is enough: Healthchecks runs as a single container and stores its data in an SQLite database. You need Docker with the Compose plugin, and a domain name pointing to the VPS, with nginx and an HTTPS certificate: ping URLs and alert links are built on that address, and the application rejects requests sent to another name.

Ideally, install Healthchecks on a machine separate from the servers it watches: if it goes down with them, nobody will warn you.

Install Healthchecks with Docker Compose

  1. Prepare the .env file

    Create the application directory and a .env file holding the secret key and the public address. Replace hc.example.com with your domain.

    mkdir -p /opt/healthchecks && cd /opt/healthchecks
    cat > .env <<EOF
    SECRET_KEY=$(openssl rand -hex 24)
    SITE_ROOT=https://hc.example.com
    EOF
  2. Create the Compose file

    Put this docker-compose.yml in the same directory. The port is published on the loopback only, public sign-up is closed and the SQLite database lives in a volume.

    services:
      healthchecks:
        image: healthchecks/healthchecks:v5.0.1
        restart: unless-stopped
        ports:
          - "127.0.0.1:8102:8000"
        env_file: .env
        environment:
          DEBUG: "False"
          DB_NAME: /data/hc.sqlite
          REGISTRATION_OPEN: "False"
          GUNICORN_WORKERS: "2"
        volumes:
          - healthchecks-data:/data
    volumes:
      healthchecks-data: {}
  3. Start and create the administrator account

    On start, the container runs the migrations, then launches the web server and the alert-sending processes. Then create your account: the command asks for an email address and a password.

    docker compose up -d
    docker compose exec healthchecks ./manage.py createsuperuser
  4. Publish over HTTPS behind nginx

    Add a server block for your domain, with a Let's Encrypt certificate, and forward the origin headers: without X-Forwarded-Proto, login fails with a CSRF error.

    location / {
        proxy_pass http://127.0.0.1:8102;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

Keep sign-up closed

By default, Healthchecks accepts public sign-ups. REGISTRATION_OPEN: "False" closes them: only the accounts you create or invite into a project can log in. On a server exposed to the Internet, set this from the very first start.

Wire up your cron jobs

In the dashboard, create a check, enter the schedule — the job's cron expression and its time zone — then a grace time covering its normal duration. Copy the ping URL and append it to the job.

# crontab: backup at 3 a.m., ping only on success
0 3 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 --retry 5 -o /dev/null https://hc.example.com/ping/<uuid>

# variant: signal the start, then send the exit code
URL=https://hc.example.com/ping/<uuid>
curl -fsS -m 10 --retry 5 -o /dev/null "$URL/start"
/usr/local/bin/backup.sh > /tmp/backup.log 2>&1
curl -fsS -m 10 --retry 5 -o /dev/null --data-binary @/tmp/backup.log "$URL/$?"

The first form pings only on success: a failure shows up as a missing ping, hence an alert after the grace time. The second is more telling: /start measures the duration, the exit code sent in place of the ping reports failure immediately, and the output sent in the request body appears in the check's history. The -m 10 --retry 5 options keep a flaky network from blocking the job or faking an outage.

Receive the alerts

Under Integrations, add at least one channel you actually read, then attach it to your checks. Webhook, Slack (incoming webhook), Mattermost, ntfy and Gotify work without any server-side configuration. Other integrations need credentials declared in .env: a bot token for Telegram, an account and token for Matrix, an OAuth app for Discord.

Email alerts go through an SMTP server: add EMAIL_HOST, EMAIL_PORT, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD and DEFAULT_FROM_EMAIL to .env, then restart with docker compose up -d. The same settings serve team invitations and periodic reports.

Troubleshooting

The page returns a 400 error. The request does not arrive with the right host name: check that SITE_ROOT exactly matches the domain served by nginx, and that the location block forwards the Host header.

Login fails with a CSRF error. nginx does not forward X-Forwarded-Proto: the application thinks it receives HTTP while the browser speaks HTTPS. Add the header and reload nginx.

A check goes into alert although the job ran. The ping did not leave: run the curl command by hand from the machine concerned, check outbound HTTPS access, and lengthen the grace time if the job lasts longer than expected.

No email arrives. Without EMAIL_HOST, Healthchecks sends nothing and says so in its logs at start-up: check docker compose logs healthchecks.

One-click install from the ServOrbit Marketplace

The ServOrbit Marketplace installs Healthchecks on your VPS without a command line: it deploys the container with sign-up closed, creates the administrator account with a generated password, and publishes the application over HTTPS on a subdomain of your domain or on the VPS's free subdomain. The login and password appear in your client area; all that is left is to create your checks.

Your scheduled jobs under watch, on your own VPS

A ServOrbit Ubuntu 24.04 VPS with 1 GB of RAM is enough for Healthchecks. One-click install from the Marketplace, HTTPS on your domain or on the VPS's free subdomain, administrator account created for you.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab