Healthchecks
Overview
Healthchecks is an open source, self-hosted monitoring service for cron jobs and scheduled tasks. It works as a dead man's switch for the jobs that run in the background where nobody is watching.
Every job, backup script, certificate renewal or batch process gets its own unique ping URL and requests it when it finishes. If a ping does not arrive on schedule, Healthchecks marks the check late or down and notifies you through email, Slack, Discord, Telegram, PagerDuty, webhooks and dozens of other integrations. Unlike active probing, which watches a service from the outside, this passive model catches the jobs that silently stopped running.
This SecureAnyCloud image serves Healthchecks 4.2 behind an nginx reverse proxy on a hardened Ubuntu 24.04 LTS base that is patched at build time. The Django application runs under gunicorn on the loopback interface (127.0.0.1:8000), nginx serves it on port 80, and a second service delivers alerts the moment a check changes state.
The image is secure by default. It ships with no administrator account and no shared password, so you create your own admin with a single command.
What you get:
- Healthchecks 4.2 served by gunicorn behind nginx, managed by systemd
- The Healthchecks web UI and ping API on
:80 - No default administrator: you create your own admin with one command
- A dedicated alert-delivery daemon (
healthchecks-sendalerts.service) for notifications - The application server bound to
127.0.0.1:8000only, never exposed directly to the network - A dedicated Azure data disk at
/opt/healthchecksfor the application, the Python venv and the SQLite database nginx.service,healthchecks.serviceandhealthchecks-sendalerts.serviceas enabled systemd units- 24/7 SecureAnyCloud support
Prerequisites
You need an active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region.
Standard_B2s (2 vCPU / 4 GiB RAM) is a sensible starting size. Size up as your check count grows.
Configure the network security group (NSG) to allow inbound 22/tcp from your management network and 80/tcp for the web UI. Healthchecks serves plain HTTP on port 80, so terminate TLS in front of it with your own domain.
The application server is never exposed directly. It listens on 127.0.0.1:8000 only, so port 8000 stays off the network.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Healthchecks by SecureAnyCloud, and select Create.
On the Basics tab, pick your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22) and HTTP (80).
Review the dedicated data disk on the Disks tab. Then select Review + create and Create.
Step 2 - Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:healthchecks-ubuntu-24-04:default:latest
az vm create \
--resource-group <your-rg> \
--name healthchecks \
--image sac1647283583153:healthchecks-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku StandardThen open port 80 for the web UI:
az vm open-port --resource-group <your-rg> --name healthchecks --port 80Step 3 - Confirm the services are running
SSH in as azureuser and confirm that nginx, the Healthchecks application and the alert daemon are active. The application server listens only on 127.0.0.1:8000, while nginx serves the web UI on port 80.
systemctl is-active nginx healthchecks healthchecks-sendalertssudo ss -tlnp | grep -E ':80 |:8000 ' | sed 's/ */ /g'Step 4 - The first-boot info note
Healthchecks ships with no administrator account. On the first boot of every VM, a one-shot service resolves the instance address and sets it as the site root. It also writes an instance info note that records the site URL and the create-admin instruction.
The note contains no password, because you set your own. Read it with sudo:
sudo cat /root/healthchecks-credentials.txtConfirm from the command line that the web UI is healthy. The sign-in page returns 200, and the root path redirects unauthenticated visitors to it (302):
curl -s -o /dev/null -w 'GET /accounts/login/ -> HTTP %{http_code}\n' http://127.0.0.1/accounts/login/Step 5 - Create your administrator
The image ships with no user, so you create the first administrator with a single command over SSH. Run the Healthchecks management wrapper and follow the prompts to set your own email and password:
# Enter your own <your-email> and a strong password when prompted.
sudo /usr/local/sbin/hc-manage createsuperuserThen browse to http://<vm-public-ip>/. Unauthenticated visitors are redirected to the sign-in page. Enter the email and password you just chose.
There are no default credentials to change, because none ship.
Step 6 - Your checks dashboard
After you sign in, you land on My Checks. This dashboard lists every check with its current up, late or down state, its schedule and when its last ping arrived. A fresh instance starts empty. Your monitored jobs appear here as you add them.
Step 7 - Create a check and copy its ping URL
Select Add Check, give it a name, and set its Period (how often the job runs) and Grace (how late a ping may be before the check goes down). Open the check to see its unique ping URL and the log of received pings. Copy that URL into your job.
Step 8 - Wire the ping URL into a cron job
Add a request to the check's ping URL at the end of the job you want to monitor. When the job finishes successfully, it pings the URL. If a ping does not arrive within the period plus grace, Healthchecks marks the check down and alerts you. For a nightly backup, for example:
0 2 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 --retry 5 https://<vm-public-ip>/ping/<your-check-uuid>You can also signal start, success and failure explicitly by appending /start or /fail to the ping URL. This lets Healthchecks measure job duration and catch jobs that begin but never finish.
Step 9 - Notification integrations
Open Notification Methods (the Integrations page) to choose where alerts are delivered. Healthchecks supports email, Slack, Discord, Telegram, PagerDuty, Opsgenie, generic webhooks and dozens more.
Add a method and assign it to your checks. The healthchecks-sendalerts daemon then sends alerts the moment a check changes state.
Step 10 - Verify the stack
Confirm that the services are enabled, that the web UI responds through nginx, and that the dedicated data disk is mounted:
systemctl is-enabled nginx healthchecks healthchecks-sendalertscurl -s -o /dev/null -w 'GET /accounts/login/ -> HTTP %{http_code}\n' http://127.0.0.1/accounts/login/findmnt -no SOURCE,TARGET,FSTYPE /opt/healthchecksStep 11 - Where your data lives
The Django application, its Python virtual environment and the SQLite database all live on a dedicated Azure data disk mounted at /opt/healthchecks, separate from the OS disk. This disk is captured into the image and re-provisioned on every VM, and you can snapshot and resize it independently.
Confirm the mount and the free space:
df -h /opt/healthchecks | tail -1sudo du -sh /opt/healthchecks/app /opt/healthchecks/venv /opt/healthchecks/dataStep 12 - Enable email notifications
To send email alerts, add your SMTP relay settings to the configuration file and restart the two application services. The file already documents the keys. Append your own values, and never commit real credentials:
# /opt/healthchecks/hc.env EMAIL_HOST=smtp.your-provider.example EMAIL_PORT=587 EMAIL_HOST_USER=<your-email> EMAIL_HOST_PASSWORD=<your-password> DEFAULT_FROM_EMAIL=healthchecks@your-domainThen apply the change:
sudo systemctl restart healthchecks healthchecks-sendalertsSecurity notes
- Healthchecks serves plain HTTP on port 80. Put it behind your own TLS-terminating reverse proxy or an Azure Application Gateway with a certificate for your domain, and restrict port 80 in the NSG to trusted networks. A common approach is
certbotwith your-domain on a front-end proxy. - The application server listens on
127.0.0.1:8000only and is never exposed directly. Only nginx on port 80 faces the network. - The image ships with no administrator and no shared password. You create the first admin with one command, so there are no default credentials to rotate. Open web sign-up stays disabled (
REGISTRATION_OPEN=False) until you choose to enable it. - Keep the VM patched. The image is patched at build time and ships with unattended security upgrades enabled.
Support
SecureAnyCloud images come with 24/7 support. If you have questions about this Healthchecks image or need help with your deployment, contact us through the SecureAnyCloud website.
Deploy on Azure
Launch Healthchecks on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com
Overview
Healthchecks is a dead man's switch for your scheduled tasks. Every cron job, backup script, certificate renewal or batch process gets its own unique ping URL and requests that URL when it completes. If a ping does not arrive on schedule, Healthchecks flags the check as late and then down. It notifies you through email, Slack, Discord, Telegram, PagerDuty, webhooks and dozens of other integrations. Unlike active uptime probing, this passive model catches the jobs that silently stopped running.
This image runs Healthchecks bare metal behind nginx. The Django application is served by gunicorn on the loopback interface. The alert-delivery daemon (the sendalerts management command) runs as its own systemd service, so notifications are dispatched the moment a check changes state.
What you get:
- SQLite on a dedicated data volume, the upstream-supported default for single-node deployments, so there is no separate database server to manage
- An administrator account and a fresh Django secret key, generated on the first boot of every deployed instance
- Credentials that are never shared: two instances launched from the same Amazon Machine Image never share credentials
- The administrator email and password written to
/root/healthchecks-credentials.txtwith mode 0600, so that only the root user can read them - Open registration disabled, so only the administrator can sign in or invite further users
- No checks and no shared credentials in the image
- The Healthchecks application, its Python environment and the SQLite database under
/opt/healthcheckson a dedicated EBS volume, separate from the operating system disk, so you can resize and snapshot application data independently
Prerequisites
Before you deploy this image, have the following ready:
- An Amazon Web Services account where you can launch EC2 instances
- IAM permissions to launch instances, create security groups, and subscribe to AWS Marketplace products
- An EC2 key pair in the target Region for SSH access to the instance
- A VPC and subnet in the target Region, with a security group allowing inbound port 22 from your management network and inbound ports 80 and 443 from the networks your users and your monitored jobs will reach Healthchecks on
- The AWS CLI (version 2) installed locally if you plan to deploy from the command line
Launch the Instance from the AWS Marketplace
Sign in to the AWS Management Console, open the EC2 service, and select Launch instance. Under Application and OS Images, choose AWS Marketplace AMIs and search for Healthchecks. Select the SecureAnyCloud listing and choose Select, then Continue on the subscription summary.
Pick an instance type of m5.large. The application is light, so a smaller instance also works for lab deployments. Choose your EC2 key pair under Key pair (login).
Under Network settings, select your VPC and subnet. Create or select a security group that allows inbound port 22 from your management network and inbound ports 80 and 443 from the networks your users and monitored jobs use. Leave the root volume at the default size.
Select Launch instance. First-boot initialization takes under a minute after the instance state becomes Running and the status checks pass.
Launch the Instance from the AWS CLI
The following command launches an instance from the SecureAnyCloud Healthchecks Marketplace AMI into an existing subnet and security group. Replace <ami-id> with the AMI ID shown on the Marketplace listing and <key-name> with your EC2 key pair name. Replace <subnet-id> with your subnet ID and <security-group-id> with a security group that opens ports 22, 80 and 443 as described above.
aws ec2 run-instances \
--image-id <ami-id> \
--instance-type m5.large \
--key-name <key-name> \
--subnet-id <subnet-id> \
--security-group-ids <security-group-id> \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=healthchecks-01}]'On success, the command prints a JSON document. Note the instance ID, then retrieve the public address once the instance is running with aws ec2 describe-instances --instance-ids <instance-id> --query "Reservations[].Instances[].PublicIpAddress" --output text.
Connect and Retrieve Initial Credentials
Connect over SSH with the key pair you selected and the public IP address from Step 2. The SSH login user depends on the operating system of the AMI variant you launched.
AMI variant: Healthchecks 4.2 on Ubuntu 24.04. SSH login user: ubuntu.
ssh -i <key.pem> ubuntu@<public-ip>The per-instance administrator credentials are written to a root-only file. Read it with sudo:
sudo cat /root/healthchecks-credentials.txtThe file lists the administrator email address, the administrator password and the URL of the web interface. Store these somewhere safe.
First Login to the Healthchecks Web Interface
Open a web browser and go to http://<instance-public-ip>/. You are redirected to the sign-in page. Enter the administrator email address and password from /root/healthchecks-credentials.txt and select Log In.
On first boot, the instance configured its own public address as the site root. The web interface and every generated ping URL therefore use the instance's launch address immediately. Open registration is disabled. Add new team members by invitation from Settings within a project, not by self sign-up.
You can verify the security posture from the SSH session. The sign-in page is served without authentication, while the dashboard requires a session and redirects to the sign-in page:
# The sign-in page is served without authentication (200)
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/accounts/login/
# Without a session the dashboard redirects to the sign-in page (302)
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/The generated administrator credentials also work for a scripted sign-in. The following command performs the same CSRF-correct login round trip that a browser does and prints 302, the redirect into the dashboard:
JAR=$(mktemp)
CSRF=$(curl -s -c "$JAR" http://127.0.0.1/accounts/login/ | grep -o 'name="csrfmiddlewaretoken" value="[^"]*"' | head -1 | cut -d'"' -f4)
curl -s -b "$JAR" -c "$JAR" -o /dev/null -w '%{http_code}\n' \
--data-urlencode "csrfmiddlewaretoken=$CSRF" \
--data-urlencode "action=login" \
--data-urlencode "email=admin@example.com" \
--data-urlencode "password=<HEALTHCHECKS_ADMIN_PASSWORD>" \
-e http://127.0.0.1/accounts/login/ http://127.0.0.1/accounts/login/ rm -f "$JAR"Change the Administrator Email and Password
Change the administrator email address to a real mailbox, and rotate the generated password. Sign in, open Account in the top right, then select Account Settings. Use Change Email Address to set your real address and Set Password to choose a new password.
The credentials file under /root is a bootstrap record only. It is not consulted after first boot, so you do not need to mirror changes from the web interface there.
Create Your First Check and Wire It Into a Job
Select Add Check on the checks page. Give the check a name (for example Nightly database backup). Set its Period, which is how often you expect the job to run, and its Grace Time, which is how late a ping may be before the check is flagged. Each check gets a unique ping URL of the form http://<instance-public-ip>/ping/<uuid>.
Append a request to that URL at the end of the job you want to monitor. In a crontab, it looks like this:
# m h dom mon dow command 15 2 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 --retry 5 -o /dev/null <ping-url>The && ensures the ping is only sent when the job succeeded, -m 10 bounds the request time and --retry 5 rides out transient network blips.
The check turns green on the first ping. From then on, a missing ping flips it to late, then down, and notifications fire. The check's detail page shows the full ping log with the source address of every ping received.
Healthchecks also supports signaling explicit failures (/ping/<uuid>/fail) and job start times (/ping/<uuid>/start) for measuring run duration. You can attach log output in the request body. Select the Usage Examples button on any check's detail page for ready-made snippets in bash, Python, PowerShell and more.
Notification Integrations
Open Integrations to connect notification channels. Healthchecks supports email, Slack, Discord, Telegram, Microsoft Teams, PagerDuty, Opsgenie, Pushover, ntfy, Signal, SMS, generic webhooks and many more. You can route each check to any subset of the configured integrations. A failing backup can page the on-call engineer, for example, while a failing report job only posts to a chat channel.
Email notifications require an SMTP relay, which is not preconfigured in the image. To enable it, append your SMTP settings to /opt/healthchecks/hc.env, for example using Amazon SES SMTP credentials:
EMAIL_HOST=email-smtp.us-east-1.amazonaws.com EMAIL_PORT=587 EMAIL_HOST_USER=<your-ses-smtp-user> EMAIL_HOST_PASSWORD=<your-ses-smtp-password> EMAIL_USE_TLS=True DEFAULT_FROM_EMAIL=healthchecks@your-domain.exampleAfter you edit the file, run sudo systemctl restart healthchecks healthchecks-sendalerts so that both the web application and the alert daemon pick up the new settings.
Services and Operations
The image runs three systemd units. nginx terminates HTTP on port 80 and reverse-proxies the gunicorn application server on 127.0.0.1:8000, and the sendalerts daemon delivers notifications. Confirm that all three are active:
systemctl is-active nginx healthchecks healthchecks-sendalertsThe alert daemon logs every delivery attempt. Review its recent activity with:
sudo journalctl -u healthchecks-sendalerts --no-pager -n 10The application is configured entirely through environment variables in /opt/healthchecks/hc.env. The file is rewritten on first boot with the per-instance secret key and site root. Inspect the operational settings at any time:
sudo grep -E '^(SITE_ROOT|REGISTRATION_OPEN|DB_NAME)=' /opt/healthchecks/hc.envRun Django management commands through the bundled wrapper, which loads the same environment and drops to the application user: sudo /usr/local/sbin/hc-manage <command>. After any change to hc.env, restart both application services with sudo systemctl restart healthchecks healthchecks-sendalerts.
If you place Healthchecks behind a custom domain or a load balancer, update SITE_ROOT in /opt/healthchecks/hc.env to that address so that generated ping URLs and links are correct. Then restart the services.
Enable HTTPS with Let's Encrypt
Serve the site over HTTPS so that session cookies and ping requests cannot be intercepted. The image ships with nginx, which certbot can configure automatically. The following commands assume a DNS record points your fully qualified domain name at the instance's public IP address:
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d checks.your-domain.example \
--non-interactive --agree-tos -m you@your-domain.example \
--redirectAfter certbot finishes, point Healthchecks at the HTTPS URL so that generated ping URLs use it. Also tell Django that the original request scheme arrives in the forwarded header:
sudo sed -i 's|^SITE_ROOT=.*|SITE_ROOT=https://checks.your-domain.example|' /opt/healthchecks/hc.env echo 'SECURE_PROXY_SSL_HEADER=HTTP_X_FORWARDED_PROTO,https' | sudo tee -a /opt/healthchecks/hc.env
sudo systemctl restart healthchecks healthchecks-sendalertsExisting checks keep working. Ping URLs accept requests on both the old and new addresses as long as nginx serves them, but freshly displayed URLs use the new site root.
Backups and Maintenance
All application state lives in the SQLite database on the data volume. Take a consistent online copy with SQLite's backup command, then ship it off the instance. The example removes the local copy again:
sudo sqlite3 /opt/healthchecks/data/hc.sqlite ".backup '/tmp/healthchecks-backup.sqlite'" ls -l /tmp/healthchecks-backup.sqlite
sudo rm -f /tmp/healthchecks-backup.sqliteCopy the backup to an Amazon S3 bucket or another object store on your own schedule, or take EBS snapshots of the data volume. The application and its database live on their own volume, so a snapshot of that single volume captures everything.
For kernel and package updates, Ubuntu's unattended-upgrades is enabled, so security patches apply automatically. To update Healthchecks itself, follow the upgrade steps in the official documentation at https://healthchecks.io/docs/self_hosted/.
Growing Beyond a Single Instance
For larger deployments, Healthchecks supports PostgreSQL and MySQL as alternative database engines. Point the DB, DB_HOST, DB_NAME, DB_USER and DB_PASSWORD variables in /opt/healthchecks/hc.env at an Amazon RDS instance and restart the services.
Put the web tier behind an Application Load Balancer and update SITE_ROOT accordingly. The sendalerts daemon can run on a separate instance against the shared database, and Amazon SES provides a scalable SMTP backend for email notifications. Each of these is documented at https://healthchecks.io/docs/.
Screenshots
The Healthchecks sign-in page, served on first boot with a per-instance administrator password and open registration disabled.
The checks dashboard showing monitored jobs with their ping URLs, schedules and current state: up, late, down and new at a glance.
A check's detail view with its unique ping URL, how-to-ping snippets, current status and the event log of received pings.
Support
SecureAnyCloud provides 24/7/365 expert technical support for this image. Response is guaranteed within 24 hours, with a one hour average for critical issues. Email sales@secureanycloud.com to reach the support team.
Deploy on AWS
Launch Healthchecks on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com