VPS Snaps

How to back up a Render PostgreSQL database

Render keeps 3 to 7 days of point-in-time recovery and 7-day exports for paid Postgres databases, and nothing once a database is deleted. To keep your own copies, run pg_dump at the database's major version or newer, either from your own server over the external URL with its IP allowed, or from a Render cron job over the internal URL, and send the dump to storage outside Render.

9 min readUpdated Checked against official documentation

What Render backs up for you

Render backs up databases on paid compute plans continuously, for point-in-time recovery (PITR), and lets you create logical exports from the database's Recovery page. Databases on the Free compute plan get neither.

Point-in-time recoveryLogical exports
How far backThe past 3 days on a Hobby workspace, 7 days on Pro or higherEach export is kept 7 days after it is created, on every workspace plan
How you start itRestore Database, then a time at least 10 minutes in the pastCreate export, one at a time per database
What you getA new database at that moment; the original keeps runningA .dir.tar.gz file holding a pg_dump directory-format archive, to download
Free compute planNot availableNot available

Upgrading a Hobby workspace to Pro doesn't backfill the window; the 3 days grow to 7 going forward. The recovery database copies the original's IP allow list. You check it, point your services at it, then delete or suspend the original.

What happens when the database or workspace is deleted

Render's documentation is direct: it does not retain backups or snapshots of a deleted database instance. The recovery history and any exports you haven't downloaded go with it. The database also goes when something around it does:

  • Deleting a project deletes all of its environments and services. Deleting an environment deletes its services.
  • A Free database expires 30 days after creation. You then have 14 days to upgrade it, after which Render deletes it and all its data.
  • Everything you create belongs to a workspace, and Render's docs describe no backup kept outside it. Treat losing the workspace, or access to it, as losing the database.

A protected environment lets only workspace Admins delete its resources or change a database's allowed IPs. That stops a teammate's mistake, not an Admin's, and not someone who has taken over an Admin account.

Why keep your own dumps

Render's backups are the fastest way back to a recent moment. When data goes missing, try PITR first: it almost always reaches newer data than any dump. But they share the database's fate and the workspace's logins, and reach back at most 7 days. PITR restores only into Render, and exports expire unless you download them.

A nightly pg_dump in a bucket you control covers the rest: deletion, a lost account, a mistake found on day 10, and a move to another host. That is the 3-2-1 rule applied to a database you don't run yourself; managed database backups makes the same case for DigitalOcean and AWS RDS.

Match the pg_dump version to the database

pg_dump refuses to dump a server whose major version is newer than its own. New Render databases default to PostgreSQL 18, and versions 13 to 18 are fully supported. Copy the external URL from the database's Connect menu and compare:

Terminal
psql "<EXTERNAL_DATABASE_URL>" -Atc "show server_version"
Terminal
pg_dump --version

On Ubuntu or Debian, install the matching client, such as postgresql-client-18, from the PostgreSQL apt repository; the managed database guide shows the setup. Render's native runtimes include postgresql-client packages, but the docs don't tie them to your database's version, so the cron job below pins its own.

Dump from your own server over the external URL

By default a Render database accepts external connections from any IP address with valid credentials. Narrow that to your backup server: on the database's Info page, under Networking, replace the 0.0.0.0/0 rule with the server's address as a /32. Rules are IPv4 CIDR ranges only. Render services in the same region keep connecting over the internal URL either way. The Render CLI does the same; this replaces the whole list:

Terminal
render pg update my-database --ip-allow-list "cidr=203.0.113.42/32,description=backup-server"

External connections are always encrypted. Add sslmode=require so libpq refuses to fall back to plain text. The URL contains the password, so keep it in a file only root can read:

/etc/render-backup.env
DATABASE_URL='postgresql://USER:PASSWORD@EXTERNAL_HOST:5432/DATABASE?sslmode=require'
Terminal
sudo chmod 600 /etc/render-backup.env

This script dumps to a temporary name, reads the whole archive back so a truncated file fails here rather than during a restore, then keeps 14 days:

/usr/local/bin/render-pg-backup.sh
#!/usr/bin/env bash
set -euo pipefail

source /etc/render-backup.env
BACKUP_DIR="/var/backups/render"
KEEP_DAYS=14
OUT="$BACKUP_DIR/mydb-$(date -u +%Y-%m-%dT%H%MZ).dump"

mkdir -p "$BACKUP_DIR"
trap 'rm -f "$OUT.partial"' EXIT

pg_dump --dbname="$DATABASE_URL" --format=custom --no-password --file="$OUT.partial"
pg_restore --file=/dev/null "$OUT.partial"
mv "$OUT.partial" "$OUT"

find "$BACKUP_DIR" -name 'mydb-*.dump' -type f -mtime +"$KEEP_DAYS" -delete
  • --format=custom writes a compressed archive that pg_restore can restore in full or in part.
  • --no-password never prompts, so a cron run fails at once instead of hanging.
  • Render's own example adds -n public. Leave it out unless public is your only schema: with -n, pg_dump skips other schemas, large objects and anything outside the schema it depends on.
  • Use the direct URL, not a connection pool URL (port 6432). Render's backup guide says not to back up through PgBouncer, and its pools use transaction-level pooling.
/etc/cron.d/render-pg-backup
15 3 * * * root /usr/local/bin/render-pg-backup.sh >> /var/log/render-pg-backup.log 2>&1

Make the script executable with chmod 755. The dumps are now off Render but on one server, so copy them to a bucket with rclone or the AWS CLI (bucket setup).

Dump from inside Render with a cron job

