How to back up PostgreSQL on a Hetzner server to Hetzner Object Storage, locked
A PostgreSQL server on a Hetzner Cloud server or dedicated box has no backup unless you make one. Hetzner's server snapshots copy the disk, which is a copy of the same machine, not of the database at a consistent moment, and they sit in the same project as the server. Hetzner Object Storage is S3-compatible, in the same regions, and supports Object Lock, so a dump written there can be made undeletable until a date, for everyone with the project's keys. This page is that setup: the bucket, credentials that can write and not delete, the dump, the restore and the check. The last section is how SafeGrd runs it on the same server.
What you get from Hetzner, and what you have to do
From Hetzner's documentation, read on 2026-10-08:
- Object Storage in Falkenstein, Nuremberg and Helsinki, at
fsn1,nbg1andhel1.your-objectstorage.com. A backup from a server in Germany stays in Germany, or goes to Finland for a second region. - Object Lock, in governance or compliance mode, with a default retention on the bucket. It has to be turned on when the bucket is created: "It is not possible to enable Object Lock on Buckets that were created without Object Lock." A compliance-mode lock cannot be shortened, by anyone.
- Billing is per hour with a monthly cap, with a storage and traffic quota included, ingress free, and objects under 64 kB charged as 64 kB. Bucket names are unique across all of Hetzner, so pick one with your project's name in it.
- No database backup of its own. Hetzner backs up the server's disk, on request or on a schedule. That is a copy of the machine, restored whole, taken whenever the schedule says, with the database's files as they were mid-write. The dump below is the database backup.
1. A bucket with Object Lock, and a key that cannot delete
In the Hetzner Console, under Object Storage, create a bucket with Object Lock enabled, in the location of your choice, then create S3 credentials. Set a default retention on the bucket with the AWS CLI, pointed at Hetzner's endpoint, so every object written is locked without the backup script asking:
Hetzner's S3 credentials are valid for every bucket in the project, so the credential on the database server can delete whatever the project holds that is not locked. The lock is what protects the backups; keep the credential off any other bucket's data by giving each purpose its own project. Compliance mode means a backup written by mistake also stays for 30 days, which is the point: what a lock does and does not protect.
2. A read-only role
On PostgreSQL 13, which has no pg_read_all_data, grant each schema and set the
default for new tables; the PostgreSQL page has the lines.
A table with row-level security needs BYPASSRLS on this role or it dumps only
the rows the policies show it.
3. The backup script
set -o pipefailmakes apg_dumpthat dies halfway fail the script, instead of uploading the truncated stream and exiting 0.- Encrypted before it leaves the server, with a public key the server
holds and a private key it never sees. Generate it on your laptop with
age-keygenand keep the private half in two places that are not the server and not the bucket. - A timestamped key never overwrites an earlier backup, which a locked bucket would refuse anyway. Traffic into Object Storage is free; the dump's size is what you pay to store, per hour, until the lock and your cleanup let it go.
Schedule it with cron and add a check that expects a new object every day, since cron reports nothing when a job fails or stops being scheduled. The S3 guide has the cron line, the heartbeat, and the large-upload flag.
4. Restore, and check it
On a new server, or the same one, with PostgreSQL at the same or a newer major version. Then compare row counts per table with the source, as How to test that a backup restores shows, and do it on a schedule.
How SafeGrd does it
SafeGrd's daemon runs on the Hetzner server, from the install script or the
ghcr.io/safegrd/cli image, and writes to your Hetzner Object Storage bucket
under Object Lock, with a credential that can write and never delete. It alerts when a
backup fails or does not run, which cron does not. SafeGrd does the same job on every platform; the PostgreSQL page has what it dumps, how it encrypts, and what a drill checks.
Run your first Fire Drill Read the install guide
safegrd init asks the bucket whether Object Lock is on and warns when it
cannot confirm it, and the first safegrd backup refuses a bucket without the
lock unless the config says worm_mode: "NONE", so a bucket made without the
lock is found out at the first backup rather than at the first deletion.
safegrd doctor checks the same, plus the role's permissions, on an enrolled
host (Storage and retention).
Questions
Why not Hetzner's server backups?
Keep them: a server backup restores the whole machine in minutes, which is the fastest way back after a disk failure. It is not a database backup. The files are copied whenever the schedule says, mid-write, and PostgreSQL recovers from that as from a crash. It restores only onto a Hetzner server of that image, it is in the same project as the server, and nothing in it is locked. The dump restores on any PostgreSQL of the same or a newer version, and the lock keeps it for 30 days.
A second region?
A bucket in hel1 for a server in fsn1 is a different data centre
in a different country with the same credentials and the same lock. For a copy outside
Hetzner altogether, safegrd export copies every snapshot to another bucket,
still encrypted and locked, whenever you want.
Related
- Immutable backups with S3 Object Lock: compliance and governance modes, and what a lock does not protect
- How to back up PostgreSQL to S3: the full script, the bucket policy and cron
- Coolify backup: the same job on a Coolify server, which many Hetzner servers run