SimpleBackupsSimpleBackups

How to restore DigitalOcean Spaces from an off-site backup

Posted on

You opened your DigitalOcean dashboard and the objects you needed are gone. Maybe someone ran a delete script against the wrong bucket. Maybe the account was compromised. Maybe you're migrating to a new region and you need to bring everything back with metadata intact.

You went looking for a "Restore" button inside Spaces. There isn't one. DigitalOcean has no native restore mechanism for Spaces: no backup history, no point-in-time recovery, no automated rollback. If you need to get data back into a Space, you are restoring from whatever off-site copy you made yourself.

This article walks you through the full restore path: recreating the Space, syncing objects back with rclone, doing the same with the AWS CLI, restoring metadata and ACLs, and verifying the restore completed correctly.

What you walk away with: a repeatable restore procedure for DigitalOcean Spaces, covering both rclone and the AWS CLI, with the metadata and ACL edge cases documented.

Why you're restoring from off-site (there's no native option)

DigitalOcean Spaces has no native backup. That sentence is not a caveat; it is the full picture. The DigitalOcean Spaces documentation covers versioning, lifecycle rules, and CDN settings. It has nothing about backup or restore because those features don't exist.

Versioning gets close. When enabled, deletes create a marker rather than immediately removing the object, and earlier versions of overwritten files remain accessible. But versioning has hard limits as a recovery tool:

  • It only works if versioning was enabled before the deletion happened. If it wasn't, the objects are gone.
  • An attacker with your Spaces access key can delete all versions, not just current objects. AWS S3 has MFA delete to prevent this; Spaces does not.
  • Deleting the bucket itself removes all versions with it.
  • All versions live in the same DigitalOcean account. Account compromise, billing dispute, or regional outage takes the backup with the production data.

Same-host risk

There is no native Spaces restore. If you don't have an off-site copy, accidental deletion or account compromise means the data is gone. This is the same-host risk that runs through every DigitalOcean native backup product. See off-site compliance for DigitalOcean for the full treatment.

If you're in the middle of an incident right now and you don't have an off-site copy, there are limited options. Check whether versioning was on (objects may still be there as prior versions). Check whether any other account or service cached a copy. If neither applies, the data is unrecoverable through DigitalOcean alone.

If you do have an off-site copy, read on.

Recreating the Space

