← Blogue

Auto-héberger Healthchecks.io pour surveiller vos tâches cron

Railyard

19 juillet 2026 · 12 min de lecture

Suivez ça sur votre propre serveur. Commencer gratuitement, sans carte

Une tâche cron qui s’arrête de tourner en silence ne peut pas vous dire qu’elle s’est arrêtée. Le script ne s’exécute tout simplement plus une nuit, rien ne plante, rien ne journalise d’erreur, et trois semaines plus tard vous découvrez durement que les sauvegardes n’ont plus tourné depuis que le noyau du serveur a été mis à jour et que le démon cron n’est jamais revenu. Healthchecks.io est un dead man’s switch conçu exactement pour ça : chacune de vos tâches planifiées envoie un ping vers une URL unique quand elle se termine, et si ce ping n’arrive pas selon l’horaire que vous avez indiqué à Healthchecks, il vous alerte par courriel, Slack, un webhook, ou toute autre intégration qu’il prend en charge. Ce guide met en place une instance auto-hébergée sur votre propre serveur avec Postgres, le HTTPS via Caddy, et un vrai courriel d’alerte, puis montre le raccourci en un clic si vous préférez sauter la configuration manuelle.

Ce qu’il vous faut

  • Un serveur. Un Hetzner CX23 (2 vCPU, 4 Go de mémoire, 40 Go NVMe) à 5,99 €/mois hors TVA suffit pour Healthchecks, son propre Postgres et un reverse proxy tournant ensemble sur une seule machine, pour un usage personnel ou le volume de tâches cron d’une petite équipe.
  • Un nom de domaine (ou un sous-domaine d’un domaine que vous possédez déjà), avec accès à ses paramètres DNS.
  • Docker et le plugin Docker Compose installés sur le serveur (couvert à l’étape 2).
  • Un compte SMTP ou un relais par lequel envoyer du courriel, puisque les courriels d’alerte sont tout l’intérêt de faire tourner ceci.
  • Un terminal et un accès SSH au serveur.

Étape 1 : créer le serveur et pointer un domaine vers lui

Déployez un serveur Ubuntu 24.04 sur le plan CX23 de Hetzner (2 vCPU, 4 Go de mémoire, 40 Go NVMe, 5,99 €/mois hors TVA). Notez son adresse IP publique, puis ajoutez votre clé SSH à la création ou immédiatement après, pour ne pas dépendre d’un mot de passe.

Dans les paramètres DNS de votre domaine, ajoutez un enregistrement A pointant un sous-domaine, par exemple checks.votredomaine.com, vers cette IP :

Type: A
Name: checks
Value: <l'IP de votre serveur>
TTL: 300

Confirmez que ça résout avant de poursuivre :

dig checks.votredomaine.com +short

Ça devrait afficher l’IP de votre serveur, pas une ancienne et pas rien du tout. Pendant que vous y êtes, verrouillez le pare-feu pour ne laisser que ce dont cette pile a besoin :

ufw allow OpenSSH
ufw allow 80/tcp
ufw allow 443/tcp
ufw enable

Étape 2 : installer Docker

Connectez-vous en SSH au serveur et installez Docker Engine ainsi que le plugin Compose :

curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER

Déconnectez-vous et reconnectez-vous pour que le changement de groupe prenne effet, puis vérifiez que les deux composants sont bien présents :

docker --version
docker compose version

Étape 3 : organiser le projet et ses secrets

Créez un répertoire pour la pile :

mkdir -p ~/healthchecks && cd ~/healthchecks

Générez une clé secrète aléatoire et un mot de passe de base de données. Healthchecks utilise SECRET_KEY pour signer les sessions, donc une valeur faible ou réutilisée est un vrai problème, pas une formalité :

openssl rand -base64 32
openssl rand -base64 24

Créez .env avec les clés de configuration propres au projet, remplies pour votre domaine :