To keep the database closed to the internet, run the dump on Render. Cron jobs can reach a database in the same region and workspace over the private network, using its internal URL, and inbound IP rules don't apply there. Two limits shape the job: cron jobs can't use a persistent disk, so stream the dump to object storage, and Render stops any run after 12 hours.

Put two files in a Git repository. Alpine 3.23 packages the PostgreSQL 16, 17 and 18 clients, each in /usr/libexec/postgresql<version>; change both 18s to your database's version. For 13 to 15, Render's example repository lists an older Alpine release that has the client:

Dockerfile
FROM alpine:3.23
RUN apk add --no-cache bash postgresql18-client aws-cli
ENV PATH="/usr/libexec/postgresql18:$PATH"
COPY backup.sh /usr/local/bin/backup.sh
RUN chmod 755 /usr/local/bin/backup.sh
ENTRYPOINT ["/usr/local/bin/backup.sh"]
backup.sh
#!/usr/bin/env bash
set -euo pipefail

KEY="render/mydb-$(date -u +%Y-%m-%dT%H%MZ).dump"

pg_dump --dbname="$DATABASE_URL" --format=custom | aws s3 cp - "s3://$S3_BUCKET/$KEY.partial"

aws s3 mv "s3://$S3_BUCKET/$KEY.partial" "s3://$S3_BUCKET/$KEY"
echo "Uploaded s3://$S3_BUCKET/$KEY"
  • aws s3 cp - uploads from standard input. If pg_dump dies, the upload can still finish with whatever arrived, so the file is renamed only after both succeed. A leftover .partial object is a failed run; a lifecycle rule can expire them.
  • Above 50 GB, add --expected-size with the approximate byte count to aws s3 cp.
  • For storage other than AWS, set AWS_ENDPOINT_URL to the provider's S3 endpoint.
  • TLS on internal connections is optional and uses self-signed certificates. Add ?sslmode=require to the URL to insist on it; verify-ca and verify-full don't work there.

Create the cron job from the repository in the Render Dashboard. Pick the database's region and the Docker runtime, a schedule such as 0 3 * * * (schedules are UTC), and the smallest plan. Set DATABASE_URL to the internal URL from the database's Connect menu, plus S3_BUCKET, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_DEFAULT_REGION. The image's ENTRYPOINT runs the script. Click Trigger Run on the Runs page, then check the bucket.

Cron jobs are billed per second of running time, with a minimum of $1 a month per cron job service (as of October 2026). Render's own walkthrough builds a similar job that uploads a gzipped plain-SQL dump.

Restore into a new Render Postgres database

Create a database on the same major version or newer, in the region your services use, and restore into it while it is still empty. Run pg_restore from a machine with a matching client, using the new database's external URL:

Terminal
pg_restore --dbname="<NEW_EXTERNAL_DATABASE_URL>" --no-owner --no-privileges mydb-2026-10-03T0315Z.dump
  • --no-owner makes the restoring user own every object, since the original owner role may not exist.
  • --no-privileges skips GRANT and REVOKE for roles the new database doesn't have.
  • pg_restore carries on past errors and prints a count at the end. Read them before you switch over.

To restore one of Render's own exports, extract it first. It unpacks to a folder named with the export time, holding a directory-format dump named after your database:

Terminal
tar -zxvf 2025-02-03T19_21Z.dir.tar.gz
Terminal
pg_restore --dbname="<NEW_EXTERNAL_DATABASE_URL>" --no-owner --no-privileges --jobs=4 --format=directory 2025-02-03T19:21Z/<database_name>

--jobs=4 loads data and builds indexes in up to four sessions at once, which directory archives allow. Then update DATABASE_URL on your services (an environment group changes several at once) and keep the old database until everything checks out.

Test the restore

A dump you have never restored is a hope. Once a month, restore the latest one into a throwaway database and compare a few important tables with production:

Terminal
psql "<NEW_EXTERNAL_DATABASE_URL>" -Atc "SELECT count(*) FROM orders"

Time it as well; that is your real recovery time. If the restored database fits in 1 GB, a Free Render database makes a fine target (one per workspace). Otherwise create a paid one and delete it afterwards, or restore into a local container as in testing a restore.

Common errors

ErrorFix
aborting because of server version mismatchpg_dump is older than the database. Install the matching client, or change the version in the Dockerfile.
No SNI information foundThe client connected to an IP address. Use the full host name from the external URL.
pg_dump can't connect from your server, though nc says the port is openIts IPv4 address isn't in the database's inbound IP rules. Render checks them after the TCP handshake, so port checks pass anyway.
could not translate host name ... to addressYou used the internal URL outside Render. It works only from Render services in the same region and workspace.
No Create export button or recovery optionsThe database is on the Free compute plan, which has no backups. Upgrade it, or use pg_dump.
role "..." does not exist during a restoreAdd --no-owner --no-privileges.
unsupported version (...) in file headerpg_restore is older than the pg_dump that wrote the archive. Use the same version or newer.

Frequently asked questions

Does Render back up PostgreSQL automatically?
On paid compute plans, yes: continuous backups for point-in-time recovery over the past 3 days on a Hobby workspace or 7 days on Pro and higher. Free databases have no backups.
How long does Render keep Postgres exports?
Seven days after each export is created, on every workspace plan. Download the .dir.tar.gz file to keep it longer.
Are backups kept if I delete a Render database?
No. Render does not retain backups or snapshots of a deleted database. Download an export or take a pg_dump first.
Can I use the internal database URL from my own server?
No. It works only for Render services in the same region and workspace. Use the external URL and allow your server's IPv4 address.

How this was checked

Commands, limits and prices were checked against these official pages, on October 3, 2026: