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
activeflag 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_postonly 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
Confirm the job exists and is active:
select jobid, jobname, schedule, active, command from cron.job order by jobid;Look at its recent runs. A
failedstatus with areturn_messagetells 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;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).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() $$ );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.
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
postgresrole inside your project's database.cron.jobandcron.job_run_detailsonly 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 fromcron.schedule. - Calling
cron.schedulefrom the Data API, an Edge Function or theservice_rolerole won't work. Onlypostgrescan use thecronschema. cron.alter_jobisn't available. Use Active on the Cron tab to pause a job, orcron.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.