# .env
DOMAIN=checks.votredomaine.com
DB=postgres
DB_HOST=db
DB_PORT=5432
DB_NAME=healthchecks
DB_USER=postgres
DB_PASSWORD=<le mot de passe que vous avez généré>
ADMINS=vous@votredomaine.com
DEFAULT_FROM_EMAIL=healthchecks@votredomaine.com
SITE_ROOT=https://checks.votredomaine.com
SITE_NAME=Healthchecks
PING_ENDPOINT=https://checks.votredomaine.com/ping/
REGISTRATION_OPEN=False
SECRET_KEY=<la clé que vous avez générée>
GUNICORN_WORKERS=4
GUNICORN_PORT=8000

Quelques-unes de ces valeurs comptent plus qu’elles n’en ont l’air :

  • SITE_ROOT est l’URL HTTPS complète de votre instance, pas juste le domaine nu. Healthchecks s’en sert pour construire chaque lien qu’il vous affiche ou vous envoie par courriel, y compris les URL de ping elles-mêmes.
  • PING_ENDPOINT devrait correspondre à SITE_ROOT avec /ping/ ajouté. C’est un paramètre séparé parce que certaines installations servent le trafic de ping depuis un chemin différent et plus léger que l’interface principale ; pour cette configuration à un seul conteneur, les deux partagent le même domaine.
  • REGISTRATION_OPEN=False ferme l’inscription publique sur votre instance. Elle est ouverte par défaut, ce qui est le seul paramètre de ce fichier qu’il ne faut pas oublier, puisqu’une page d’inscription ouverte sur un domaine public est une invitation pour des inconnus à créer des comptes sur votre serveur.
  • DOMAIN est une seconde forme, en domaine nu, de la même valeur, utilisée par le Caddyfile à l’étape 5. Healthchecks lui-même ne lit jamais DOMAIN ; il lit SITE_ROOT.

Il vous faudra aussi des paramètres SMTP pour que Healthchecks puisse réellement vous envoyer des courriels d’alerte, puisqu’un outil de surveillance qui ne peut pas vous envoyer de courriel quand une tâche se tait ne fait pas son travail. Ajoutez EMAIL_HOST, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD, EMAIL_PORT et EMAIL_USE_TLS au même fichier .env, pointés vers le fournisseur SMTP ou le relais que vous utilisez déjà. La documentation de configuration auto-hébergée du projet lui-même couvre chaque variable liée au courriel en détail si votre fournisseur a besoin de plus que les bases.

Fixez les permissions pour que seul votre utilisateur puisse lire le fichier :

chmod 600 .env

Étape 4 : écrire le fichier Docker Compose

Le docker/docker-compose.yml propre à Healthchecks, dans le dépôt GitHub du projet, est écrit pour construire l’image localement depuis docker/Dockerfile et publier le port 8000 directement sur l’hôte, ce qui est la bonne forme pour le faire tourner sur votre propre machine mais pas pour un serveur exposé à Internet. Cette configuration l’adapte de trois façons : elle utilise l’image pré-construite healthchecks/healthchecks plutôt que de construire depuis la source, elle fixe une étiquette de version exacte plutôt que latest pour qu’un redémarrage non surveillé ne puisse pas vous faire glisser silencieusement vers une nouvelle version, et elle ne publie pas du tout le port 8000 sur l’hôte, puisque Caddy le dessert à sa place.

Créez docker-compose.yml :

services:
  db:
    image: postgres:17-alpine
    restart: unless-stopped
    volumes:
      - db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=${DB_NAME}
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d ${DB_NAME}"]
      interval: 5s
      timeout: 5s
      retries: 10
    networks:
      - healthchecks-net

  web:
    image: healthchecks/healthchecks:v5.0.1
    restart: unless-stopped
    env_file:
      - .env
    depends_on:
      db:
        condition: service_healthy
    networks:
      - healthchecks-net

  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    environment:
      - DOMAIN=${DOMAIN}
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      - web
    networks:
      - healthchecks-net

networks:
  healthchecks-net:

volumes:
  db-data:
  caddy_data:
  caddy_config:

postgres:17-alpine remplace le postgres:16 nu de l’exemple du projet lui-même ; les deux fonctionnent bien, 17 est simplement la version la plus récente au moment d’écrire ceci. POSTGRES_USER est laissé non défini volontairement, ce qui fait que Postgres utilise par défaut un utilisateur postgres, correspondant à DB_USER=postgres dans .env sans le répéter. Seul Caddy publie des ports vers l’hôte ; db et web ne sont joignables que sur le réseau Docker interne.

Étape 5 : écrire le Caddyfile et tout démarrer

Créez Caddyfile dans le même répertoire :

{$DOMAIN} {
	reverse_proxy web:8000
}

Démarrez la pile :

docker compose up -d

Observez Caddy obtenir un certificat :

docker compose logs -f caddy

Vous devriez voir une ligne mentionnant un certificat obtenu pour votre domaine, puis https://checks.votredomaine.com devrait se charger avec un cadenas valide, sans avertissement.

Étape 6 : configuration du premier lancement et fermeture de l’inscription

Visiter votre domaine pour la première fois affiche la propre page d’inscription de Healthchecks. Il n’y a pas d’étape CLI séparée pour créer un compte administrateur ici ; le premier compte que vous enregistrez est celui que vous utiliserez pour gérer les vérifications, les projets et les intégrations par la suite. Enregistrez-le maintenant, connectez-vous, et créez votre première vérification pour confirmer que les pings l’atteignent.

Une fois ce compte créé, assurez-vous que REGISTRATION_OPEN=False est bien défini dans .env (c’est déjà le cas depuis l’étape 3) et redéployez le service web pour que le paramètre prenne effet si vous avez changé quelque chose :

docker compose up -d web

À partir de ce moment, visiter la page d’inscription sur votre instance affiche un message d’inscription fermée plutôt qu’un formulaire fonctionnel, donc personne d’autre ne peut créer de compte sur votre serveur.

Étape 7 : sauvegarder la base de données et la clé secrète

La base de données contient chaque projet, vérification, intégration et historique de ping que vous avez. Un dump Postgres quotidien, conservé hors du serveur :

docker compose exec -T db pg_dump -U postgres healthchecks > "healthchecks-backup-$(date +%F).sql"

Mettez ça dans une tâche cron (ironiquement, une que vous pourriez faire surveiller par Healthchecks lui-même) et copiez le résultat ailleurs que sur ce serveur, puisqu’une sauvegarde qui ne vit que sur le serveur qu’elle sauvegarde ne survit pas au mode de défaillance qui détruit réellement un serveur.

Sauvegardez aussi .env, conservé séparément et avec un accès contrôlé. Son SECRET_KEY signe chaque session que Healthchecks émet ; le perdre signifie simplement que tout le monde est déconnecté et doit se reconnecter, ce qui est un désagrément mineur, mais une copie divulguée de cette clé permet à quelqu’un d’autre de forger une session, alors traitez-la avec le même soin que le mot de passe de la base de données.

Étape 8 : mettre à jour proprement

Consultez les notes de version sur le dépôt GitHub du projet entre l’étiquette que vous faites tourner et celle vers laquelle vous passez, puis changez la version dans docker-compose.yml :

image: healthchecks/healthchecks:<new-version>

Récupérez et redémarrez seulement ce service :

docker compose pull web
docker compose up -d web

Vérifiez les journaux pour un démarrage propre et une étape de migration réussie :

docker compose logs web

L’image pré-construite est censée exécuter automatiquement toute migration de base de données en attente dans le cadre de son propre démarrage, donc des journaux propres sans erreur de migration est ce que vous recherchez ; si quelque chose échoue, votre sauvegarde Postgres de l’étape 7 est le chemin de retour vers la version précédente.

