Cron-Ping

Surveiller les crons Supabase : attraper le job qui s'est arrêté en silence

Dernière mise à jour : juillet 2026 · Par Yohann Kipfer, Cron-Ping

En résumé

Supabase planifie de deux façons — pg_cron (SQL, dans la base) et des Edge Functions déclenchées sur un horaire. Aucune ne vous prévient quand elle s'arrête. Un projet Free se met en pause après une semaine d'inactivité, une Edge Function atteint son quota d'invocations mensuel, une migration supprime la fonction qu'un job appelle — et le job ne tourne juste… plus, sans e-mail, sans alarme au dashboard. Transformez le job en dead man's switch en pinguant à la fin :

-- à la fin du corps du job pg_cron
select net.http_get('https://cron-ping.com/p/TOKEN');

Si le job tourne, le ping arrive ; s'il s'est arrêté, aucun ping n'arrive dans le délai de grâce et vous recevez un mail ou un Slack. pg_net envoie la requête de façon asynchrone : elle ne bloque et ne casse jamais votre job.

Les deux façons de planifier sur Supabase

Avant de le surveiller, soyez clair sur celle que vous utilisez — elles échouent pour des raisons différentes.

Comparaison des deux mécanismes de planification Supabase
pg_cronEdge Function planifiée
Où ça vitla base Postgresfonction Deno + un trigger pg_cron
Défini aveccron.schedule('nom', '*/5 * * * *', $$…$$)cron.schedule qui appelle net.http_post sur l'URL de la fonction
Exécutedu SQL dans la basevotre TypeScript, invoqué en HTTP
Granularité mini1 minute (sous la minute avec pg_cron récent)1 minute (bornée par pg_cron)
Historiquetable cron.job_run_detailslogs Edge Function + cron.job_run_details

Les écrans Cron et Integrations du dashboard Supabase sont une interface au-dessus de pg_cron — sous le capot, tout est une ligne dans la table cron.job. C'est important : ça veut dire que vous pouvez inspecter et instrumenter chaque job planifié en simple SQL.

Pourquoi les crons Supabase cassent en silence

Le fil rouge avec /vercel-cron-monitor et /github-actions-monitoring : un scheduler managé n'a aucune notion d'exécution manquée. Il peut parfois vous dire qu'une exécution a échoué, dans un log que vous ne lisez pas. Il ne peut pas vous dire qu'une exécution qui aurait dû avoir lieu n'a jamais démarré. Seul un heartbeat externe — quelque chose qui attend un ping qui ne vient pas — comble ce trou.

Ajouter le ping dans un job pg_cron (pg_net)

pg_net permet à Postgres de faire des appels HTTP. Activez-le une fois, puis ajoutez un ping à la fin du corps du job pour que la requête ne parte qu'après la réussite du vrai travail :

-- une seule fois
create extension if not exists pg_net;

-- planifier un nettoyage de nuit qui signale qu'il a tourné
select cron.schedule(
  'nettoyage-nuit',
  '0 3 * * *',
  $$
    delete from events where created_at < now() - interval '30 days';
    select net.http_get('https://cron-ping.com/p/TOKEN');
  $$
);

net.http_get est asynchrone — il met la requête en file et rend la main tout de suite, donc le ping ne peut jamais ralentir ni casser votre job. Comme c'est la dernière instruction, il n'atteint Cron-Ping que si le delete au-dessus n'a pas levé d'erreur. Si le delete échoue, la transaction est annulée, aucun ping n'est envoyé, et le heartbeat manquant vous alerte.

Pour inspecter l'historique réel d'un job à tout moment :

select jobid, status, return_message, start_time, end_time
from cron.job_run_details
order by start_time desc
limit 10;

Ajouter le ping dans une Edge Function planifiée

Si l'horaire déclenche une Edge Function, pinguez depuis l'intérieur, juste avant le return — ainsi un ping veut dire que la fonction a réellement atteint la fin sans planter :

// supabase/functions/rapport-quotidien/index.ts
Deno.serve(async () => {
  await construireEtEnvoyerRapport();  // le vrai travail

  // heartbeat : atteint seulement si le travail ci-dessus n'a pas planté
  await fetch('https://cron-ping.com/p/TOKEN');

  return new Response('ok');
});

Enveloppez dans un try/catch et tapez l'endpoint /fail dans le catch, pour distinguer une erreur applicative d'un scheduler qui ne s'est jamais déclenché :

