Render Tutorials
Postgres on Render: a deep dive

Backups, PITR, and deletion safety

⏱ 7 min

Backups and recovery are the boring half of database operations until they’re not. Render takes care of the daily snapshot rhythm and offers point-in-time recovery on paid plans - but the moment you need a backup is the moment you discover whether you understood the rules.

This step is the playbook: what’s automatic, what you need to do yourself, and the deletion rule everyone wishes they’d known sooner.

What Render does for you

flowchart LR
  pg[("Postgres instance")]
  pitr["Continuous PITR<br/>(paid plans)"]
  logical["On-demand logical backups<br/>(.dir.tar.gz, 7-day retention)"]

  pg --> pitr
  pg --> logical
MechanismWhat it coversRetention
Point-in-time recovery (PITR)Continuously back up paid databases. Restore to any moment within the recovery window3 days (Hobby workspace) or 7 days (Pro+ workspace)
On-demand logical backupsCompressed .dir.tar.gz exports you trigger from the Render Dashboard. Download for long-term retention or restore elsewhere7 days regardless of plan

The combination of continuous PITR within the recovery window plus on-demand exports covers most of the “oh no” scenarios - accidental deletes, bad migrations, an UPDATE without a WHERE clause.

Recovery windows by workspace plan

PITR retention
Hobby workspace Pro or higher workspace
─────────────── ────────────────────────
Past 3 days Past 7 days

A few rules worth memorizing:

  • PITR is not available on the Free instance type. Upgrade the instance to enable it.
  • Upgrading the workspace plan does not retroactively backfill the recovery window. If you upgrade Hobby → Pro today, your recovery window extends to 7 days going forward - yesterday’s data still falls off after 3 days from creation.
  • You can’t restore to within the last 10 minutes. Render needs a small lead time to consolidate backup state.
  • Recovery instances are billed. They’re full Postgres instances until you delete them - clean up after a successful cutover.

Performing a recovery

PITR always creates a new instance, never overwrites the original. That makes the flow safer than it sounds: spin up the recovery instance, validate it, then cut over.

  1. Open the database's Recovery page in the Render Dashboard In the Render Dashboard, select your database from the service list and open Recovery.
  2. Scroll to Point-in-Time Recovery and click Restore Database A modal appears.
  3. Provide a name for the recovery instance A new database resource will be created with this name.
  4. Pick the date and time to restore to Within the recovery window. Can’t be within the last 10 minutes.
  5. Choose whether to copy existing settings Yes copies instance type, Datadog API key, and project. No lets you change them. Either way, the recovery instance copies the IP allowlist from the original.
  6. Click Start Recovery Status advances from Recovery In ProgressCreatingAvailable.
  7. Validate the recovered data Connect to the new instance with the PSQL Command on its Info page and confirm what you expect to see is there.
  8. Update your services to point at the recovery instance Either edit fromDatabase references in your Blueprint, or update an env group (one place to change DATABASE_URL for many services). Resync.
  9. Delete or suspend the original instance Once you’ve verified all systems are connecting to the recovery instance.

Logical backups: the on-demand kind

PITR is great for “restore to a point in time” inside Render. For portable backups - files you download, archive, hand to another database service, or restore on your laptop - you have two paths:

  • Render-generated logical backups triggered from the Render Dashboard. Render produces a compressed directory archive (.dir.tar.gz) and retains it for 7 days.
  • Self-managed pg_dump that you run from your laptop or a Render cron job. You control the format, retention, and storage location.

Render-generated logical backups

From the database’s Recovery page, click Create export. Render produces a .dir.tar.gz file you can download from the Render Dashboard. Each export’s filename encodes its timestamp (e.g. 2026-05-20T19_21Z.dir.tar.gz).

A few constraints to know:

  • Only one export at a time per database - the next “Create export” is disabled while one is in progress.
  • Not available on the Free instance type. Upgrade the instance, or run pg_dump from your laptop instead.
  • Render retains the export file for 7 days regardless of workspace plan. Download what you want to keep longer.

Restoring a Render export

Render’s exports are in directory format, restored with pg_restore --format=directory:

Terminal
# Extract the export
tar -zxvf 2026-05-20T19_21Z.dir.tar.gz
# Restore into a target database (note --clean and --if-exists drop matching objects first)
pg_restore \
--dbname="$EXTERNAL_DATABASE_URL" \
--format=directory \
--clean --if-exists --no-owner --no-privileges \
--verbose \
2026-05-20T19_21Z/my_render_database_name

Self-managed pg_dump

When you want full control - different format, different retention, your own off-Render storage - run pg_dump yourself:

Plain SQL - readable, slow to restore on big DBs
pg_dump "postgres://user:pass@host/db?sslmode=require" > backup.sql
Custom format - compressed, parallel restore
pg_dump -Fc "postgres://user:pass@host/db?sslmode=require" -f backup.dump

Use plain SQL when:

  • The database is small (under ~2 GB).
  • You want a human-readable file you can grep and edit.
  • You’ll restore with psql backup.sql.

