Virteche
Guideschevron_rightStorage

Storage · 7 min read · Updated

How to Back Up MySQL or PostgreSQL to S3 Automatically

To back up MySQL, MariaDB or PostgreSQL to S3 automatically, dump each database every night with mysqldump --single-transaction or pg_dump, compress it, upload it with rclone, and delete copies older than 30 days. A systemd timer runs the script, and a restore into a scratch database proves the dumps work. The same script works with any S3-compatible storage; the examples use Virteche object storage.

1. Set Up rclone for the Bucket

Create a bucket (for example db-backups) in your dashboard, then as root:

sudo -i
curl https://rclone.org/install.sh | bash
rclone config create backups s3 provider=Other endpoint=https://s3.us-west.virteche.com region=us-west \
  access_key_id=YOUR_KEY secret_access_key=YOUR_SECRET force_path_style=true no_check_bucket=true
rclone lsd backups:

2. The Backup Script

Save this as /usr/local/sbin/db-backup-to-s3. Keep the block for your database and delete the other:

#!/bin/bash
set -euo pipefail
STAMP=$(date +%F-%H%M)
DIR=/var/backups/db
DEST="backups:db-backups/$(hostname)"
RCLONE="rclone --config /root/.config/rclone/rclone.conf"
mkdir -p "$DIR"

# MySQL / MariaDB: every database in one file
mysqldump --all-databases --single-transaction --routines --triggers --events \
  | gzip > "$DIR/mysql-$STAMP.sql.gz"

# PostgreSQL: one file per database
for db in $(runuser -u postgres -- psql -Atc "SELECT datname FROM pg_database WHERE NOT datistemplate"); do
  runuser -u postgres -- pg_dump -Fc "$db" > "$DIR/pg-$db-$STAMP.dump"
done

$RCLONE copy "$DIR" "$DEST"
$RCLONE delete "$DEST" --min-age 30d          # keep 30 days in the bucket
find "$DIR" -type f -mtime +2 -delete         # and 2 days on the server
chmod 700 /usr/local/sbin/db-backup-to-s3
/usr/local/sbin/db-backup-to-s3
rclone ls backups:db-backups

pipefail makes the script stop if mysqldump fails, rather than uploading an empty file. On Debian and Ubuntu, root can sign in to MariaDB and MySQL without a password; elsewhere, put the credentials in /root/.my.cnf with permissions 600.

3. Run It Every Night

# /etc/systemd/system/db-backup.service
[Unit]
Description=Database backup to S3
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/db-backup-to-s3

# /etc/systemd/system/db-backup.timer
[Unit]
Description=Nightly database backup

[Timer]
OnCalendar=*-*-* 02:30
RandomizedDelaySec=15m
Persistent=true

[Install]
WantedBy=timers.target
systemctl daemon-reload
systemctl enable --now db-backup.timer
systemctl list-timers db-backup.timer
journalctl -u db-backup.service       # the last run

4. Prove You Can Restore

Restore into a scratch database, never over the live one:

# MySQL / MariaDB: restore everything into a test server, or pull one database out:
rclone copy backups:db-backups/$(hostname)/mysql-2026-10-01-0230.sql.gz /tmp/
gunzip < /tmp/mysql-2026-10-01-0230.sql.gz | mysql      # on a test server only

# PostgreSQL: restore one database under a new name
rclone copy backups:db-backups/$(hostname)/pg-mydb-2026-10-01-0230.dump /tmp/
runuser -u postgres -- createdb mydb_restore_test
runuser -u postgres -- pg_restore -d mydb_restore_test < /tmp/pg-mydb-2026-10-01-0230.dump

An --all-databases dump recreates every database and its users when loaded, which is why it belongs on a test server. Check a few tables, then drop the test database.

The Rest of the Server

The database is usually the most important part, but not the only one. Back up uploads, config and code too; How to back up a Linux server with Borg includes /var/backups, where this script leaves the latest dumps. See the 3-2-1 backup rule for how many copies to keep, and where.

Common Questions

  • Can I just copy the database's data folder instead?

    Not while the database is running: the copy can be inconsistent and fail to start. A dump (mysqldump, pg_dump) is a consistent snapshot taken while the database keeps serving requests.

  • Are the dumps encrypted?

    Not by this script. Virteche buckets are private, and traffic is sent over HTTPS, but the files themselves are readable by anyone with your keys. To encrypt them before upload, point the script at an rclone crypt remote layered on the S3 remote, or back the dump folder up with restic instead.

  • How big a database can this handle?

    Dumps work well into the tens of gigabytes. Much larger, or databases that change too fast for a nightly snapshot, are better served by physical backups with point-in-time recovery, such as pgBackRest for PostgreSQL or Percona XtraBackup and Mariabackup for MySQL and MariaDB.

S3-compatible object storage from 1 TB at a flat monthly price, with transfer included, so restores are not billed per gigabyte.

See object storage

Keep Reading

All guides