pgbr
Guides

Scheduling Backups

Cron schedules, timezones, and retention.

A schedule runs a backup on a cron pattern. A schedule is one row in Postgres, and that row is the scheduler — its next_run_at column is what makes it fire. There's no registration anywhere else that could fall out of step with it.

Manage them from the Schedules page.

Creating a schedule

FieldNotes
NameA label. 1–100 characters.
DatabaseThe database to back up. Fixed after creation.
ScheduleA preset, or a custom cron expression.
TimezoneAny IANA timezone. Defaults to your browser's.
Keep lastHow many successful backups to retain.
EnabledWhether it's registered to run.

Below those sit the same pg_dump flags as a one-off backup — see the flag reference.

A schedule's database cannot be changed after creation. Everything else — cron, timezone, flags, retention — can. To move a schedule to a different database, create a new one.

Presets and cron

The presets cover the common cases and write the cron expression for you:

PresetCronRuns
Hourly0 * * * *Every hour, on the hour
Daily0 H * * *Every day at your chosen hour
Weekly0 H * * DWeekly, on your chosen day
Monthly0 H D * *Monthly, on your chosen date

Choose Custom to write the expression yourself. pgbr requires exactly five fieldsminute hour day month weekday — and validates the expression before saving, so a malformed pattern is rejected rather than silently never firing.

┌───────────── minute (0–59)
│ ┌─────────── hour (0–23)
│ │ ┌───────── day of month (1–31)
│ │ │ ┌─────── month (1–12)
│ │ │ │ ┌───── day of week (0–6, Sunday = 0)
│ │ │ │ │
* * * * *

The form shows a plain-English description of whatever expression is in the field, which is the fastest way to catch a cron you've misread.

Setting day of month and day of week together is an OR in cron, not an AND — 0 0 1 * 1 runs on the 1st and every Monday. This trips people up regularly; read the description the form generates.

Timezones

Schedules store an IANA timezone and the cron fires in that zone, so a "daily at 2am" schedule stays at 2am local across DST changes rather than drifting by an hour twice a year.

The form defaults to your browser's timezone. The database column defaults to UTC.

DST transitions have the usual edge cases: a schedule at 2:30am fires twice on the day the clock goes back and not at all on the day it springs forward. For hourly-or-finer schedules it doesn't matter. For a daily backup, avoid the hour your zone shifts.

Retention

Keep last bounds growth. With 7, each successful backup from that schedule prunes anything beyond the newest 7 — deleting both the row and the artifact. The range is 1–365. Leave it unset to keep everything forever.

What retention does not touch:

  • Failed backups are always kept. A schedule that has been failing for a week shouldn't quietly erase the evidence.
  • Manual backups — pruning only ever considers backups from the same schedule.
  • The lifetime backup counter, which only counts up.

Pruning runs after each successful backup from that schedule. A retention failure is logged but never fails the backup — an unpruned bucket is a smaller problem than a lost backup.

Retention counts backups, not time. "Keep last 7" on an hourly schedule is seven hours of history. Set it against how far back you'd actually need to go.

Enabling and disabling

The toggle sets or clears next_run_at. A disabled schedule keeps its row and its history and simply doesn't run; enabling it computes the next occurrence from now.

Deleting

Deleting removes the schedule row, and that's the whole operation. Backups it created are kept — their scheduleId becomes null and their artifacts stay. You lose the schedule, not its output.

Once unlinked, those backups are no longer pruned by anything. Retention only ever applies through a live schedule, so delete a schedule and its old backups become yours to manage.

How schedules fire

Every 30 seconds a worker takes a transaction-scoped advisory lock — so exactly one replica ticks, however many you run — and, for each enabled schedule whose next_run_at has passed, queues a backup and computes the next occurrence. Both writes are in the same transaction: a schedule cannot fire without being rescheduled, or be rescheduled without firing.

The next occurrence is computed from now, never from the run that was missed. If every worker was down for a day, each schedule fires once when one returns and then resyncs — you don't get a day of backups replayed at you.

Because the schedule row is the only state involved, there is nothing to reconcile and nothing that can drift. Creating, editing, enabling, or disabling a schedule is a single write to a single row.

On this page