Le raccourci : avec Railyard

Tout ce qui précède, une instance Healthchecks appuyée sur Postgres avec une clé secrète générée et le HTTPS déjà en place, existe aussi comme gabarit en un clic :

https://app.railyard.run/deploy?template=healthchecks

Railyard le construit directement depuis le docker/Dockerfile du propre dépôt du projet, provisionne automatiquement une base de données Postgres gérée pour lui, et exécute la propre commande de migration du projet comme étape de release avant chaque déploiement, le même python manage.py migrate que vous lanceriez à la main autrement. Il définit aussi ALLOWED_HOSTS=* et génère un vrai SECRET_KEY pour vous, donc il n’y a aucun secret à inventer et coller vous-même.

Si vous préférez partir de Railyard en général avant de décider pour Healthchecks spécifiquement, le guide de démarrage rapide couvre les bases, bouton de déploiement explique le format de lien ?template= si vous voulez en intégrer un ailleurs, et bases de données et sauvegardes couvre le fonctionnement des sauvegardes à travers les gabarits en général, pas seulement celui-ci. Le plan Free de Railyard couvre un serveur plus un serveur de base de données et trois applications sans carte requise, Pro est à 19 $/mois par équipe, et Team est à 49 $/mois par équipe.

Dépannage

Les courriels d’alerte n’arrivent jamais. C’est presque toujours des paramètres SMTP manquants ou incorrects, pas un bogue de Healthchecks. Vérifiez que EMAIL_HOST, EMAIL_HOST_USER, EMAIL_HOST_PASSWORD, EMAIL_PORT et EMAIL_USE_TLS sont tous définis dans .env et que le compte qu’ils décrivent peut réellement envoyer du courriel ; plusieurs fournisseurs bloquent le premier envoi depuis un nouveau serveur ou une nouvelle IP tant qu’il n’est pas vérifié. Un outil de surveillance dont le propre chemin d’alerte est brisé échouera silencieusement à la seule tâche pour laquelle il existe, alors envoyez-vous un échec de test et confirmez que le courriel arrive vraiment avant de compter là-dessus pour quoi que ce soit de réel.

Les URL de ping affichées dans l’interface pointent vers le mauvais domaine. SITE_ROOT ne correspond pas à la véritable adresse HTTPS depuis laquelle vous servez l’application. Corrigez-le dans .env et redéployez web ; chaque URL de ping que Healthchecks vous a déjà montrée ou mise dans un courriel a été construite à partir de l’ancienne valeur et doit être recopiée depuis l’interface après la correction.

Bad Request (400) au chargement du site. ALLOWED_HOSTS n’inclut pas le domaine que vous visitez. La configuration ici le fixe à * via le traitement par défaut du projet lui-même, donc ça n’apparaît habituellement que si vous avez changé ce comportement ; vérifiez qu’il correspond à votre véritable domaine si c’est le cas.

Connexion Postgres refusée, ou le conteneur web redémarre sans arrêt. Vérifiez d’abord docker compose logs db pour l’erreur réelle. Un mot de passe mal assorti entre DB_PASSWORD dans .env et celui avec lequel Postgres a été initialisé est la cause la plus fréquente, généralement en modifiant .env après que le volume db-data existe déjà ; Postgres ne lit POSTGRES_PASSWORD qu’au tout premier démarrage avec un répertoire de données vide, donc un changement ultérieur nécessite soit un volume neuf, soit un changement de mot de passe directement dans la base pour que ça corresponde.

Le HTTPS n’est jamais émis, ou le navigateur affiche un avertissement de certificat. Lancez dig checks.votredomaine.com +short et confirmez qu’il retourne l’IP de ce serveur. Let’s Encrypt a aussi besoin que le port 80 soit joignable depuis Internet pour son étape de validation, donc vérifiez que ufw status montre les ports 80 et 443 ouverts, pas seulement 443.

FAQ

