# 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.

Source: https://supahost.app/kb/cron-job-not-running · Updated 2026-11-11

## 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:

   ```sql
   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:

   ```sql
   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:

   ```sql
   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):

   ```sql
   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](/kb/webhook-debugging) 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](/docs/cron-queues/queues) 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](/docs/functions/scheduling).

## Related

- [Monitoring and debugging cron jobs](/docs/cron-queues/monitoring-jobs)
- [Cron syntax and examples](/docs/cron-queues/cron-syntax)
- [Cleaning up cron run history](/kb/cron-job-history)
- [ERROR: can only create extension in database postgres](/kb/create-extension-pg-cron)
