How to back up Managed PostgreSQL to Object Storage
This guide describes how to set up a Cloud Server that dumps a Managed PostgreSQL database with pg_dump every hour, saves the dumps in a Managed Object Storage bucket, and removes dumps that are older than seven days. It also describes how to restore a dump into a new service.
The dumps are independent of the service's automatic backups, and you can keep them for longer than the backup retention. For the automatic backups, see Managed PostgreSQL backups.
Before you start
You need:
- A Managed PostgreSQL service.
- A Managed Object Storage instance with a bucket for the dumps, and a user with full access to that bucket only. To create the user and its policy, see Getting started with Managed Object Storage for the Control Panel, or How to create a user for an Object Storage instance using the API and How to apply user access policies using the API.
- An SDN Private Network with a router attached, in the database's location. Databases and Object Storage instances can only be attached to a network that has a router. If the database is already attached to an SDN Private Network, use that network.
This guide covers the following operating systems, installed from the UpCloud templates:
- Ubuntu 22.04, 24.04, and 26.04
- Debian 12 and 13
- AlmaLinux 8, 9, and 10
- Rocky Linux 8, 9, and 10
- Fedora 43
Connect the services to the network
The database accepts connections only from networks attached to it. Attach the database and the Object Storage instance to the network. If the database is already attached to an SDN Private Network, use that network and attach only the Object Storage instance. Attaching takes a few minutes. Switching an existing database from the Utility network takes longer, because the service is rebuilt. When both are running again, create a Cloud Server on the same network, so that it gets the routes to them when it starts. If the server already exists, restart it. Run the commands in the rest of this guide as root on that server.
- New database: when you create it, click Attach private network under Connection access and select the network.
- Existing database on the Utility network: on its Overview page, under Connection, click Switch to SDN Networks and select the network. The switch rebuilds the service with its data, and the database can't be switched back to the Utility network afterwards.
- Object Storage instance: on its Overview page, under Private access, click Attach private network, select the network, and enter a name.
Install the PostgreSQL client and rclone
Install the client for your service's PostgreSQL major version, for example 18, and rclone, and create the folder for rclone's configuration file.
apt-get update && apt-get install -y curl ca-certificates
install -d /usr/share/postgresql-common/pgdg
curl -fsSo /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc https://www.postgresql.org/media/keys/ACCC4CF8.asc
. /etc/os-release
echo "deb [signed-by=/usr/share/postgresql-common/pgdg/apt.postgresql.org.asc] https://apt.postgresql.org/pub/repos/apt ${VERSION_CODENAME}-pgdg main" > /etc/apt/sources.list.d/pgdg.list
apt-get update && apt-get install -y postgresql-client-<major-version> rclone
mkdir -p /root/.config/rcloneSave the connection details
Save the database's service URI in a file that only root can read. In the Control Panel, the service URI is the Connection string under Connection on the database's Overview page. Click SHOW_PASSWORD in it to show the password before you copy it. In the API, it's the service's
service_uri. The dump contains the database named in the URI,defaultdbby default. If your data is in another database, replacedefaultdbin the URI with its name.install -m 600 /dev/null /etc/pg-backup.env echo "DATABASE_URL='<service-uri>'" > /etc/pg-backup.envSave the following as
/root/.config/rclone/rclone.conf, with the user's access keys and the instance's private endpoint:[dumps] type = s3 provider = Other access_key_id = <access-key> secret_access_key = <secret-key> endpoint = https://<private-endpoint> region = <region>Check that the server reaches the bucket. The command lists the bucket's contents, or prints nothing for an empty bucket, without an error:
rclone lsf dumps:<bucket>
Schedule the backup
Save the backup script as
/usr/local/bin/pg-backup:#!/bin/bash set -euo pipefail source /etc/pg-backup.env name=<database-name>-$(date -u +%Y%m%dT%H%M%SZ).dump pg_dump --format=custom "$DATABASE_URL" | rclone rcat dumps:<bucket>/$name.partial rclone moveto dumps:<bucket>/$name.partial dumps:<bucket>/$name rclone delete --min-age 7d dumps:<bucket>Replace
<database-name>with the name of the database you back up. It's used only in the dump file names. Each dump is saved as a.partialfile first and gets its final name only whenpg_dumpsucceeds. The last line removes dumps older than seven days. To keep them longer, for example for longer than the database's backup retention, change7d.Save the service as
/etc/systemd/system/pg-backup.service:[Unit] Description=Back up PostgreSQL to Object Storage [Service] Type=oneshot ExecStart=/usr/local/bin/pg-backupSave the timer as
/etc/systemd/system/pg-backup.timer:[Unit] Description=Back up PostgreSQL every hour [Timer] OnCalendar=hourly Persistent=true [Install] WantedBy=timers.targetMake the script executable, start the timer, and run the first backup:
chmod 755 /usr/local/bin/pg-backup systemctl daemon-reload systemctl enable --now pg-backup.timer systemctl start pg-backup.service
Check the dumps
rclone lsl dumps:<bucket>A .partial file in the list is a backup that failed or is still running. If a dump is missing or a .partial file stays there, run journalctl -u pg-backup.service to see why.
Restore a dump
Create a new Managed PostgreSQL service for the restore. Then, on a server on a network that's attached to both the new service and the Object Storage instance, set up the PostgreSQL client and rclone as above and run:
rclone cat dumps:<bucket>/<file> | pg_restore --no-owner --no-privileges --dbname='<target-service-uri>'