Sending backups to object storage

Write the backup to a file, upload it to an object storage bucket with rclone or the AWS CLI, and schedule it with cron. Keep the copy off the server and test the restore.

Updated 6 min read

Learning objectives

After reading this article you will be able to:

  • Upload a database dump and a folder of files to an S3-compatible bucket
  • Schedule the upload, name files by date and apply automatic retention
  • Verify integrity and restore from the bucket
On this page

A backup that lives on the same server as the data it protects fails together with that server. A broken disk, a suspended account, an rm -rf in the wrong directory or ransomware takes both at once. That is why a backup belongs somewhere separate, and object storage is one of the most practical places: cheap per GB, reachable over HTTP from anywhere and with a standard API.

The full flow is to create the file, upload it to an S3-compatible bucket, name it by date, delete what got old and test the restore. The examples use PostgreSQL and rclone, and the ideas apply to any database or folder. Check each tool's options in its official documentation, listed in the sources.

Serverdatabase +filescrondump namedby datercloneBucketanother service2026-10-13.dump2026-10-12.dump2026-10-11.dump2026-10-10.dump⋮2026-09-12.dumprule: 30 daysRestore testsha256sum -c + pg_restore
The dated dump leaves the server for a bucket on another service, the rule deletes old copies and a restore test proves the copy works.

What do you need before you start?#

  • A bucket on an S3-compatible object storage service, with its endpoint, access key and secret key (see what S3-compatible storage is and what a bucket is).
  • rclone installed on the machine that runs the backup. The aws-cli works too, with similar commands.
  • The database tools: pg_dump and pg_restore for PostgreSQL (for MySQL and MongoDB, see how to back up a database).
  • A key dedicated to backups, separate from the key your application uses. If the provider allows it, scope it to that one bucket.

Never put keys in the script or the repository. Use environment variables or a config file with restricted permissions (chmod 600).

How do you configure rclone for an S3 bucket?#

rclone accepts configuration through environment variables named RCLONE_CONFIG_<NAME>_<OPTION>, so you need no config file. For an S3-compatible service that is not Amazon, use provider=Other:

bash
export RCLONE_CONFIG_BACKUP_TYPE=s3
export RCLONE_CONFIG_BACKUP_PROVIDER=Other
export RCLONE_CONFIG_BACKUP_ENDPOINT="https://your-endpoint.example.com"
export RCLONE_CONFIG_BACKUP_ACCESS_KEY_ID="your-key"
export RCLONE_CONFIG_BACKUP_SECRET_ACCESS_KEY="your-secret"

The values above are placeholders: use your provider's. Test the connection by listing the bucket:

bash
rclone lsd backup:
rclone ls backup:my-bucket

If the provider complains about permissions when creating the bucket and it already exists, add --s3-no-check-bucket to the upload commands: rclone then skips the bucket check (and the attempt to create it).

How do you upload a database dump to the bucket?#

Write a script that creates the dump, computes a hash, uploads both files and removes the temporary directory. The name carries the UTC date, which sorts the files and avoids overwriting yesterday's.

bash
#!/usr/bin/env bash
set -euo pipefail

: "${DATABASE_URL:?set DATABASE_URL}"
: "${BUCKET:?set BUCKET}"

STAMP="$(date -u +%Y-%m-%dT%H%M%SZ)"
DIR="$(mktemp -d)"
FILE="app-$STAMP.dump"
trap 'rm -rf "$DIR"' EXIT

  # -Fc: custom format, compressed and restorable with pg_restore
pg_dump "$DATABASE_URL" -Fc -f "$DIR/$FILE"
(cd "$DIR" && sha256sum "$FILE" > "$FILE.sha256")

rclone copyto "$DIR/$FILE"        "backup:$BUCKET/db/$FILE"
rclone copyto "$DIR/$FILE.sha256" "backup:$BUCKET/db/$FILE.sha256"

echo "uploaded: db/$FILE"

What matters here:

  • set -euo pipefail stops the script at the first error. Without it, a pg_dump that failed halfway would be uploaded as if it were good.
  • -Fc makes pg_dump write a compressed archive that pg_restore can restore, even partially.
  • rclone copyto copies one file to an exact destination path.
  • Run it with DATABASE_URL and BUCKET set in the environment, never written into the script.

What about a folder of files?#

For user uploads or configuration, compress the folder and upload it as a single file:

bash
tar -czf "$DIR/files-$STAMP.tar.gz" -C /path/to/folder .
rclone copyto "$DIR/files-$STAMP.tar.gz" "backup:$BUCKET/files/files-$STAMP.tar.gz"

If the folder is large and changes little, rclone copy /path/to/folder backup:$BUCKET/files-current uploads only what changed. Avoid rclone sync for backups: it deletes from the destination whatever no longer exists at the source, so a mistake at the source would erase the copy too.

How do you schedule the upload?#

Use the system cron. To run every day at 3 AM, with the variables in a file only your user can read:

bash
0 3 * * * set -a && . /etc/backup.env && /opt/scripts/backup-db.sh >> /var/log/backup-db.log 2>&1

set -a exports every variable the file defines, so the script and rclone can see them. The log keeps the output so you can investigate failures. Because cron doesn't tell you when something breaks, add one more step: at the end of the script, call a URL on an external monitor (a "heartbeat" service) and set an alert for when the call stops arriving. Silence is the symptom of a backup that stopped.

How do you apply retention and delete old copies?#

Without retention, the bucket grows forever and so does the bill. You have two options:

  1. A lifecycle rule on the bucket, when the provider supports it: it deletes objects in a folder after N days, with no script. See object lifecycle rules.
  2. Cleanup in the script itself, which works on any service:
bash
rclone delete "backup:$BUCKET/db" --min-age 30d

--min-age 30d limits the command to objects older than 30 days. Run it with --dry-run first to see what would be deleted.

The lifecycle rule is preferable because it keeps working even if the source server is down. Keep copies from different periods (for example, daily for 7 days plus one monthly): if corrupted data lands in today's backup, you still need an earlier version. The full reasoning is in the 3-2-1 rule.

How do you restore and check that it worked?#

A backup that was never restored is only a hope. Test the way back on a test machine or database:

bash
  # find the most recent file
rclone lsf "backup:$BUCKET/db" | grep '\.dump$' | sort | tail -n 1

  # download the dump and its hash, keeping the original name
N=app-2026-10-13T030000Z.dump
rclone copyto "backup:$BUCKET/db/$N"        "./$N"
rclone copyto "backup:$BUCKET/db/$N.sha256" "./$N.sha256"

  # check integrity
sha256sum -c "$N.sha256"

  # restore into a new database
createdb restore_test
pg_restore -d restore_test --no-owner "$N"

Keep the original name when downloading, because the .sha256 file records the dump's name. Then compare counts (SELECT COUNT(*) on the main tables) with the original database and write down how long the restore took. That is the real recovery time you would have in an emergency.

What are the common mistakes?#

  • A partial dump uploaded as good: set -e/pipefail is missing, or nobody checks the exit code.
  • Keys with too much power: if the backup key can also delete the whole bucket and it leaks, the attacker deletes the copies. Use a separate key and, if the service offers it, versioning or object lock.
  • Unencrypted copies with personal data: encrypt with gpg or age before rclone, and keep the key outside the bucket.
  • rclone sync instead of copy: it deletes from the destination along with the source.
  • Large files failing: check the provider's size limit and multipart upload support. See how to upload large files.
  • Never testing the restore: the problem only shows up when you need it most.

How to do this on Square Cloud#

Square Cloud Blob Storage is object storage with an S3-compatible API, so the commands in this guide work once you swap the endpoint and keys: in rclone, set RCLONE_CONFIG_BACKUP_ENDPOINT="https://s3-blob.squarecloud.app", RCLONE_CONFIG_BACKUP_REGION=auto and the S3 keys created in the "S3 access" tab of the dashboard. The aws-cli and boto3 follow the same idea. Each account has three fixed buckets (public, private and legacy, which is read-only), and folders are key prefixes: send your dumps to private, under a folder such as db/.

There is no ingress or egress fee, and every plan includes a quota of 5 GB to 250 GB. For retention, the "delete after N days" folder rule replaces rclone delete --min-age: the count starts from each file's last write, and periods under 7 days are only available on Enterprise. Files up to 10 GB are accepted, multipart is only required above 100 MB, and durability is 99.999999999% with triple replication.

Square Cloud snapshots (manual on every plan; automatic daily for databases on every plan that includes them and for applications from the Pro plan) complement this flow. They cover quick recovery, including restoring an application snapshot into a new application in one click. The dumps you upload to Blob with rclone are a copy you control and can take anywhere.

See quotas and details on the Blob Storage page.

Blob Storage

S3-compatible object storage with no egress fees

Learn more

FAQs

Why not keep the backup on the same server as the database?

Because the server and the copy share the same risks: a failing disk, a suspended account, an accidental deletion or ransomware. If both disappear together, you have no backup. A copy on another service removes that single point of failure.

Should I use rclone or the AWS CLI to upload?

Both speak the S3 API. rclone is more convenient for copying folders and filtering by age; the AWS CLI is the natural choice if you already use it. For an S3-compatible service, both only need the endpoint and the keys.

How many copies should I keep in the bucket?

It depends on how far back you may need to go. A common pattern is daily copies for 7 to 30 days and, if it makes sense, one per month for longer. Set the period and let an expiration rule delete the rest.

Do I need to encrypt the backup before uploading?

If the file contains personal data or secrets, encrypt it before the upload with gpg or age, so anyone with access to the bucket cannot read it. Store the decryption key separately from the bucket.

How do I know the backup in the bucket actually restores?

Only by restoring it. Download the latest file, restore it into a test database and compare row counts. Do this periodically and write down how long it took.

Next articleShould you store images in the database or in object storage?Store images in object storage and keep only the key in the database. See BLOB, bytea and GridFS limits, a side-by-side table and when the database still wins.

More in Storage

See all 15 articles