Cron job not running

A pg_cron job that never runs, runs at the wrong time or fails silently. How to check cron.job and cron.job_run_details, and the usual causes on SupaHost.

What you see

You scheduled a job with cron.schedule, but nothing happens. Or it shows runs, but the work you expected (deleted rows, a webhook call, an email) never happens. There's often no error anywhere obvious.

Why it happens

The common causes, roughly in order of how often they come up:

  • The SQL fails. The job runs, errors, and records the error in cron.job_run_details. Nobody looks there.
  • The job is paused. Its active flag is off.
  • The schedule isn't what you think. Schedules run in UTC, so 0 9 * * * is 09:00 UTC, not your local time.
  • The job "succeeds" but the HTTP call fails. net.http_post only queues a request. The run is marked succeeded even if the endpoint later returns a 401 or times out.
  • A run is still going. pg_cron won't start a job again while its previous run is still going, so a slow job can skip a whole schedule slot.
  • The instance was down or restarting (a plan change or upgrade) when the run was due. pg_cron doesn't catch up on missed runs.

How to fix it

  1. Confirm the job exists and is active:

    select jobid, jobname, schedule, active, command
    from cron.job
    order by jobid;
    
  2. Look at its recent runs. A failed status with a return_message tells you exactly what went wrong:

    select r.start_time, r.status, r.return_message
    from cron.job_run_details r
    join cron.job j using (jobid)
    where j.jobname = 'send-booking-reminders'
    order by r.start_time desc
    limit 20;
    
  3. If there are no rows at all, check the schedule. Paste it into a cron expression checker and remember it's UTC. For sub-minute jobs, use the interval form, for example '30 seconds' (1 to 59 seconds).

  4. Run the command by hand in the SQL editor. If it fails there, it fails in the job too. Fix it, then reschedule with the same name to replace it:

    select cron.schedule(
      'send-booking-reminders',
      '*/10 * * * *',
      $$ select public.queue_booking_reminders() $$
    );
    
  5. For jobs that call an HTTP endpoint, check the response pg_net recorded (responses are kept for a few hours):

    select id, status_code, error_msg, timed_out, created
    from net._http_response
    order by created desc
    limit 20;
    

    See Debugging database webhooks and pg_net for what each result means.

  6. If the work is slow, move it into a function that processes a batch per run, or use a queue so overlapping runs don't matter.

On SupaHost

  • Jobs run as the postgres role inside your project's database. cron.job and cron.job_run_details only show your project's jobs.
  • Dashboard → your project → Cron tab lists every job with an Active toggle, Edit and Delete. Recent runs shows the last 50 runs and refreshes every 30 seconds.
  • Job names must match ^[a-z0-9][a-z0-9_-]{0,48}$. An uppercase name or a space gets an error from cron.schedule.
  • Calling cron.schedule from the Data API, an Edge Function or the service_role role won't work. Only postgres can use the cron schema.
  • cron.alter_job isn't available. Use Active on the Cron tab to pause a job, or cron.unschedule('name') to remove it.
  • If the job calls one of your Edge Functions, send Authorization: Bearer <key> unless the function has Require JWT off. See Running functions on a schedule.

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us