Ai-je besoin de Postgres, ou SQLite fonctionne-t-il ? Healthchecks prend en charge SQLite pour les petites installations à faible trafic, mais Postgres est le meilleur choix par défaut dès que plus d’une ou deux personnes dépendent de la disponibilité continue de cette instance, puisqu’il gère les écritures concurrentes et les historiques de ping plus volumineux avec plus d’aisance. Ce guide utilise Postgres dès le départ pour cette raison.

Comment est-ce que je fais réellement pointer une tâche cron vers ceci une fois que ça tourne ? Ajoutez un appel curl à la fin de la tâche elle-même, après que la commande que vous surveillez a réussi :

votre-script-de-sauvegarde.sh && curl -fsS -m 10 --retry 5 https://checks.votredomaine.com/ping/<uuid>

Chaque vérification dans l’interface vous donne sa propre URL de ping unique à copier dans la tâche. Si le script échoue avant d’atteindre cette ligne, aucun ping n’arrive, et c’est exactement le signal que Healthchecks attend.

Quelle est la vraie différence avec le service hébergé healthchecks.io ? Sur le plan fonctionnel, très peu ; c’est la même application open source dans les deux cas. La version hébergée est maintenue pour vous et facturée mensuellement une fois passé son palier gratuit ; une instance auto-hébergée coûte seulement le serveur sur lequel elle tourne, sans limite sur le nombre de vérifications, mais c’est vous qui vous occupez de la maintenir à jour et sauvegardée.

Faut-il vraiment fermer REGISTRATION_OPEN ? Oui, une fois votre propre compte créé. Il n’y a aucune raison qu’une instance de surveillance auto-hébergée sur votre propre domaine accepte des inscriptions de qui que ce soit qui tombe sur l’URL, et la laisser ouverte est le seul paramètre de tout ce guide qu’il serait réellement une erreur de sauter.

Combien ça coûte à faire tourner ? Le serveur coûte 5,99 €/mois hors TVA. Healthchecks lui-même est un logiciel libre et gratuit, sans frais par vérification ni par intégration ; vous payez pour la machine sur laquelle ça tourne, pas pour ce qui y tourne.

Une tâche cron qui échoue bruyamment est facile à remarquer. Celle qui échoue en silence, la sauvegarde arrêtée depuis trois semaines, le renouvellement de certificat qui s’est tu discrètement, c’est pour ça que ceci existe vraiment, et maintenant vous avez votre propre instance qui surveille exactement ça.

Pourquoi les gens choisissent Railyard pour ça

  • Votre propre compte infonuagique. Déployez sur DigitalOcean, Hetzner, Vultr, AWS, OVHcloud, ou n'importe quel serveur Linux par SSH, facturé directement par votre fournisseur, sans majoration.
  • Un prix fixe par équipe. L'offre gratuite est gratuite pour toujours : un serveur plus un serveur de base de données, trois applications, sans carte. Pro coûte 19 $ par mois pour jusqu'à cinq serveurs et un nombre illimité d'applications. Team coûte 49 $ par mois avec des serveurs illimités. Aucuns frais par application, par déploiement, par siège ou par gigaoctet.
  • Des gabarits en un clic. Déployez des applications open source, comme des bases de données, des outils d'analyse et des outils de développement, sur votre propre serveur en un clic.
  • Des bases de données gérées. Postgres, MySQL et Redis, avec des sauvegardes planifiées, vérifiées par restauration, et la récupération à un point dans le temps.
  • Déploiement par push. Déployez depuis GitHub, GitLab ou la CLI, avec HTTPS automatique et des mises en production sans interruption.

Sautez la configuration du serveur

Déployez le gabarit Healthchecks vérifié sur Railyard en un clic et obtenez une instance fonctionnelle avec Postgres et le HTTPS déjà en place.

Offre gratuite, sans carte, et 14 jours de Pro pour chaque nouvelle équipe. Prix fixe par équipe.