VPS Snaps

How to use Hetzner Object Storage for server backups

Hetzner Object Storage is S3-compatible storage in Falkenstein, Nuremberg and Helsinki, at https://fsn1.your-objectstorage.com, nbg1 or hel1; as of October 2026 it costs €6.49 a month before VAT, with 1 TB of storage and 1 TB of egress included. Every S3 key can read, write and delete every bucket in its project, so keep the backup bucket and the servers' keys in separate projects and grant each key upload and read with a bucket policy. Lifecycle rules then prune old backups, and Object Lock can make them undeletable for a set time.

10 min readUpdated Checked against official documentation

Pick a location and its endpoint

Object Storage runs only in Hetzner's three European locations, each with one endpoint for all its buckets:

LocationCodeEndpointBucket URL
Falkenstein, Germanyfsn1fsn1.your-objectstorage.com<bucket>.fsn1.your-objectstorage.com
Nuremberg, Germanynbg1nbg1.your-objectstorage.com<bucket>.nbg1.your-objectstorage.com
Helsinki, Finlandhel1hel1.your-objectstorage.com<bucket>.hel1.your-objectstorage.com

A bucket's data stays in one data center in its location, spread over storage servers with erasure coding that Hetzner says survives three of them failing. Nothing replicates it to another location, so back up a Nuremberg server to Helsinki, for example. The examples use a bucket named acme-web-01-backups in hel1.

Hetzner's FAQ says Nuremberg has temporary limits on upload speed and concurrent requests, answered with HTTP 503, and that clients without retries abort the upload. Set up retries, as below, wherever your bucket is.

Create the bucket

  1. In Hetzner Console, open the project for backups, click Object Storage, then Create Bucket.
  2. Choose the Location.
  3. Enter a Name: 3 to 63 lowercase letters, digits and hyphens, unique across all Hetzner customers and fixed once created.
  4. Set Visibility to private, so every request needs S3 keys.
  5. Set Object Lock to Enabled if you may want locked backups. It can only be turned on now, and it turns versioning on for good.
  6. Click Create & Buy now.

You pay for every hour you have at least one bucket, even an empty one, and buckets can't move between projects. Turn on the bucket's protected property in Hetzner Console so it can't be deleted until you turn it off; a bucket that still holds objects can't be deleted either.

Keys reach every bucket in their project

S3 keys are made only in Hetzner Console: in a project, open Security, then S3 Credentials, then Generate credentials. The secret is shown once.

By default, a key can read, write and delete every bucket in its project, including buckets created later, and Hetzner Console has no per-key permissions. A server's key in the same project as your backups can delete all of them.

Hetzner documents several ways around it. A project per server, holding only its bucket and key, is simple, but the key can still delete its own backups. For backups, keep keys and buckets apart and grant each key what it needs with a bucket policy:

  • Project backups holds the buckets and an admin key, which stays on your workstation and sets policies, lifecycle rules and locks.
  • Project backup-keys holds one key per server and no buckets, so its keys reach only what a policy grants.

Configure the AWS CLI

The endpoint belongs to a location and the key to a project, so use a profile for each pair. On the server:

~/.aws/config
[profile hetzner]
region = hel1
endpoint_url = https://hel1.your-objectstorage.com
retry_mode = standard
max_attempts = 5
~/.aws/credentials
[hetzner]
aws_access_key_id = <access_key>
aws_secret_access_key = <secret_key>
  • endpoint_url must match the bucket's location, and region is its code, as in Hetzner's examples.
  • retry_mode = standard retries throttling error codes such as SlowDown and TooManyRequestsException, timeouts, and HTTP 500, 502, 503 and 504, with exponential backoff; max_attempts = 5 allows five attempts instead of three.
  • Don't add addressing_style = virtual: Hetzner says to leave it out when creating buckets.

Run chmod 600 on both files, since the secret is in plain text. On your workstation, add a hetzner-admin profile the same way with the backups project's key.

Let the server's key upload and read, not delete

A bucket policy names a key as p<project_id>:<access_key>, with the ID of the key's own project, backup-keys, from its Console URL: https://console.hetzner.com/projects/<project_id>/servers. This lets web-01's key list, upload and download, and nothing else:

web-01-policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "ListBackups",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam:::user/p<project_id>:<access_key>" },
      "Action": "s3:ListBucket",
      "Resource": "arn:aws:s3:::acme-web-01-backups"
    },
    {
      "Sid": "UploadAndReadBackups",
      "Effect": "Allow",
      "Principal": { "AWS": "arn:aws:iam:::user/p<project_id>:<access_key>" },
      "Action": [
        "s3:PutObject",
        "s3:AbortMultipartUpload",
        "s3:GetObject",
        "s3:GetObjectVersion"
      ],
      "Resource": "arn:aws:s3:::acme-web-01-backups/*"
    }
  ]
}
  • s3:ListBucket takes the bucket's ARN; object actions take the ARN ending in /*.
  • s3:PutObject covers multipart uploads, and s3:AbortMultipartUpload cleans up a failed one.
  • Hetzner says to allow s3:GetObjectVersion along with s3:GetObject on a versioned bucket.
  • Without s3:DeleteObject the server can't delete backups, so lifecycle rules prune them.
