RDS backups and EBS snapshots without AWS: the boring stack
TL;DR. Two of the scariest things to give up when you leave AWS are RDS automated backups (with point-in-time recovery — rewind the database to any second before a mistake) and EBS volume snapshots. Both reproduce cleanly on a plain OVH or Hetzner server with mature, boring open-source tools: pgBackRest streams full + incremental + WAL backups of Postgres to any S3-compatible bucket with built-in retention (true PITR); ZFS + sanoid/syncoid gives you instant, scheduled, auto-expiring volume snapshots replicated offsite (the real EBS-snapshot analog); restic or BorgBackup handle files. You even get two things AWS doesn’t hand you by default: immutable, ransomware-proof backups (object-lock / WORM) and cheap or free restore egress. The honest caveat: managed RDS backups are labor you no longer do — for a team with zero ops capacity, that convenience can be worth paying for.
The fear, named
Leaving AWS, the database is where nerve fails. RDS quietly takes a snapshot every night, lets you rewind to any second in the retention window, and replicates it across zones — and EBS snapshots do the same for whole volumes. Run your own server and you own that now. The good news: this is one of the most solved problems in operations. The tools below are what banks and hosting companies have used for a decade. None of it is exotic.
There are two separate jobs, and people conflate them:
- Database backups — logical/physical backups of Postgres/MySQL with point-in-time recovery. (The RDS part.)
- Volume / block images — a frozen image of an entire disk you can roll back or clone. (The EBS part.)
You want both. They use different tools.
Part 1 — RDS-style database backups with point-in-time recovery
Postgres: pgBackRest (the direct RDS replacement)
pgBackRest is the gold standard. It does full + differential + incremental backups, archives WAL for point-in-time recovery, pushes straight to S3-compatible object storage, encrypts client-side, runs in parallel, and has retention/expiry built in:
# /etc/pgbackrest/pgbackrest.conf (illustrative — verify against the docs)
[global]
repo1-type=s3
repo1-s3-bucket=db-backups
repo1-s3-endpoint=<r2-or-b2-or-hetzner-endpoint>
repo1-s3-region=auto
repo1-path=/pg
repo1-cipher-type=aes-256-cbc # client-side encryption
repo1-retention-full=4 # keep 4 full backups, expire older
repo1-retention-diff=12
[main]
pg1-path=/var/lib/postgresql/16/main
Schedule with a systemd timer (or cron):
# nightly full on Sunday, incremental the other days
pgbackrest --stanza=main --type=full backup # Sun
pgbackrest --stanza=main --type=incr backup # Mon–Sat
Restore to a point in time is one command:
pgbackrest --stanza=main --type=time \
--target="2026-06-15 14:30:00" restore
That’s RDS’s headline feature — nightly backups + PITR — on storage you control. Lighter alternatives:
pg_dump (logical, no PITR — fine for small DBs), Barman (another full PITR manager), or WAL-G
(popular, S3-native, also does MySQL/MongoDB).
MySQL / MariaDB
Same shape, different tool: mariabackup / Percona XtraBackup (physical, hot, supports incremental +
binlog for PITR) or mydumper (fast parallel logical dump). Pipe the output to object storage on a timer.
Where the backups go
Any S3-compatible bucket — and this is where the cloud-exit math shows up again:
| Backup target | Storage | Restore egress | Notes |
|---|---|---|---|
| Cloudflare R2 | ~$15/TB-mo | $0 | Zero egress = free disaster recovery |
| Backblaze B2 | ~$6/TB-mo | free up to 3× stored | Cheapest storage |
| Hetzner Object Storage | low, included-ish transfer | cheap | Same vendor as the box — keep one copy elsewhere |
| OVH Object Storage | low | cheap | Ditto |
| MinIO / SeaweedFS (self-host) | your disk | free (LAN) | Good as the first local copy |
| AWS S3 (for comparison) | ~$23/TB-mo | metered ($0.09/GB) | Restoring a 1 TB DB = ~$90 just in egress |
The quiet win: on AWS, restoring a large backup is metered egress and cross-AZ — you pay to get your own data back in a disaster. On R2/B2/Hetzner that restore is free or near-free. Your DR drill stops being a line item.
Part 2 — EBS-snapshot-style volume images (this is where filesystem choice matters)
A database backup protects the database. A volume snapshot protects the whole disk — config, the OS, an app’s data dir — and lets you roll back or clone in seconds. On AWS that’s EBS snapshots; on your own box it’s a copy-on-write filesystem, and yes — ZFS is the smart choice.
ZFS + sanoid + syncoid (the real EBS-snapshot analog)
zfs snapshot is instant, atomic, copy-on-write — a point-in-time image of a dataset at near-zero cost. Two
companion tools turn that into a full snapshot-and-replicate system:
- sanoid — the scheduler + pruner. Declarative policy (“keep 24 hourly, 30 daily, 8 weekly, 12 monthly”), auto-expires the rest. This is “scheduled volume images with auto-expiry.”
- syncoid — offsite replication.
zfs send | zfs receiveover SSH to a backup box or another provider, incrementally (only changed blocks — like an incremental EBS snapshot).
# /etc/sanoid/sanoid.conf
[tank/pgdata]
use_template = production
[template_production]
hourly = 24
daily = 30
weekly = 8
monthly = 12
autosnap = yes
autoprune = yes
# replicate hourly to an offsite box (run from a timer)
syncoid tank/pgdata backup@offsite:tank/pgdata
Bonuses EBS doesn’t give you: checksums (catches silent corruption), lz4 compression (~free), encryption at rest, and atomic snapshots of a running database (combine with a quick checkpoint for app-consistency).
Alternatives
- Btrfs + btrbk — same CoW snapshot model with a sanoid-style scheduler/replicator. Good if you’d rather stay in-kernel. (Avoid Btrfs RAID 5/6.)
- LVM snapshots (on ext4/xfs) — work without a special filesystem, but they’re heavy and not meant to live long. Use one only to grab a brief consistent image to back up from, then drop it — not as a retention store.
- Provider snapshots — Hetzner Cloud automated backups (≈20% of the server price) + manual snapshots; OVH snapshots/backup storage. Sometimes the simplest EBS-equivalent is just enabling the provider’s own feature.
Part 3 — files and everything else: restic / Borg
For application files, uploads, configs (anything that isn’t a database or a whole volume), restic or BorgBackup are the de-facto standard: dedup, encryption, and retention built in — pointed straight at object storage.
restic backup /srv/app/data
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 12 --prune
restic speaks S3/R2/B2 natively; that forget --prune is your auto-expiry.
Which filesystem should the server use?
- Database / data box → ZFS. Tune it for Postgres: the data dir on its own dataset with
recordsize=16k,logbias=throughput,compression=lz4,atime=off. You get EBS-style snapshots, send/receive, checksums and compression in one tool. This is the strong default when your goal is “RDS + EBS behaviour on my own server.” - Don’t want ZFS’s RAM/ARC footprint or out-of-tree module? → XFS + LVM for the data volume (mature, in-kernel), with pgBackRest for the DB and restic for files, plus provider snapshots for whole-volume images. Boring and bulletproof.
- Files/objects only (no DB on the box) → ext4 or XFS is fine; let restic/Borg do the work.
Put together: the boring backup stack
| What AWS gives you | What you run instead |
|---|---|
| RDS automated backups + PITR | pgBackRest (or WAL-G / Barman) → R2 / B2 / Hetzner, retention in-tool |
| EBS snapshots / AMIs | ZFS + sanoid (local, fast) + syncoid (incremental offsite) — or provider snapshots |
| File/volume backup | restic / Borg → object storage, forget --prune |
| Cross-region snapshot copy | syncoid to a box at a different provider |
| Backup Vault Lock (immutability) | S3 Object Lock / WORM on R2 / B2 / MinIO |
| Backup scheduling windows | systemd timers (or cron) |
Follow 3-2-1: at least 3 copies, on 2 kinds of media/location, 1 of them offsite — and make 1 immutable (object-lock), so a compromised host can’t delete or encrypt your backups. That last point is a genuine upgrade over a default RDS setup, not a compromise.
Test your restores (the part everyone skips)
A backup you’ve never restored is a hope, not a backup. On owned infra this is easier than on AWS, because restore egress is free:
- Monthly:
pgbackrest restoreinto a throwaway container and runSELECT count(*)on a known table. - Quarterly:
zfs receivethe offsite snapshot onto a spare box and boot it. - Automate the check and alert if it fails. A green restore is the only proof that matters.
When managed RDS backups are genuinely worth keeping (be honest)
This isn’t free — it’s cheaper and more controllable, which is different. (Backups are only one slice of “managed” — for automatic failover, HA and version upgrades, see Managed database without AWS.) Stay on managed when:
- You have no ops capacity and won’t hire it — RDS’s nightly-backup-you-never-think-about is real labor saved, and for a tiny team that convenience can outweigh the cost.
- Compliance mandates a managed provider with specific attestations you can’t easily self-certify.
- The database is huge and business-critical and you’d rather buy a vendor’s on-call than build your own restore-tested pipeline (at least until the bill justifies the move).
For most small and scale-up teams with even basic Linux skill, the stack above is a few days of setup for a backup system you fully own — and a restore that doesn’t bill you egress to recover from a bad day.
Quick wins you can do this week
- Stand up pgBackRest against a cheap B2/R2 bucket; take one full backup; do one PITR restore into a container to prove it.
- If the box is ZFS (or can be), enable sanoid with a sane policy and syncoid to one offsite target.
- Turn on Object Lock on the backup bucket so backups are immutable.
- Add a restore drill to your calendar. Untested backups don’t count.
Designing this — the right filesystem, the retention policy, where the offsite copy lives, and the honest call on what to keep managed — is part of a Cloud-Exit Assessment. Or send me your cloud bill and I’ll show you what RDS/EBS/snapshot lines are costing you and what the owned equivalent runs, free, in 24 hours. Read by me, never shared.
Sources
- pgBackRest (full/incr/WAL, S3 repos, retention, PITR) — pgbackrest.org (User Guide + command reference).
- WAL-G, Barman — github.com/wal-g/wal-g, pgbarman.org.
- MariaBackup / Percona XtraBackup, mydumper — the respective project docs.
- ZFS snapshots/send-receive and Postgres tuning (recordsize, logbias) — OpenZFS docs + the PostgreSQL-on-ZFS community guidance.
- sanoid / syncoid — github.com/jimsalterjrs/sanoid.
- Btrfs / btrbk — btrbk docs; LVM snapshots — the LVM admin guide.
- restic, BorgBackup — restic.net, borgbackup.readthedocs.io.
- S3 Object Lock / WORM immutability on Cloudflare R2, Backblaze B2, MinIO — provider docs.
- Hetzner Cloud backups / OVH snapshots — the respective provider pricing/feature pages.
- Egress/restore-cost context — see S3 egress cost on this site.