Deno.serve(async () => {
  try {
    await construireEtEnvoyerRapport();
    await fetch('https://cron-ping.com/p/TOKEN');        // succès
    return new Response('ok');
  } catch (e) {
    await fetch('https://cron-ping.com/p/TOKEN/fail');    // a tourné, mais échoué
    throw e;
  }
});

Mesurer la durée et attraper une exécution à zéro résultat

Les endpoints /start et /fail transforment le heartbeat en quelque chose de plus riche qu'un simple up/down. Pinguez /start en tête du job et /success à la fin, et Cron-Ping enregistre la *durée* — une agrégation de nuit qui prend normalement 40 secondes et soudain 6 minutes signale une table qui a grossi ou une requête qui a perdu son index.

Vous pouvez aussi rendre le ping conditionnel au résultat. Si un job est censé traiter des lignes et en a traité zéro, c'est souvent un échec déguisé en succès — envoyez /fail :

do $$
declare n int;
begin
  delete from sessions_expirees where expire_at < now();
  get diagnostics n = row_count;
  if n = 0 then
    perform net.http_get('https://cron-ping.com/p/TOKEN/fail');
  else
    perform net.http_get('https://cron-ping.com/p/TOKEN');
  end if;
end $$;

FAQ

Supabase m'alerte-t-il quand un cron s'arrête ?

Non. Supabase expose un historique dans cron.job_run_details et les logs Edge Functions, mais n'envoie aucune alerte quand un job échoue ou, pire, cesse totalement de tourner (projet Free en pause, quota épuisé). Vous devez soit interroger cette table vous-même, soit pousser un heartbeat vers un moniteur externe.

Que deviennent mes crons quand un projet Supabase Free est mis en pause ?

Ils s'arrêtent complètement. Les projets Free passent en pause après 7 jours d'inactivité ; la base part hors ligne, donc pg_cron ne peut pas se déclencher et tout trigger d'Edge Function planifiée n'a plus rien contre quoi tourner. Le projet reste en pause jusqu'à restauration manuelle. Un heartbeat externe attrape ça dans son délai de grâce — le scheduler interne ne peut pas vous prévenir que sa propre base est à terre.

pg_net va-t-il ralentir ou casser mon cron ?

Non. net.http_get / net.http_post sont asynchrones : ils mettent la requête en file et rendent la main tout de suite, l'appel HTTP réel se fait dans le worker de fond pg_net. Si l'endpoint de ping est lent ou injoignable, votre job SQL a déjà été validé. Mettez le ping en dernière instruction pour qu'il ne s'enfile qu'après la réussite du vrai travail.

pg_cron désactive-t-il un job après une erreur ?

Non — par défaut il continue de lancer le job à l'horaire et enregistre chaque échec dans cron.job_run_details avec un statut « failed ». Un job cassé par une migration échoue donc en silence chaque nuit. C'est exactement le cas qu'une alerte de heartbeat manquant attrape, car le ping d'un job cassé ne quitte jamais la transaction.

Puis-je surveiller une Edge Function planifiée sans toucher au SQL ?

Oui — ajoutez un seul await fetch('https://cron-ping.com/p/TOKEN') avant le return de la fonction. C'est suffisant pour un heartbeat. N'utilisez pg_net que si vous planifiez des jobs SQL purs avec pg_cron, ou si vous voulez rendre le ping conditionnel à ce que le SQL a réellement fait.

Transformez votre cron Supabase en dead man's switch

Un net.http_get à la fin d'un job pg_cron, ou un fetch avant le return d'une Edge Function. Si le job s'arrête — projet en pause, quota explosé, migration qui l'a cassé — le ping n'arrive jamais et Cron-Ping vous alerte par e-mail ou Slack.

Créer un check gratuit

Plan gratuit : 10 checks, alertes email, historique 7 jours. Sans carte. Hébergé en UE.

À lire ensuite

Surveiller les Vercel Cron JobsLe même problème d'échec silencieux sur Vercel — un ping depuis le route handler.Surveiller le scheduler LaravelUn cron maître répartit toutes les tâches — et masque chaque échec silencieux.Alerte quand une sauvegarde ne tourne plusLe dead man's switch sur le job qu'on ne remarque que trop tard.Documentation Cron-PingPings de start, succès et fail ; délais de grâce ; exemples d'appels HTTP.

Sources