Terminal
aws s3api put-bucket-policy --bucket acme-web-01-backups --policy file://web-01-policy.json --profile hetzner-admin

On the server, aws s3 ls s3://acme-web-01-backups/ --profile hetzner prints nothing for an empty bucket. Upload a test file with aws s3 cp /etc/hostname s3://acme-web-01-backups/test.txt --profile hetzner; aws s3 rm on it should then fail with AccessDenied. If the delete works, the key is in the bucket's project.

Upload backups on a schedule

Name backups by date so nothing overwrites them, and store a checksum beside each. This script archives two directories with tar and uploads both files:

/usr/local/bin/hetzner-backup.sh
#!/bin/sh
set -eu
NAME="web-01-$(date +%F).tar.gz"
cd /var/backups
tar -czf "$NAME" /etc /var/www
sha256sum "$NAME" > "$NAME.sha256"
for f in "$NAME" "$NAME.sha256"; do
  aws s3 cp "$f" "s3://acme-web-01-backups/$f" --profile hetzner --only-show-errors
done
rm "$NAME" "$NAME.sha256"

set -eu stops at the first error, so a failed upload never reaches the rm. Make it executable with chmod 700 and schedule it; the PATH line lets cron find aws (see scheduling backups with cron):

/etc/cron.d/hetzner-backup
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
30 3 * * * root /usr/local/bin/hetzner-backup.sh

For rclone, Hetzner's guide uses the Other provider, which every version has; rclone 1.72 and later also offer provider = Hetzner. no_check_bucket stops rclone trying to create the bucket, which this key can't do:

~/.config/rclone/rclone.conf
[hetzner]
type = s3
provider = Other
access_key_id = <access_key>
secret_access_key = <secret_key>
endpoint = hel1.your-objectstorage.com
region = hel1
acl = private
no_check_bucket = true

Then upload with rclone copy. Avoid rclone sync, which deletes remote files that are gone locally and fails with this key.

Expire old backups with lifecycle rules

With the server unable to delete, a lifecycle configuration prunes. This is Hetzner's documented format with nightly-backup numbers:

lifecycle.json
{
  "Rules": [
    {
      "ID": "expire-backups",
      "Status": "Enabled",
      "Prefix": "",
      "Expiration": { "Days": 30 },
      "NoncurrentVersionExpiration": { "NoncurrentDays": 7 },
      "AbortIncompleteMultipartUpload": { "DaysAfterInitiation": 7 }
    },
    {
      "ID": "remove-delete-markers",
      "Status": "Enabled",
      "Prefix": "",
      "Expiration": { "ExpiredObjectDeleteMarker": true }
    }
  ]
}
  • An empty Prefix covers the whole bucket, existing objects included.
  • Expiration acts 30 days after upload: it deletes the backup or, in a versioned bucket, adds a delete marker and makes the backup a noncurrent version.
  • NoncurrentVersionExpiration deletes noncurrent versions 7 days later unless they're locked, so each backup lives about 37 days. Hetzner supports only NoncurrentDays here.
  • AbortIncompleteMultipartUpload removes unfinished uploads, which don't show in listings but are billed. The second rule removes delete markers with nothing left behind them.
Terminal
aws s3api put-bucket-lifecycle-configuration --bucket acme-web-01-backups --lifecycle-configuration file://lifecycle.json --profile hetzner-admin

Without Object Lock, turn versioning on yourself with aws s3api put-bucket-versioning --bucket acme-web-01-backups --versioning-configuration Status=Enabled --profile hetzner-admin, so a deleted backup stays recoverable for the 7 days.

Lock backups with Object Lock

Versioning keeps deleted backups as older versions, but a key with full access, like the admin key, can delete those too. Object Lock blocks that for a set time. On a bucket created with it, set a default retention for new uploads:

Terminal
aws s3api put-object-lock-configuration --bucket acme-web-01-backups --object-lock-configuration '{"ObjectLockEnabled": "Enabled", "Rule": {"DefaultRetention": {"Mode": "GOVERNANCE", "Days": 14}}}' --profile hetzner-admin
  • GOVERNANCE: a version can't be deleted for 14 days, except by a key allowed to bypass governance mode that sends --bypass-governance-retention.
  • COMPLIANCE: nobody can delete a version or shorten its retention until the date passes.

Upload a test file with the server's key; aws s3api get-object-retention --bucket acme-web-01-backups --key test.txt --profile hetzner-admin should then show the mode and a RetainUntilDate 14 days out.

Switch to COMPLIANCE only once the numbers are right: a compliance-locked version is stored, and billed, until its date. Keep the lock shorter than the lifecycle window, as here: locked for 14 days, removed after about 37.