Use custom format (-Fc) when:

  • The database is bigger than a few GB.
  • You want compression (significant space savings).
  • You’ll restore with pg_restore, optionally in parallel (pg_restore -j 4 --format=custom).

For most production databases, -Fc is the right default for self-managed dumps. Plain SQL is for dev and ad-hoc work.

Where to run pg_dump from

Three options, in increasing order of robustness:

The simplest path for ad-hoc work. Use the external URL with ?sslmode=require:

Terminal
pg_dump -Fc "postgres://user:pass@external-host/db?sslmode=require" -f backup.dump

Make sure your laptop’s IP is in the database’s allowlist (Dashboard → Access Control). Don’t cat the URL - keep it in an env var so it doesn’t end up in shell history.

For scheduled backups, run a cron job that uses the internal URL and uploads the dump to S3 or another durable store:

render.yaml - backup cron
services:
- type: cron
name: pg-dump-nightly
runtime: docker
schedule: "0 4 * * *"
image:
url: ghcr.io/yourorg/pg-dump-uploader:latest
envVars:
- key: DATABASE_URL
fromDatabase:
name: app-db
property: connectionString
- key: AWS_S3_BUCKET
value: my-postgres-backups
- key: AWS_ACCESS_KEY_ID
sync: false
- key: AWS_SECRET_ACCESS_KEY
sync: false

The image is a small wrapper around pg_dump -Fc and the AWS CLI. Build once, run nightly. This is the right pattern for any database whose data matters more than Render’s retention window.

render psql (the CLI’s shell access) is fine for small ad-hoc dumps:

Terminal
render psql <database-id> -- -c '\copy (SELECT * FROM users) TO STDOUT' > users.csv

For databases under ~2 GB, this works. For larger ones, validate against shell timeout limits before relying on it for production backups.

Restoring a self-managed pg_dump

Match the command to the format you wrote with:

Plain SQL
psql "postgres://user:pass@host/db?sslmode=require" < backup.sql
Custom format
pg_restore --format=custom -d "postgres://user:pass@host/db?sslmode=require" backup.dump

For large -Fc dumps, parallel restore is dramatically faster:

Parallel restore
pg_restore -j 4 --format=custom -d "postgres://..." backup.dump

-j 4 runs four restore workers in parallel. Pick the number based on your target instance’s CPU count; on a 4-core plan, 4 is a good default. (For Render’s .dir.tar.gz exports, use --format=directory instead - covered above.)

Test your restores

The single most useful habit you can build: periodically restore a recent dump to a non-production database and confirm it works. A backup you’ve never restored is hope, not a backup.

Once a quarter is enough for most teams. Make it part of the on-call rotation: rotate who runs the restore drill, write down what was different from last time, fix the surprises before they’re real.

The deletion rule that loses data

flowchart LR
  alive["Render Postgres<br/>(snapshots + PITR)"]
  delete["Delete instance"]
  gone["No more snapshots,<br/>no more PITR - everything is gone"]

  alive --> delete --> gone

This trips up teams in two scenarios:

  • Cleanup after a migration. You moved data to a new database, the new one is working, you delete the old one - and a week later you realize you needed a row from the old database. The snapshots aren’t there to fall back to.
  • Tearing down a staging environment. The data was “just staging” so it didn’t seem important; six months later, “just staging” turns out to have had a useful test fixture.

The rule of thumb: pg_dump and store the dump before any deletion, even if you’re sure you don’t need it. The cost is a few minutes; the upside is a get-out-of-jail card if you’re wrong.

What about cross-region disaster recovery?

Render doesn’t ship a built-in cross-region replication product. The options:

  • pg_dump to a different-region object store (e.g. S3 in us-east-1 even if your database is in oregon). Your backup survives a region outage even if the live database doesn’t.
  • Self-managed logical replication to a Postgres instance in another region. More moving parts; you operate the replication yourself.
  • Periodic restore to a warm secondary in another region. A cron job that nightly restores the latest backup to a database in a different region - expensive but real.

Most teams don’t go beyond the first option. It’s enough to recover from a regional event with a few hours of data loss, which is the right trade-off for most apps below the “global SaaS” tier.

You're deleting an old Render Postgres instance that's been running for a year. The Render Dashboard shows it has PITR available within the last 7 days and three on-demand exports created over the past month. After you confirm the deletion, what's preserved?

What you learned

  • PITR is continuous and automatic on paid Postgres. Recovery window is 3 days on Hobby workspaces, 7 days on Pro+. Not available on Free instances
  • PITR always creates a *new* instance - validate it before cutting consumers over. The recovery instance is billable until you delete the original
  • On-demand logical backups produce `.dir.tar.gz` files, restored with `pg_restore --format=directory`. Retained 7 days on Render regardless of plan
  • Self-managed `pg_dump -Fc` is the portable backup; pair with `pg_restore --format=custom` for restore. Run nightly from a cron job and upload to S3/R2 for off-Render durability
  • Deleting a Postgres instance deletes its PITR state, exports, and snapshots - all of it, permanently. Download or `pg_dump` everything important before deletion
  • Test your restores quarterly - a backup you've never restored is hope, not a backup