DocsS3 storage providers

S3 storage providers

Set up Wasabi, Amazon S3, Cloudflare R2, Backblaze B2, Spaces, Google Cloud or MinIO for ZoPanel backups, with a bucket-only key and the exact endpoint to enter.

ZoPanel can copy backups to any S3-compatible storage. This page explains what to create at each provider, which values to type into the panel, the smallest set of permissions the key needs, and how to fix the errors you are most likely to see.

What ZoPanel stores in S3

Feature Where to set it up What goes to the bucket
Remote backups Settings → Remote backups, Where to: S3 Every account backup and database backup, encrypted on the server first, plus the daily configuration backup (.zpb)
Incremental backups (restic) Backups page, Incremental backups card, Destination: S3 One restic repository per server, encrypted by restic
Customers' own storage The customer's Backups page, Automatic backups card, Copy to my storage That customer's scheduled backups

Licensing. Remote backups need a Pro license (see Licensing). Incremental backups do not have their own S3 form: when you choose S3 as their Destination, they use the endpoint, bucket and keys saved in Settings → Remote backups, so fill in and test that form first.

Where the files go. With the default Folder prefix zopanel, a server called web1 writes:

Path in the bucket Contents
zopanel/web1/<account>/ Account backups, encrypted before upload (the name stays .tar.gz; the content starts with ZPENC1)
zopanel/web1/<account>/db/ Database backups
zopanel/web1/_panel/ Configuration backups (.zpb)
zopanel/web1/restic/ The incremental (restic) repository
zopanel/web1/_zopanel/ A small test file, written and deleted by Test connection

Several servers can share one bucket, since each one writes under its own hostname. A separate key per server is still safer: see below.

Before you start: the bucket and the key

Create a dedicated bucket. Use it for ZoPanel only. ZoPanel accepts bucket names of 3 to 63 characters: lowercase letters, digits, dots and hyphens. Underscores and capital letters are refused with "invalid bucket name". Pick a region close to the server for faster uploads, or in another country if you want the copies far from the data center.