Before you sync anything back, you need a destination Space. If the original bucket still exists (you're doing a partial restore, or versioning gives you access to prior object versions), you can skip this step.

If the original bucket is gone, create a new one with matching configuration.

A few things to get right before you start syncing:

Region. Create the Space in the same region as the original, or a different region if you're intentionally changing your topology. Buckets are region-bound. An object URL that includes nyc3.digitaloceanspaces.com won't resolve if the bucket now lives in ams3. If your application has hard-coded Spaces URLs, use the same region.

Bucket name. Bucket names in Spaces are globally unique per region. If the original bucket name is still taken (perhaps by a partially deleted bucket that's in a cleanup state), you may need to use a different name and update your application configuration.

Versioning. If versioning was enabled on the original, enable it on the new bucket before you start the restore sync. Syncing into an unversioned bucket won't break anything, but you won't get version history for the objects you're restoring.

CDN and lifecycle rules. These don't transfer with the objects. If the original had a CDN distribution or lifecycle expiry rules, recreate them manually on the new bucket before the restore, or immediately after. Otherwise, objects may be accessible but without the CDN edge, or lifecycle rules won't apply to the newly restored objects.

If you have your original Spaces configuration in Terraform or a similar IaC tool, apply it first to recreate the bucket with the correct settings. Then run the restore sync. That order prevents configuration drift between the original and the restored bucket.

Syncing objects back with rclone

rclone is the most flexible way to sync objects back into Spaces from an external bucket. It speaks S3 natively and works with any S3-compatible source: AWS S3, Backblaze B2, Wasabi, Cloudflare R2, another Spaces bucket in a different region, or almost anything else.

If you followed the backup approach from how to back up DigitalOcean Spaces, your rclone.conf already has both remotes configured. If not, configure them now: one remote pointing at your off-site backup source, one pointing at the Spaces destination.

To configure the Spaces destination, you need:

  • The Spaces access key and secret (from the API page in the DigitalOcean control panel)
  • The region endpoint, which follows the pattern <region>.digitaloceanspaces.com

Use rclone config to create or verify the remotes, then run the restore sync:

rclone sync
  offsite-remote:your-backup-bucket
  do-spaces:your-destination-space
  --progress
  --transfers 8
  --checkers 16
  --metadata
  --s3-acl private

Note that the direction is reversed from backup: the off-site bucket is the source, Spaces is the destination.

A few flags worth explaining:

  • --metadata tells rclone to copy object metadata (Content-Type, Cache-Control, custom headers) from the source. This flag requires rclone 1.62 or later. Without it, rclone transfers the object bytes but not the metadata.
  • --transfers 8 runs 8 parallel transfers. Increase this on a fast connection; decrease it if you hit rate limits.
  • --s3-acl private sets the ACL on restored objects to private. See the metadata and ACL section below for when you'd want a different value here.
  • sync makes the destination match the source. If the destination Space is empty, that's equivalent to a full restore. If it has partial content already, sync fills in the gaps and removes objects that shouldn't be there. Use copy instead if you want to preserve objects already in the destination that aren't in the source.

Before running against the real destination, use --dry-run to preview what rclone will do:

rclone sync
  offsite-remote:your-backup-bucket
  do-spaces:your-destination-space
  --dry-run
  --metadata

This shows you the object count, the total size, and any errors before a single byte is written.

For large restores (hundreds of thousands of objects or tens of gigabytes), use --log-level INFO --log-file /var/log/rclone-restore.log to write a full transfer log. If the restore is interrupted, rclone's sync is idempotent: restart it from the same command and it will resume from where it left off, skipping objects that are already correct at the destination.

Syncing with aws cli

If your off-site backup lives in AWS S3 and you prefer the AWS CLI, you can run the restore directly against the Spaces endpoint without installing rclone. The --endpoint-url flag redirects the CLI to your Spaces region.

Configure the CLI to use your Spaces credentials. The clearest approach is a named profile:

aws configure --profile spaces-restore
# AWS Access Key ID: <your-spaces-access-key>
# AWS Secret Access Key: <your-spaces-secret-key>
# Default region name: us-east-1
# Default output format: json

The region value in the profile doesn't control where Spaces writes; the endpoint URL does. Set it to anything valid to satisfy the CLI validation.

Run the restore sync:

aws s3 sync
  s3://your-aws-source-bucket/
  s3://your-destination-space/
  --endpoint-url https://nyc3.digitaloceanspaces.com
  --profile spaces-restore
  --no-verify-ssl

Replace nyc3 with your Space's region code (ams3, sgp1, fra1, and so on). The --no-verify-ssl flag is sometimes needed when the CLI's SSL negotiation picks up a mismatch between the AWS certificate chain and the Spaces endpoint; it skips that verification without affecting whether the transfer itself is encrypted.

For a dry run, add --dryrun:

aws s3 sync
  s3://your-aws-source-bucket/
  s3://your-destination-space/
  --endpoint-url https://nyc3.digitaloceanspaces.com
  --profile spaces-restore
  --no-verify-ssl
  --dryrun

One limitation of the AWS CLI path: it does not copy S3 object metadata as reliably as rclone when the source and destination are different providers. Specifically, custom metadata headers may not arrive at Spaces correctly depending on the AWS CLI version and how the source objects were stored. If metadata fidelity matters, use rclone with --metadata instead and treat the AWS CLI path as a fallback for bucket-to-bucket copies where both ends are well-behaved S3.

Restoring metadata and ACLs

Objects carry more than bytes. When you restore a Spaces bucket, you want Content-Type, Cache-Control, and any custom metadata headers to survive the round-trip. Whether they do depends on your restore method and which fields you're asking about.

Here's what the round-trip actually looks like:

FieldSurvives with rclone + --metadataSurvives with aws cli syncNotes
Content-TypeYesYes (usually)Inferred from extension if missing at destination
Cache-ControlYesYes (usually)Check headers on a few objects after restore
Custom metadata (x-amz-meta-*)YesPartiallyrclone more reliable across providers
ACLs (public/private per-object)NoNoMust be reapplied manually or via policy
Versioning historyNoNoPrior versions don't transfer; only latest object
Lifecycle rulesNoNoRecreate on the bucket, not per-object
Bucket policyNoNoRecreate manually on the new bucket

The ACL row is the one that consistently trips people up.

ACLs don't round-trip perfectly between S3 and Spaces. When rclone syncs objects, it applies the ACL you pass via --s3-acl globally to all transferred objects. Per-object ACLs from the source don't carry over individually. If your original bucket had a mix of public and private objects, you'll need to reapply the public ACLs after the restore.

If your application relies on per-object public URLs, here's the safest restore sequence:

  1. Run the sync with --s3-acl private to get all objects into the destination without inadvertently exposing anything.
  2. After the sync completes, generate a list of objects that should be public (from your application database, from a saved ACL export, or from the source bucket if it's still accessible).
  3. Apply the public ACL to those objects using rclone or the aws s3api put-object-acl command.

For most use cases, restoring everything as private and then making specific objects public is safer than trying to replicate a mixed ACL state in a single pass.

For Content-Type specifically: if rclone doesn't have --metadata available (older versions) or you're using the AWS CLI and some headers didn't transfer, you can batch-update Content-Type on misidentified objects using the Spaces S3-compatible API via aws s3api copy-object --metadata-directive REPLACE. This is tedious on large buckets, which is another reason to prefer rclone 1.62+ with --metadata for the initial restore.

Verifying the restore is complete

A restore is only complete when you've confirmed the destination matches the source. "The sync command exited 0" is not the same thing as "the restore is correct."

Start with counts:

# Count objects in the source (off-site backup)
rclone size offsite-remote:your-backup-bucket

# Count objects in the restored Space
rclone size do-spaces:your-destination-space

Both should report the same number of objects and the same total size. If they differ, the sync didn't complete cleanly.

For a deeper check, use rclone check to compare checksums between source and destination:

rclone check
  offsite-remote:your-backup-bucket
  do-spaces:your-destination-space
  --one-way

The --one-way flag checks that every object in the source exists at the destination with a matching checksum. Without it, rclone also flags objects that exist at the destination but not in the source, which may not be relevant if you used copy instead of sync.

Expect rclone check to run for a while on large buckets. It pulls checksums from both sides and compares them without downloading object bytes. On a bucket with a million objects, this can take thirty minutes or more depending on API rate limits.

Spot-check metadata on a sample of objects:

rclone lsjson
  --metadata
  do-spaces:your-destination-space
  --include "sample-object-key.jpg"

Confirm that Content-Type and any custom headers you care about are present and correct.

Finally, test a real access path. Load a URL, run a query against the data, or have your application's health check hit a known object. Restoring bytes without confirming the application can read them is an incomplete test.

Restore verification in practice

We back up DigitalOcean every day. The failure mode we see most after a restore isn't missing objects: it's metadata that didn't transfer and broke CDN caching or broke Content-Type detection in the application. Run rclone check and spot-check metadata headers before you tell anyone the restore is done.

What to do next

If this restore was an emergency, the first thing to do after verifying is to understand why the off-site copy existed and how current it was. A backup that was two weeks stale during a critical restore is a different problem than the restore procedure itself.

If you haven't set up automated off-site backups for Spaces yet, the backup article for this restore guide is how to back up DigitalOcean Spaces. It covers the rclone mirror setup, scheduling, and the versioning caveats in detail.

If you've read this far, you probably already know whether native Spaces retention is enough for your project. If it isn't, SimpleBackups gives you cross-region off-site backup, automated verification, and a restore you can actually test.

Keep learning

FAQ

Can I restore individual files to a Space?

Yes. Both rclone and the AWS CLI support restoring a single object or a prefix rather than the entire bucket. With rclone, use rclone copy offsite-remote:your-backup-bucket/path/to/object.jpg do-spaces:your-destination-space/path/to/object.jpg. With the AWS CLI, pass the full S3 key path instead of a bucket prefix. The sync and copy commands both accept specific paths, not just top-level bucket names.

Does rclone preserve Spaces metadata during restore?

Yes, with the --metadata flag and rclone 1.62 or later. Without that flag, rclone transfers object bytes but not per-object metadata headers like Content-Type, Cache-Control, or custom x-amz-meta-* fields. ACLs are handled separately: pass --s3-acl to control the ACL applied during the transfer, but note that per-object source ACLs don't map one-to-one to the destination.

How long does it take to restore a large Space?

It depends on object count, total data size, the bandwidth of the machine running the restore, and Spaces API rate limits. A bucket with 50,000 small objects may take longer than a bucket with 100 large files, because the per-object API overhead dominates. As a rough guide, rclone with --transfers 8 on a machine with a 1 Gbps uplink can transfer several hundred gigabytes per hour on large objects. For very large buckets, run the restore from a cloud VM in the same region as the destination Spaces bucket to minimize latency and egress cost.

Can I restore a Space to a different region?

Yes. The restore process is the same regardless of destination region. Set the --endpoint-url to the new region's endpoint (for example ams3.digitaloceanspaces.com instead of nyc3.digitaloceanspaces.com) and create the destination Space in the new region first. Keep in mind that your application's hardcoded Spaces URLs will reference the old region and will need to be updated. CDN distributions are also region-specific and need to be recreated.

What if my off-site backup is corrupted?

Run rclone check against your backup source before starting the restore. If the check reports mismatched checksums or missing objects, the backup is unreliable. At that point your options depend on whether you have multiple backup copies (a second destination, older backup snapshots), whether versioning was enabled on the original Spaces bucket, or whether any downstream caches still hold a copy of the data. This is why testing your restore procedure before you need it is the only way to know it works. See what DigitalOcean native backup doesn't cover for how to build a backup posture with multiple layers.


This article is part of The complete guide to DigitalOcean backup, an honest, practical reference from the team that backs up DigitalOcean every day.