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.
Pick a location and its endpoint
Object Storage runs only in Hetzner's three European locations, each with one endpoint for all its buckets:
| Location | Code | Endpoint | Bucket URL |
|---|---|---|---|
| Falkenstein, Germany | fsn1 | fsn1.your-objectstorage.com | <bucket>.fsn1.your-objectstorage.com |
| Nuremberg, Germany | nbg1 | nbg1.your-objectstorage.com | <bucket>.nbg1.your-objectstorage.com |
| Helsinki, Finland | hel1 | hel1.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
- In Hetzner Console, open the project for backups, click Object Storage, then Create Bucket.
- Choose the Location.
- Enter a Name: 3 to 63 lowercase letters, digits and hyphens, unique across all Hetzner customers and fixed once created.
- Set Visibility to private, so every request needs S3 keys.
- 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.
- 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:
[profile hetzner]
region = hel1
endpoint_url = https://hel1.your-objectstorage.com
retry_mode = standard
max_attempts = 5[hetzner]
aws_access_key_id = <access_key>
aws_secret_access_key = <secret_key>endpoint_urlmust match the bucket's location, andregionis its code, as in Hetzner's examples.retry_mode = standardretries throttling error codes such asSlowDownandTooManyRequestsException, timeouts, and HTTP 500, 502, 503 and 504, with exponential backoff;max_attempts = 5allows 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:
{
"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:ListBuckettakes the bucket's ARN; object actions take the ARN ending in/*.s3:PutObjectcovers multipart uploads, ands3:AbortMultipartUploadcleans up a failed one.- Hetzner says to allow
s3:GetObjectVersionalong withs3:GetObjecton a versioned bucket. - Without
s3:DeleteObjectthe server can't delete backups, so lifecycle rules prune them.
aws s3api put-bucket-policy --bucket acme-web-01-backups --policy file://web-01-policy.json --profile hetzner-adminOn 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:
#!/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):
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
30 3 * * * root /usr/local/bin/hetzner-backup.shFor 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:
[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 = trueThen 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:
{
"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
Prefixcovers the whole bucket, existing objects included. Expirationacts 30 days after upload: it deletes the backup or, in a versioned bucket, adds a delete marker and makes the backup a noncurrent version.NoncurrentVersionExpirationdeletes noncurrent versions 7 days later unless they're locked, so each backup lives about 37 days. Hetzner supports onlyNoncurrentDayshere.AbortIncompleteMultipartUploadremoves unfinished uploads, which don't show in listings but are billed. The second rule removes delete markers with nothing left behind them.
aws s3api put-bucket-lifecycle-configuration --bucket acme-web-01-backups --lifecycle-configuration file://lifecycle.json --profile hetzner-adminWithout 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:
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
aws s3 ls s3://acme-web-01-backups/ --recursive --human-readable --summarize --profile hetznerHetzner 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:
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 hetznersha256sum -c web-01-2026-10-04.tar.gz.sha256 && tar -tzf web-01-2026-10-04.tar.gz > /dev/null && echo OKsha256sum -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:
| Item | EUR | USD |
|---|---|---|
| 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 price | 1 TB of storage (744 TB-hours in a 31-day month) and 1 TB of egress | Same |
| 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 month | Working | Cost |
|---|---|---|
| A nightly 20 GB archive, each kept about 37 days | About 740 GB stored, inside the quota | €6.49 |
| 2.5 TB stored all month | 1,860 TB-hours − 744 included = 1,116 × €0.0087 = €9.71 | €16.20 |
| Plus a 300 GB restore to a server at another provider | Inside 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.
AccessDeniedfor a server's key: the policy has the wrong project ID or key, or lacks the action.Missing required header for this request: Content-MD5or checksum errors: newer AWS CLI versions add checksums by default, which not every S3-compatible service accepts. Addrequest_checksum_calculation = when_requiredandresponse_checksum_validation = when_requiredto 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.comorhttps://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:
- Hetzner Docs: Object Storage overview (endpoints, billing model, limits)
- Hetzner Docs: Creating a Bucket
- Hetzner Docs: Generating S3 keys
- Hetzner Docs: Using S3 compatible CLI tools (AWS CLI, rclone, s3cmd)
- Hetzner Docs FAQ: S3 credentials (How do I restrict access per key?)
- Hetzner Docs FAQ: Buckets & objects (names, protection, Object Lock, lifecycle)
- Hetzner Docs FAQ: General (locations, redundancy, encryption, NBG limits)
- Hetzner Docs: List of supported actions
- Hetzner Docs: Applying lifecycle policies
- Hetzner Docs: Versioning
- Hetzner Docs: Object Lock: Retention
- Hetzner Docs: Troubleshooting HTTP 400
- Hetzner Docs: Cloud locations and network zones
- Hetzner: Object Storage product and pricing page
- rclone docs: Amazon S3 (Hetzner provider, no_check_bucket, force_path_style)
- rclone changelog (v1.72.0 adds the Hetzner provider)
- AWS CLI User Guide: Configuration and credential file settings
- AWS CLI User Guide: Retries
- AWS SDKs and Tools Reference Guide: Data integrity protections for Amazon S3
- AWS CLI reference: s3api put-bucket-policy
- Amazon S3 User Guide: Multipart upload and permissions
- AWS CLI reference: s3api put-bucket-lifecycle-configuration
- AWS CLI reference: s3api put-object-lock-configuration
- AWS CLI reference: s3api get-object-retention
- AWS CLI reference: s3api list-object-versions
- AWS CLI reference: s3api put-bucket-versioning
- AWS CLI reference: s3 ls
- AWS CLI reference: s3 cp