Setup guide
Everything you must provision before this skill can run โ the exact credentials, permissions, and where to click. Links go to the official consoles and docs, which stay current.
Provision two independent Hetzner credential paths โ a project-scoped Hetzner Cloud API token for the hcloud CLI/Cloud API, and a separate Robot webservice user/password for dedicated bare metal โ plus an SSH key registered in both systems and, for backups, a Storage Box or Object Storage S3 key pair.
You need to already have
- A Hetzner account (accounts.hetzner.com) with completed identity verification and a payment method on file; new accounts ship with low per-project resource limits and outbound port 25 blocked until the first invoice is paid
- Owner or member access to a Hetzner Cloud project in Hetzner Console โ API tokens are minted per project and cannot be shared across projects
- For the bare-metal half only: a Hetzner Robot account with at least one dedicated/auction server. Robot and Cloud are separate products with separate APIs, credentials and billing views under one Hetzner login
- hcloud CLI >= 1.45 installed and on PATH (the skill uses --without-ipv4, --placement-group and label selectors, which older builds do not support)
- curl and jq for the Robot webservice calls (there is no official CLI for Robot)
- An ed25519 SSH keypair; the public key must be uploaded twice โ once to the Cloud project and once to Robot key management โ because the two systems do not share key stores
- Optional for offsite backups: a Storage Box with SSH support enabled, and/or an Object Storage bucket plus S3 credentials
- Optional for the IaC path: Terraform >= 1.5 and the hetznercloud/hcloud provider ~> 1.48, with a remote state backend chosen before first apply
How the pieces connect
Credentials this skill needs
Set these as environment variables. Never paste secrets into a chat or commit them.
| Variable | What it is | Where to get it |
|---|---|---|
HCLOUD_TOKENsecret | Hetzner Cloud project API token format: 64-character alphanumeric string, project-scoped, shown exactly once at creation | Hetzner Console > select your project > Security (left menu bar) > API tokens (upper menu bar) > Generate API token > enter a description > choose Read & Write > copy the token immediately (it cannot be viewed again) |
ROBOT_USERoptional | Robot webservice username (dedicated/bare-metal only) format: The webservice user string displayed on the Robot settings page after activation; it is NOT your Robot web-login email | Robot (robot.hetzner.com) > user menu in the upper right corner > Settings > Web service and app settings > create/read the web service user |
ROBOT_PASSsecretoptional | Robot webservice password (dedicated/bare-metal only) format: Password you set yourself on the web service settings page; used as HTTP Basic auth against https://robot-ws.your-server.de | Robot (robot.hetzner.com) > user menu in the upper right corner > Settings > Web service and app settings > set password. Set it to something distinct from your Robot login password โ three failed Basic-auth attempts block your source IP for 10 minutes |
KEY_FINGERPRINToptional | Robot SSH key fingerprint (for rescue-system boot with key auth) format: MD5 fingerprint of the registered public key, colon-separated hex (e.g. aa:bb:cc:dd:...); the Robot boot/rescue endpoint accepts the fingerprint, not the key body | Robot (robot.hetzner.com) > Server > Key management > add your public key, then copy the fingerprint column. Also retrievable via GET https://robot-ws.your-server.de/key |
BORG_REPOoptional | Storage Box BorgBackup repository URL (offsite backup target) format: ssh://[email protected]:23/./backups/<host> โ note the non-standard port 23 and the ./ relative path prefix | Hetzner Console > Storage Boxes > your Storage Box > note the username uXXXXXX and hostname; enable SSH support in the Storage Box settings before first use |
BORG_PASSPHRASEsecretoptional | BorgBackup repository passphrase format: Free-form passphrase chosen at 'borg init'; lose it and the offsite backup is unrecoverable | Set by you when the repository is initialised (borg init --encryption=repokey-blake2). Store it in your secret manager, never on the machine being backed up |
AWS_ACCESS_KEY_IDoptional | Hetzner Object Storage S3 access key format: S3-style access key issued per project; used with --endpoint-url https://fsn1.your-objectstorage.com (or nbg1/hel1) | Hetzner Console > select your project > Security (left menu bar) > S3 Credentials (upper menu bar) > Generate credentials > copy the access key from the upper text box |
AWS_SECRET_ACCESS_KEYsecretoptional | Hetzner Object Storage S3 secret key format: S3-style secret key, shown exactly once alongside the access key | Hetzner Console > select your project > Security > S3 Credentials > Generate credentials > copy the secret key from the lower text box before closing the dialog; it cannot be retrieved later via Console or API |
Permissions to grant
Read & WriteCloud API token permissionLets the skill issue GET, POST, PUT and DELETE against api.hetzner.cloud/v1 โ creating SSH keys, networks, subnets, firewalls, placement groups, servers, volumes, load balancers, floating IPs, snapshots, and enabling backups.
โณ Use a Read token for the sizing, inventory and cost-modelling paths (server-type list, datacenter list, server describe). A Read token can only issue GET, so it physically cannot provision or delete.
ReadCloud API token permissionSufficient for the whole cost-model and audit half of the skill: hcloud server-type list, hcloud datacenter list, hcloud server list --selector, hcloud server describe, load-balancer health inspection.
โณ This is already the least-privilege option; mint a second Read-only token for CI cost reporting so the provisioning token never leaves the deploy pipeline.
Web service and app settingsRobot account credential (HTTP Basic)Authenticates every call to https://robot-ws.your-server.de โ listing dedicated servers, activating the rescue system, triggering hardware resets, creating vSwitches and assigning servers to them, setting rDNS on dedicated IPs, and querying traffic.
โณ None exists. The Robot webservice is account-wide and unscoped: one credential can reset, reinstall, re-route and order hardware for every server on the account, including server_ordering. If you need blast-radius control, put production metal in a separate Hetzner account rather than trying to scope the credential.
S3 CredentialsObject Storage access key pairAuthorises the offsite backup path โ aws s3 sync ... --endpoint-url https://fsn1.your-objectstorage.com โ used because Hetzner automatic backups are 7 slots inside the same failure domain and are not disaster recovery.
โณ By default each key pair can reach every bucket in the same project. Put backup buckets in a dedicated project so a leaked backup key cannot read or delete production objects.
SSH supportStorage Box feature toggleOpens the extended SSH service on port 23 so BorgBackup and rsync can write the offsite copy; without it the Storage Box only speaks SMB/WebDAV/FTP.
โณ Create a Storage Box sub-account restricted to a single sub-directory and give that to the backup host, instead of handing out the main uXXXXXX account which owns the whole box.
Step by step
- 1
Create the Hetzner account and clear the new-account limits
Register at accounts.hetzner.com, complete identity verification, and add a payment method. New accounts carry modest default per-project limits on servers, volumes and floating IPs, and outbound port 25 is blocked. Raising either is a support/limit request with real lead time โ file it before you plan a go-live, not during one.
Open in the console / docs โ - 2
Create a Cloud project and mint the API token
Open Hetzner Console, create (or select) a project, then Security (left menu bar) > API tokens (upper menu bar) > Generate API token. Give it a description naming the consumer (e.g. 'terraform-prod'), select Read & Write, and copy the 64-character token โ the dialog will not show it again. Export it as HCLOUD_TOKEN.
Open in the console / docs โ - 3
Install the hcloud CLI and pin a named context
Install hcloud (>= 1.45) from the hetznercloud/cli releases, then run: hcloud version; hcloud context create prod (paste the token when prompted); hcloud context use prod; hcloud server list. Empty output is a valid answer for a fresh project; an auth error is not. The context is stored in ~/.config/hcloud/cli.toml โ HCLOUD_TOKEN in the environment overrides it and is what CI should use.
Open in the console / docs โ - 4
Upload the SSH public key to the Cloud project
Run: hcloud ssh-key create --name ops --public-key-from-file ~/.ssh/id_ed25519.pub โ or Hetzner Console > your project > Security > SSH keys > Add SSH key. Do this before creating any server; keys injected at create time are the only ones cloud-init will authorise, and a server created without a key needs a rescue-boot to fix.
Open in the console / docs โ - 5
Enable the Robot webservice user (bare-metal path only)
Log in to Robot at robot.hetzner.com, open the user menu in the upper right corner, choose Settings > Web service and app settings, and create the web service user and password. These are ROBOT_USER and ROBOT_PASS. The Robot API base URL is https://robot-ws.your-server.de and it is HTTPS + HTTP Basic only. The hcloud CLI cannot touch any of it.
Open in the console / docs โ - 6
Register the SSH key in Robot and record its fingerprint
Robot > Server > Key management > add the same public key. Copy the MD5 fingerprint into KEY_FINGERPRINT โ the rescue endpoint (POST /boot/{server-number}/rescue with authorized_key=<fingerprint>) takes the fingerprint, not the key material. Confirm with: curl -su "$ROBOT_USER:$ROBOT_PASS" https://robot-ws.your-server.de/key | jq '.[].key.fingerprint'
Open in the console / docs โ - 7
Provision the offsite backup target
Storage Box path: Hetzner Console > Storage Boxes > create a box, then open its settings and enable SSH support (port 23), and add your public key under the Storage Box SSH keys view. Set BORG_REPO to ssh://[email protected]:23/./backups/<host>. Object Storage path: Console > project > Object Storage > create a bucket, then Security > S3 Credentials > Generate credentials, and use --endpoint-url https://fsn1.your-objectstorage.com.
Open in the console / docs โ - 8
Plan network zones before creating any network
A Cloud network lives in exactly one network zone: eu-central (fsn1/nbg1/hel1), us-east (ash), us-west (hil), ap-southeast (sin). It cannot span zones, and floating IPs must come from the same zone as the servers they attach to. Model multi-region as separate networks joined over WireGuard. Robot dedicated servers are DE/FI only and join a Cloud network through a vSwitch subnet, not directly.
Open in the console / docs โ - 9
Create the vSwitch bridge if metal must reach the Cloud network
Robot > Server > vSwitches > create a vSwitch (note the VLAN ID, in the 4000-4091 range) and assign the dedicated servers to it. Then in Hetzner Console attach that vSwitch to the Cloud network as a vswitch-type subnet. On the metal, bring up the tagged VLAN interface with MTU 1400 and persist it in netplan or systemd-networkd โ ip commands do not survive reboot, and a wrong MTU shows up as large payloads hanging while small requests succeed.
Open in the console / docs โ - 10
Set rDNS and, if you send mail, request the port 25 unblock
Cloud: hcloud server set-rdns <server> --ip <addr> --hostname <fqdn>. Dedicated: PUT https://robot-ws.your-server.de/rdns/<ip> with ptr=<fqdn>, or Robot > Server > IPs > the pencil icon next to the IP. Outbound port 25 is blocked by default on new accounts; open a limit/unblock request from the Console once the first invoice is paid. Missing rDNS is the fastest route to rejected mail and blocked outbound API traffic.
Open in the console / docs โ - 11
Wire the Terraform provider if you are taking the IaC path
Set required_providers { hcloud = { source = "hetznercloud/hcloud", version = "~> 1.48" } } and export HCLOUD_TOKEN (the provider reads it automatically; do not put the token in a .tf file). Choose and name a remote state backend before the first apply. On hcloud_server, set firewall_ids at create time โ attaching a firewall afterwards leaves a window where the box is exposed โ plus placement_group_id, backups, labels and public_net { ipv4_enabled = false }.
Open in the console / docs โ
Check it worked
curl -sS -o /dev/null -w 'cloud=%{http_code}\n' -H "Authorization: Bearer $HCLOUD_TOKEN" https://api.hetzner.cloud/v1/servers && curl -sS -o /dev/null -w 'robot=%{http_code}\n' -u "$ROBOT_USER:$ROBOT_PASS" https://robot-ws.your-server.de/server # expect cloud=200 and robot=200 (robot=404 also proves auth works and simply means no dedicated servers on the account)If you hit an error
{"error":{"code":"unauthorized","message":"unable to authenticate"}}Cause: HCLOUD_TOKEN is empty, truncated, revoked, or belongs to a deleted project. Tokens are project-scoped, so a token minted in project A returns this against project B's resources.
Fix: Re-mint the token in Hetzner Console > project > Security > API tokens > Generate API token and re-run hcloud context create. Verify the exported value is the full 64 characters with no trailing newline (printf '%s' "$HCLOUD_TOKEN" | wc -c).
forbiddenCause: The Cloud token was created with the Read permission, so POST/PUT/DELETE are rejected โ typically hit on hcloud server create, firewall add-rule, or terraform apply.
Fix: Create a second token with Read & Write. Token permission cannot be changed after creation; you must generate a new one and delete the old.
rate_limit_exceededCause: The Cloud API allows roughly 3600 requests per hour per project and the code is polling per-server in a loop instead of filtering server-side.
Fix: Replace per-server loops with a single labelled query (hcloud server list --selector role=app, or GET /v1/servers?label_selector=role%3Dapp) and back off on the RateLimit-Reset response header.
resource_unavailableCause: The requested server type is sold out in that location โ very common for CAX (Ampere) and CCX types in fsn1/nbg1 โ or the spread placement group has no free physical host left.
Fix: Re-check live availability with hcloud server-type list and hcloud datacenter list, then try a sibling location in the same network zone (fsn1/nbg1/hel1 are all eu-central) or a different type family. Do not hardcode SKU names.
resource_limit_exceededCause: The project hit its default quota for servers, volumes, floating IPs or primary IPs. New projects ship with modest limits.
Fix: File a limit-increase request from Hetzner Console; it is a support ticket with real lead time. Meanwhile, free unused primary IPs (--without-ipv4 avoids allocating them at all) and delete orphaned snapshots.
401 Unauthorized (Robot: authentication fails, IP blocked for 10 minutes after 3 failed attempts)Cause: The Robot webservice user has not been activated, or the Robot web-login password is being sent instead of the separate webservice password.
Fix: Robot > user menu (upper right) > Settings > Web service and app settings > set the web service password. After three bad attempts wait out the 10-minute source-IP block before retrying โ retry loops extend it.
403 RATE_LIMIT_EXCEEDEDCause: The Robot API is rate-limited far more tightly than the Cloud API: about 200 requests/hour by default, 50/hour for reset, and 10/hour for failover and wake-on-LAN.
Fix: Cache Robot responses (the server inventory changes rarely), never poll rescue or reset status in a tight loop, and drive automation from a single scheduled sync rather than per-action lookups.
404 BOOT_NOT_AVAILABLECause: Rescue was requested for a server that does not support the chosen boot option, or rescue is already active, or the server-number/IP in the path is wrong.
Fix: GET /boot/{server-number} first to read the available options, DELETE /boot/{server-number}/rescue to clear a stale activation, and confirm the identifier with GET /server.
409 RESET_MANUAL_ACTIVECause: A manual reset request is already queued for that dedicated server, so POST /reset/{server-number} is rejected.
Fix: Wait for the pending reset to complete before issuing another; check GET /reset/{server-number} for the currently available reset types instead of retrying blindly.
Permission denied (publickey)Cause: The SSH key was uploaded to the Cloud project but not to Robot key management (or vice versa) โ the two systems do not share key stores โ or the server was created without --ssh-key and cloud-init authorised nothing.
Fix: Upload the same public key to both Hetzner Console > Security > SSH keys and Robot > Key management. For an already-broken Cloud server, boot the Cloud rescue system and re-add the key; for metal, activate the Robot rescue system with the correct KEY_FINGERPRINT.
unknown flag: --without-ipv4Cause: hcloud CLI is older than 1.45; the flags the skill relies on (--without-ipv4, --placement-group, --firewall) do not exist in older builds.
Fix: Upgrade the CLI from the hetznercloud/cli releases and confirm with hcloud version before running any provisioning script.
Official documentation: https://docs.hetzner.com/cloud/api/getting-started/generating-api-token/ โ
API operations this skill uses (25)
Every call the skill makes, linked to its official reference page.
| Operation | Call | Permission | Ref |
|---|---|---|---|
| List server types (live catalogue for sizing) Sizes the workload from the live catalogue instead of remembered SKU names, and reads the architecture column to decide whether the arm64 CAX line is viable. | GET /v1/server_types (CLI: hcloud server-type list -o columns=name,cores,memory,disk,cpu_type,architecture) | Cloud API token: Read | docs โ |
| List datacenters and locations Determines which server types actually exist in fsn1/nbg1/hel1/ash/hil/sin and which network zone each location belongs to before a network is created. | GET /v1/datacenters (CLI: hcloud datacenter list) | Cloud API token: Read | docs โ |
| Create SSH key Registers the admin key so it can be injected at server-create time; a server created without it needs a rescue boot to recover. | POST /v1/ssh_keys (CLI: hcloud ssh-key create --name ops --public-key-from-file ~/.ssh/id_ed25519.pub) | Cloud API token: Read & Write | docs โ |
| Create private network Creates the single-zone private network that carries all inter-server traffic so backends can drop public IPv4. | POST /v1/networks (CLI: hcloud network create --name core --ip-range 10.0.0.0/16) | Cloud API token: Read & Write | docs โ |
| Add subnet to network Carves the cloud-type subnet inside the chosen network zone; the same call with type=vswitch is how Robot metal is bridged into the network. | POST /v1/networks/{id}/actions/add_subnet (CLI: hcloud network add-subnet core --network-zone eu-central --type cloud --ip-range 10.0.1.0/24) | Cloud API token: Read & Write | docs โ |
| Create firewall and set rules Builds the edge firewall (SSH limited to an admin CIDR, 443 open) before any server exists so the box is never briefly exposed. | POST /v1/firewalls then /v1/firewalls/{id}/actions/set_rules (CLI: hcloud firewall create --name edge; hcloud firewall add-rule edge --direction in --protocol tcp --port 22 --source-ips 203.0.113.10/32) | Cloud API token: Read & Write | docs โ |
| Create placement group Guarantees anti-affinity across physical hosts (max 10 servers per spread group); without it two 'redundant' replicas can land on the same host, and retro-fitting requires a power cycle. | POST /v1/placement_groups (CLI: hcloud placement-group create --name spread-app --type spread) | Cloud API token: Read & Write | docs โ |
| Create server with firewall, network and placement group attached The core provisioning call โ firewalls and placement group are attached at birth, public_net.enable_ipv4=false removes the recurring primary-IPv4 charge, and user_data carries cloud-init. | POST /v1/servers (CLI: hcloud server create --name app-1 --type cax31 --image ubuntu-24.04 --location fsn1 --ssh-key ops --network core --firewall edge --placement-group spread-app --without-ipv4 --label role=app --user-data-from-file cloud-init.yaml) | Cloud API token: Read & Write | docs โ |
| List servers filtered by label selector Server-side filtering keeps the fleet inventory inside the ~3600 req/hour project rate limit instead of looping per server. | GET /v1/servers?label_selector=role%3Dapp (CLI: hcloud server list --selector role=app -o columns=name,type,location,ipv6,status) | Cloud API token: Read | docs โ |
| Enable automatic backups Turns on the 7 rolling backup slots (+20% of server price). The skill treats this as convenience only, not disaster recovery, because the slots share a failure domain with the project. | POST /v1/servers/{id}/actions/enable_backup (CLI: hcloud server enable-backup app-1) | Cloud API token: Read & Write | docs โ |
| Create snapshot image from a server Takes the pre-upgrade rollback point before destructive maintenance; snapshots bill per GB/month and are the golden-image source for rebuilds. | POST /v1/servers/{id}/actions/create_image (CLI: hcloud server create-image db-1 --type snapshot --description "pre-upgrade") | Cloud API token: Read & Write | docs โ |
| Create load balancer, attach to network, add service and label-selector target Provides the HTTP failover path with health checks; label-selector targets auto-enrol new servers and --use-private-ip keeps traffic internal so backends stay IPv4-free. | POST /v1/load_balancers, then /v1/load_balancers/{id}/actions/attach_to_network, /actions/add_service, /actions/add_target (CLI: hcloud load-balancer create/attach-to-network/add-service/add-target --label-selector role=app --use-private-ip) | Cloud API token: Read & Write | docs โ |
| Create volume and attach to server Adds network-attached persistence (10 GB-10 TB, max 16 per server, same-location only) that survives a server rebuild, while WAL and hot data stay on local NVMe. | POST /v1/volumes (CLI: hcloud volume create --name pgdata --size 200 --location fsn1 --format ext4 --automount --server db-1) | Cloud API token: Read & Write | docs โ |
| Create floating IP and assign it to a server Non-HTTP failover VIP. The API assignment is only half the job โ the address must also be added inside the guest with ip addr add, which is why the skill labels this manual failover unless keepalived drives it. | POST /v1/floating_ips, then /v1/floating_ips/{id}/actions/assign (CLI: hcloud floating-ip create --type ipv4 --home-location fsn1 --name vip-db; hcloud floating-ip assign vip-db db-1) | Cloud API token: Read & Write | docs โ |
| Change reverse DNS pointer (Cloud) Missing rDNS is the fastest route to rejected mail and blocked outbound API traffic; the skill sets it on every public IP as part of hardening. | POST /v1/servers/{id}/actions/change_dns_ptr (CLI: hcloud server set-rdns app-1 --ip 2a01:4f8::1 --hostname app-1.example.com) | Cloud API token: Read & Write | docs โ |
| Delete server (teardown) Documented teardown step. The parallel DELETE /v1/volumes/{id} deliberately fails while delete_protection is set, which is the point of enabling it. | DELETE /v1/servers/{id} (CLI: hcloud server delete app-1) | Cloud API token: Read & Write | docs โ |
| List dedicated servers (Robot) Builds the bare-metal inventory. hcloud cannot see these machines at all โ Cloud and Robot are separate products with separate APIs. | GET https://robot-ws.your-server.de/server (curl -su "$ROBOT_USER:$ROBOT_PASS" "$R/server" | jq '.[].server.server_ip') | Robot webservice user (HTTP Basic); ~200 requests/hour | docs โ |
| Activate the rescue system on a dedicated server Boots the PXE rescue environment so installimage can lay down the OS. Returns 404 BOOT_NOT_AVAILABLE if rescue is already active or the identifier is wrong. | POST https://robot-ws.your-server.de/boot/{server-number}/rescue (body: os=linux&authorized_key=$KEY_FINGERPRINT; read current options with GET /boot/{server-number}) | Robot webservice user (HTTP Basic) | docs โ |
| Trigger a hardware reset Reboots the dedicated server into the newly activated rescue system. Returns 409 RESET_MANUAL_ACTIVE if a reset is already queued. | POST https://robot-ws.your-server.de/reset/{server-number} (body: type=hw) | Robot webservice user (HTTP Basic); ~50 requests/hour | docs โ |
| Install the OS onto software RAID (rescue shell) Provisions the dedicated box; cloud-init does not apply to Robot servers. Confirm disks with lsblk first โ the -d flag wipes the named devices immediately with no confirmation prompt. | CLI installimage -a -r yes -l 1 -i images/Ubuntu-2404-noble-amd64-base.tar.gz -d nvme0n1,nvme1n1 -p /boot:ext3:1024M,swap:swap:8G,/:ext4:all -H app-metal-1 | root inside the Hetzner rescue system (SSH key auth via KEY_FINGERPRINT) | docs โ |
| Create a vSwitch and assign dedicated servers to it Layer-2 bridge between Robot metal and a Cloud network; the Cloud side is then attached as a vswitch-type subnet. Requires MTU 1400 on the tagged VLAN interface or large payloads hang. | POST https://robot-ws.your-server.de/vswitch, then https://robot-ws.your-server.de/vswitch/{vswitch-id}/server (VLAN IDs 4000-4091) | Robot webservice user (HTTP Basic) | docs โ |
| Set reverse DNS on a dedicated server IP Same hardening requirement as the Cloud side, but a completely different API โ rDNS for metal cannot be set with hcloud. | PUT https://robot-ws.your-server.de/rdns/{ip} (body: ptr=app-metal-1.example.com) | Robot webservice user (HTTP Basic) | docs โ |
| Query dedicated-server traffic Feeds the egress term of the cost model with measured rather than guessed TB/month โ the single largest lever in a Hetzner-vs-hyperscaler comparison. | POST https://robot-ws.your-server.de/traffic (body: type=day&from=...&to=...&ip[]=...) | Robot webservice user (HTTP Basic); ~200 requests/hour | docs โ |
| Push an offsite backup to a Storage Box with BorgBackup Real disaster recovery outside the project's failure domain, because Hetzner automatic backups are 7 slots in the same failure domain. Note the non-standard port 23 and the --remote-path pin for Borg version compatibility. | CLI BORG_REPO=ssh://[email protected]:23/./backups/db-1 borg create --stats --compression zstd,6 ::'{now:%Y-%m-%dT%H:%M}' /var/lib/postgresql && borg prune --keep-daily 7 --keep-weekly 4 | Storage Box account (or sub-account) with SSH support enabled on port 23 | docs โ |
| Sync backups to Object Storage over the S3 API Alternative S3-compatible offsite target at roughly โฌ6 per TB/month; the skill requires one restore to actually be performed and timed before the backup counts as a control. | CLI aws s3 sync ./backups s3://acme-backups --endpoint-url https://fsn1.your-objectstorage.com | Object Storage S3 credentials (AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY), project-wide by default | docs โ |