Create a key that can only reach this bucket. Never use the account's root or master key. The key ZoPanel needs must be able to:

  • check that the bucket exists (HEAD bucket, which needs the list permission on most providers),
  • list objects,
  • upload objects (large files are sent in 64 MB parts),
  • download objects,
  • delete objects (old copies beyond Remote copies to keep per account, the connection test file, and restic's pruning).

It does not need to create or delete buckets, or to see other buckets.

Versioning and object lock. ZoPanel removes old copies itself. If versioning is on, a removed copy becomes a "noncurrent version" and you keep paying for it: add a lifecycle rule that deletes noncurrent versions after a few days. Object lock (immutability) protects copies from someone who steals the key, but ZoPanel's own deletions then only add delete markers, and storage grows until the lock period ends. If you use object lock, keep the lock period short (for example 7 to 30 days) and pair it with a lifecycle rule for noncurrent versions.

Cost tips.

  • Minimum storage duration. Some providers bill each object for a minimum time even if it is deleted sooner: Wasabi bills 90 days on its pay-as-you-go plan, and Cloudflare R2 bills 30 days for its Infrequent Access class (Standard has no minimum). Keep backups in the provider's standard class, and on Wasabi prefer a retention that keeps copies for at least 90 days.
  • Egress. Restores download data, and every Saturday the incremental repository check reads back 2% of it. Most providers bill downloads. Cloudflare R2 has no egress fees.
  • Deduplication. Incremental (restic) snapshots only upload what changed. A full account backup is a complete archive every time, so daily full backups with a long remote retention grow fast.

The ZoPanel form

Settings → Remote backups, with Where to set to S3:

Field What to enter
Provider presets Fills Endpoint, Region and Path-style URLs for AWS S3, Cloudflare R2, Backblaze B2, Wasabi or DigitalOcean. Then correct the region for your bucket.
Endpoint Host name only, such as s3.ap-southeast-1.wasabisys.com. https:// is optional. A port is allowed (minio.example.com:9000), a path such as /bucket is not.
Region The bucket's region. Always fill it in: a wrong region is the most common cause of errors.
Bucket The bucket name
Folder prefix zopanel by default. Use the same value on a replacement server.
Access key, Secret key The bucket-only key. The secret is shown as ******** after saving. It must be typed again when you change the endpoint, bucket or access key.
Remote copies to keep per account 1 to 365 (default 14)
Path-style URLs (R2, MinIO) Sends requests as https://endpoint/bucket/... instead of https://bucket.endpoint/.... Leave off for Amazon S3. ZoPanel already uses path-style for endpoints other than Amazon and Google, so turning it on is harmless for the others.
Plain HTTP (private MinIO only) Only for a MinIO server on a private network. Keys and data then travel unencrypted.

Save with the switch on runs the connection test, and does not save if it fails.

Wasabi

Tested with ZoPanel (October 2026, Wasabi us-east-1): connection test, account and database backups (uploaded encrypted), retention, restoring a deleted account from Browse S3, downloading a backup back to the server, incremental (restic) snapshots with a single-file and a database restore, and configuration backups.

  1. Create the bucket. In the Wasabi console, click Buckets → Create Bucket, enter a name and choose a region.

  2. Create a policy. Click Policies → Create Policy and paste the policy below, with your bucket name:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["s3:ListBucket", "s3:GetBucketLocation"],
          "Resource": "arn:aws:s3:::zopanel-backups"
        },
        {
          "Effect": "Allow",
          "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:AbortMultipartUpload"],
          "Resource": "arn:aws:s3:::zopanel-backups/*"
        }
      ]
    }
    

    Wasabi's own bucket-separation example grants s3:* on the bucket and its objects, plus s3:ListAllMyBuckets so the user can see buckets in the console. A key used only by ZoPanel does not need console access.

  3. Create a sub-user. Click Users → Create User, enter a name and tick Programmatic access only. Skip the group step, attach the policy from step 2 instead of WasabiFullAccess, and click Create User. Copy the access key and the secret key it shows.

  4. In ZoPanel, click the Wasabi preset, then set:

    Field Value
    Endpoint s3.<region>.wasabisys.com, for example s3.ap-southeast-1.wasabisys.com, s3.eu-central-1.wasabisys.com
    Region The same region, for example ap-southeast-1
    Path-style URLs Off

    For US East 1, the endpoint is s3.wasabisys.com (or its alias s3.us-east-1.wasabisys.com) with region us-east-1. Use the endpoint of the region where the bucket was created.

Watch out: on pay-as-you-go, Wasabi bills every object for at least 90 days. With daily backups and the default 14 copies, each deleted copy is still billed for the remaining 76 days. Setting Remote copies to keep per account to 90 costs about the same and gives you three months of restore points.

Amazon S3

  1. Create the bucket. In the S3 console, create a general purpose bucket in the region you want. Leave Block all public access on.

  2. Create the policy. In IAM, create a policy with this JSON (replace the bucket name):

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Sid": "Bucket",
          "Effect": "Allow",
          "Action": ["s3:ListBucket", "s3:GetBucketLocation"],
          "Resource": "arn:aws:s3:::zopanel-backups"
        },
        {
          "Sid": "Objects",
          "Effect": "Allow",
          "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:AbortMultipartUpload"],
          "Resource": "arn:aws:s3:::zopanel-backups/*"
        }
      ]
    }
    

    The bucket ARN (without /*) is for listing, the /* ARN for the objects. Mixing them up gives AccessDenied.

  3. Create an IAM user without console access, and attach only this policy.

  4. Create an access key. Open the user, then the Security credentials tab, and click Create access key. Choose Other as the use case, then Create access key. The secret access key is shown only on this page: copy it or download the .csv file.

  5. In ZoPanel, click AWS S3, then set:

    Field Value
    Endpoint s3.<region>.amazonaws.com, for example s3.ap-southeast-1.amazonaws.com (Singapore), s3.eu-central-1.amazonaws.com (Frankfurt), s3.us-east-1.amazonaws.com
    Region The bucket's region, for example ap-southeast-1
    Path-style URLs Off

    The preset fills s3.amazonaws.com with region ap-southeast-1: correct the region if your bucket is elsewhere, or use the regional endpoint.

Watch out: new buckets store objects in S3 Standard, which has no minimum storage duration. A lifecycle rule that moves backups to Standard-IA or Glacier adds minimum durations and retrieval fees, and Glacier objects cannot be read until they are restored, so ZoPanel cannot restore from them directly.

Cloudflare R2

  1. Create the bucket. In the Cloudflare dashboard, open R2 object storage and create a bucket. Keep the Standard storage class.

  2. Create an API token. On the R2 object storage overview, under Account Details, click Manage next to API Tokens, then Create Account API token.

    • Permissions: Object Read & Write.
    • Limit the token to your bucket. Object permissions can be scoped to specific buckets.
    • Click Create Account API token. Copy the Access Key ID and Secret Access Key: the secret cannot be shown again.
  3. Find your account ID. It is shown in the Cloudflare dashboard.

  4. In ZoPanel, click Cloudflare R2, then set:

    Field Value
    Endpoint <ACCOUNT_ID>.r2.cloudflarestorage.com
    Region auto
    Path-style URLs On (set by the preset)

    A bucket created in the EU jurisdiction uses <ACCOUNT_ID>.eu.r2.cloudflarestorage.com.

Watch out: enter the endpoint without the bucket name. If you paste an address ending in /<bucket>, ZoPanel answers "invalid S3 endpoint". R2 does not charge for egress, which makes restores and the weekly restic check free of transfer costs.

Backblaze B2

  1. Create the bucket. In the Backblaze web console, create a Private bucket. Note the Endpoint shown for it, for example s3.us-west-004.backblazeb2.com.

  2. Set the lifecycle. B2 buckets keep file versions by default. In the bucket's lifecycle settings, choose Keep only the last version so older versions do not keep using space.

  3. Create an application key. Under B2 Cloud Storage → Application Keys, click Add a New Application Key:

    • Allow access to Bucket(s): your bucket only.
    • Type of Access: Read and Write.
    • Tick Allow List All Bucket Names. Bucket-restricted keys need it for S3 tools.
    • Click Create New Key, then copy the keyID and applicationKey. The applicationKey is shown only once.

    The master application key does not work with the S3-compatible API.

  4. In ZoPanel, click Backblaze B2, then set:

    Field Value
    Endpoint s3.<region>.backblazeb2.com, as shown on the bucket, for example s3.us-west-004.backblazeb2.com
    Region The middle part of the endpoint, for example us-west-004
    Access key / Secret key keyID / applicationKey

    The preset fills us-west-004. Your account may be in another region: always copy the endpoint from your bucket.

S3 or "Backblaze B2"? Where to also offers a native Backblaze B2 target. It works for remote backups, but incremental backups need the S3 form. Use the S3-compatible endpoint above if you want both.

DigitalOcean Spaces

  1. Create the bucket in Spaces Object Storage, in a datacenter region such as sgp1, nyc3 or fra1.
  2. Create a limited key. On the Access Keys tab, click Create Access Key:
    • Select access scope: Limited access.
    • Select your bucket and set its Permissions to Read/Write/Delete.
    • Name the key and click Create Access Key. The secret key appears only once.
  3. In ZoPanel, click DigitalOcean, then set:
    Field Value
    Endpoint <region>.digitaloceanspaces.com, for example sgp1.digitaloceanspaces.com
    Region The region slug, for example sgp1

Watch out: use the regional endpoint, not the bucket's own address (<bucket>.<region>.digitaloceanspaces.com). Limited access keys cannot be combined with bucket policies on the same bucket.

Google Cloud Storage

Cloud Storage accepts S3-style requests through its XML API with HMAC keys.

  1. Create the bucket in Cloud Storage, with the Standard storage class and uniform access.
  2. Create a service account (IAM & Admin → Service accounts) with no project roles.
  3. Give it access to the bucket only. On the bucket's Permissions tab, grant the service account:
    • Storage Object Admin (roles/storage.objectAdmin): read, write, delete and multipart uploads,
    • Storage Legacy Bucket Reader (roles/storage.legacyBucketReader): adds storage.buckets.get, which the connection test needs to check that the bucket exists.
  4. Create the HMAC key. Open Cloud Storage Settings, the Interoperability tab, and click Create a key for a service account. Choose the service account and click Create key. Store the secret now: it cannot be recovered later.
  5. In ZoPanel (no preset), set:
    Field Value
    Endpoint storage.googleapis.com
    Region The bucket's location, for example asia-southeast1, or auto
    Access key / Secret key The HMAC access ID / secret
    Path-style URLs Off

MinIO and other self-hosted storage

  1. Create the bucket and a user with the MinIO client. Save the policy below as zopanel.json:

    {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["s3:ListBucket", "s3:GetBucketLocation"],
          "Resource": ["arn:aws:s3:::zopanel-backups"]
        },
        {
          "Effect": "Allow",
          "Action": ["s3:GetObject", "s3:PutObject", "s3:DeleteObject", "s3:AbortMultipartUpload"],
          "Resource": ["arn:aws:s3:::zopanel-backups/*"]
        }
      ]
    }
    
    mc mb myminio/zopanel-backups
    mc admin user add myminio zopanel-web1 '<a long random secret>'
    mc admin policy create myminio zopanel-backups zopanel.json
    mc admin policy attach myminio zopanel-backups --user zopanel-web1
    
  2. In ZoPanel (no preset), set:

    Field Value
    Endpoint minio.example.com or minio.example.com:9000
    Region The server's region, us-east-1 unless you changed it
    Path-style URLs On
    Plain HTTP Only when the MinIO server is on a private network without TLS

Watch out: give MinIO a certificate from a public authority (for example Let's Encrypt). A self-signed certificate fails with a TLS error, because ZoPanel checks certificates and has no option to skip that check.

ZoPanel S3 storage on another server

A second ZoPanel server with the S3 storage component makes a cheap off-site target. Never use the S3 storage of the same server: it would be lost with it.

  1. On the storage server, install S3 storage and publish it on a domain with Publish on a domain (for example s3.example.com).
  2. Create a bucket in S3 storage → New bucket, from an account kept for backups. The bucket gets its own access key that works only for that bucket. The secret is shown once (use Rotate key if it is lost).
  3. In the bucket's settings, leave Delete objects after (days) empty, or set it well beyond your retention. ZoPanel deletes old copies itself, and the incremental repository breaks if the storage deletes its files.
  4. On the server being backed up, set:
    Field Value
    Endpoint s3.example.com
    Region us-east-1
    Path-style URLs On

The bucket's size is limited by that account's package disk size: pick a package large enough for your retention.

Test the connection and make a first backup

  1. Click Test connection. ZoPanel checks that the bucket exists, writes a small file under <prefix>/<hostname>/_zopanel/ and deletes it. "Connection works" means the key can list, write and delete.
  2. Turn the switch on and click Save.
  3. On the Backups page, choose an account and click Create backup. The job log shows Uploading … (encrypted) and then Uploaded … bytes. The file appears in the bucket under <prefix>/<hostname>/<account>/.
  4. Open Backups → Disaster recovery → Show key and store the recovery key outside the server. Without it, nothing in the bucket can be read on another server.
  5. For incremental backups, choose S3 as the Destination in the Incremental backups card and save. The job log shows the repository address (s3:https://<endpoint>/<bucket>/<prefix>/<hostname>/restic). Click Show recovery key and store that key too.

If you later change the endpoint, bucket or keys in Settings → Remote backups, save the Incremental backups card again: restic keeps the S3 settings it was given when it was set up.

Restore from S3

  • One account: Backups → Disaster recovery → Browse S3, open the server, then the account, and click Restore on a backup. See Restoring a whole account.
  • Single files, folders, mail or databases from the incremental repository: the Snapshots (incremental) card on the Backups page. See Restoring.
  • A whole server: connect the same bucket and Folder prefix on a new server, restore the configuration backup from _panel with the recovery key, then the accounts. See Server lost: restore on a new server.

Test a restore on a spare server once in a while. It also proves that the key and endpoint you wrote down still work.

Troubleshooting

ZoPanel shows the storage provider's error after "cannot access bucket:", "write test failed:" or "upload failed:". The first check (does the bucket exist?) is a HEAD request, and providers send no error text for it. So a wrong secret, a wrong region, a skewed clock and a missing permission can all show as cannot access bucket: Access Denied. The full error code appears on the write test, in the upload job log or with any S3 tool using the same key.

Error Likely cause Fix
SignatureDoesNotMatch Wrong secret key, a space copied with it, or a wrong region Paste the secret again (the field keeps spaces). Check the Region. On B2, use the applicationKey, not the master key.
AccessDenied / Access Denied. The key's policy lacks a permission, the policy is attached to a different bucket name, or the R2 token is scoped to another bucket Compare the policy with the examples above: list on the bucket ARN, object actions on bucket/*. On B2, tick Allow List All Bucket Names. On Google Cloud, add Storage Legacy Bucket Reader.
bucket does not exist / NoSuchBucket Bucket name misspelled, or the endpoint is in another region or account Copy the bucket name exactly. Use the endpoint of the bucket's region (Wasabi, B2, Spaces).
invalid bucket name Capital letters, underscores, or fewer than 3 characters Create a bucket with a valid name
invalid S3 endpoint The endpoint contains a path (/bucket) or other characters Enter the host name only, with an optional :port
the bucket is in region … The bucket was created in another region than the endpoint and region you entered (the Wasabi preset fills in ap-southeast-1) Set Region to the region named, and use that region's endpoint, e.g. s3.us-east-1.wasabisys.com.
301 Moved Permanently The endpoint belongs to another region than the bucket's Use the endpoint of the bucket's region.
400 Bad Request, AuthorizationHeaderMalformed, the region '…' is wrong; expecting '…' Region does not match the bucket Set Region to the value the error expects. On R2 it is auto.
RequestTimeTooSkewed, "the difference between the request time and the current time is too large" The server clock is off (S3 refuses requests more than about 15 minutes off) Run zopanel ctl doctor, which checks the clock. Turn on NTP: timedatectl set-ntp true.
no such host, connection timeouts Endpoint misspelled, or the firewall blocks outgoing HTTPS Check the host name with getent hosts <endpoint>. Allow outgoing port 443 (or the MinIO port).
no such host or a certificate error naming <bucket>.<endpoint> Virtual-host addressing: the bucket name is put in front of the endpoint, and that name does not exist Turn Path-style URLs on (R2, MinIO, ZoPanel S3 storage and other self-hosted storage). Leave it off for Amazon S3.
x509: certificate signed by unknown authority, certificate is valid for …, not … Self-signed certificate, or the endpoint name does not match the certificate Use a publicly trusted certificate whose name matches the Endpoint. Only for MinIO on a private network, use Plain HTTP.
re-enter the secret key when changing the endpoint, bucket or access key The saved secret is only reused for the same target Type the secret key again
Incremental backups still write to the old bucket restic kept the settings from when it was set up Save the Incremental backups card again
Storage bill grows although old copies are deleted Versioning, object lock or a minimum storage duration Add a lifecycle rule for noncurrent versions. On B2, choose Keep only the last version. On Wasabi, see the 90-day note above.

← Malware scan Resellers →