Verify and restore a backup

Terminal
aws s3 ls s3://acme-web-01-backups/ --recursive --human-readable --summarize --profile hetzner

Hetzner Console's totals lag 15 to 20 minutes and include old versions and unfinished uploads. To prove a backup restores, download it with its checksum file and check both:

Terminal
mkdir -p /tmp/restore-test && cd /tmp/restore-test && aws s3 cp s3://acme-web-01-backups/ . --recursive --exclude "*" --include "web-01-2026-10-04.tar.gz*" --profile hetzner
Terminal
sha256sum -c web-01-2026-10-04.tar.gz.sha256 && tar -tzf web-01-2026-10-04.tar.gz > /dev/null && echo OK

sha256sum -c compares the download with the hash taken before upload, and tar -tzf reads the whole archive. Then extract it and check the files, as in testing a restore. For an expired or overwritten backup, list its versions with aws s3api list-object-versions and the admin profile, then fetch one with aws s3api get-object and --version-id.

What it costs

As of October 2026, excluding VAT, per account across all projects and locations:

ItemEURUSD
Base price, billed hourly with a monthly cap€0.0104 an hour, at most €6.49 a month$0.0128 an hour, at most $7.99 a month
Included in the base price1 TB of storage (744 TB-hours in a 31-day month) and 1 TB of egressSame
Storage over the quota€0.0087 per TB-hour$0.0123 per TB-hour
Egress over the quota€1.00 per TB$1.20 per TB

Uploads, traffic within Hetzner's eu-central network zone (Falkenstein, Nuremberg and Helsinki) and S3 operations are free. The quota builds up hourly and expires at month end. Objects under 64 kB are billed as 64 kB, and old versions and unfinished uploads count.

A 31-day monthWorkingCost
A nightly 20 GB archive, each kept about 37 daysAbout 740 GB stored, inside the quota€6.49
2.5 TB stored all month1,860 TB-hours − 744 included = 1,116 × €0.0087 = €9.71€16.20
Plus a 300 GB restore to a server at another providerInside the 1 TB of egress€0 extra

Limits and common errors

  • Objects up to 5 TB: up to 5 GB in one PUT, larger in up to 10,000 parts.
  • Per bucket: 100 TB, 50 million objects and 750 requests a second. Per account: 100 buckets and 200 keys.
  • Nothing is encrypted at rest by default; server-side encryption is SSE-C only, with a key sent on every request.
  • Not supported: replication, bucket tagging, and copying objects between buckets.
  • HTTP 400 in the middle of an upload: Hetzner drops a connection that sends nothing for 60 seconds.
  • HTTP 503: the cluster is shedding load. Retries with backoff ride it out.
  • AccessDenied for a server's key: the policy has the wrong project ID or key, or lacks the action.
  • Missing required header for this request: Content-MD5 or checksum errors: newer AWS CLI versions add checksums by default, which not every S3-compatible service accepts. Add request_checksum_calculation = when_required and response_checksum_validation = when_required to the profile.

A Hetzner bucket for a Hetzner server

For a Hetzner Cloud server, a bucket in another location covers a deleted server, a broken upgrade or trouble in one location, and unlike Hetzner's own backups and snapshots, its files restore anywhere.

It doesn't cover the account. Server and bucket share one login and one bill, Object Lock doesn't change who controls the account, and Hetzner itself calls each of its products only one part of a backup strategy. Keep another copy with a different provider, per the 3-2-1 rule, and encrypt backups before upload. The reverse works too: with free uploads, Hetzner is a cheap off-site target for servers elsewhere.

For SFTP, rsync or Borg rather than S3, Hetzner sells Storage Boxes: see how to use a Hetzner Storage Box for backups.

Frequently asked questions

What is the S3 endpoint for Hetzner Object Storage?
https://fsn1.your-objectstorage.com, https://nbg1.your-objectstorage.com or https://hel1.your-objectstorage.com, for buckets in Falkenstein, Nuremberg or Helsinki.
Can I limit a Hetzner S3 key to one bucket?
Not in Hetzner Console: a key reaches every bucket in its project. Keep keys and buckets in separate projects and grant each key access with a bucket policy, or give each server its own project.
Does Hetzner Object Storage support Object Lock?
Yes, with governance or compliance retention and legal hold, but only on buckets created with Object Lock enabled. It turns versioning on permanently.
How much does Hetzner Object Storage cost?
As of October 2026, €6.49 a month excluding VAT, billed hourly, including 1 TB of storage and 1 TB of egress per account. Extra storage is €0.0087 per TB-hour and extra egress €1.00 per TB.
Is Hetzner Object Storage available in the US?
No. As of October 2026 it runs only in Falkenstein, Nuremberg and Helsinki, though servers anywhere can upload to it.

How this was checked

Commands, limits and prices were checked against these official pages, on October 4, 2026: