Backups
Server backups are made on the node and sent off it, to an S3 bucket or an SFTP server. They’re deduplicated in chunks, compressed, and encrypted on the node before upload.
Backup storage
Fleet → Backup storage → Add storage. Each location backs up to exactly one destination; locations without one are flagged.
| Field | S3-compatible | SFTP server |
|---|---|---|
| Name | 2–60 characters | 2–60 characters |
| Endpoint / Host | e.g. s3.eu-central-1.wasabisys.com |
Host name or IPv4, optional :port |
| Anywhere on your network, but not a link-local address such as 169.254.169.254 (the cloud metadata service), nor the panel's own port on the controller (8080). | ||
| Bucket / Directory | The bucket | The directory |
| Region | Optional | |
| Username | Access key ID | Username |
| Secret | Secret access key | The panel's SSH key (see below), or a password or private key of your own |
| Quota | Optional, in GB | Optional, in GB |
| Locations | Node groups that back up here | Node groups that back up here |
Secrets are write-only and kept encrypted with the controller’s secret.key.
Test uploads, reads back and deletes a test file, and warns when a destination is more than 90% full. The page shows used space, how many backups are stored and a health status (Healthy, Slow or Unreachable).
Signing in to SFTP with the panel’s SSH key
For an SFTP server, Sign in with → The panel’s SSH key is the default. The panel makes one ed25519 key the first time it’s needed, keeps the private half with its encrypted secrets, and shows the public half to paste on the backup server, as a line for ~/.ssh/authorized_keys of the backup user:
restrict ssh-ed25519 AAAA… vorticpanel-backups
restrict lets the key use SFTP but not open a shell or forward ports. The page also gives a command to run as root on the backup server that adds it for the user you entered. The same key works for every SFTP storage you add. Saving tests the connection, so add the key first. Nodes never get the key or a password: they read and write SFTP storage through the controller, one file at a time. See What a node can reach for what that means if a node is broken into.
Over the API: GET /admin/backup-storage/ssh-key returns { publicKey }, and a destination created with auth: "panel_key" needs no secret. Storage added before this keeps signing in with its own password or key until you switch it.
SFTP host keys
An SFTP server’s host key is recorded at its first test, and the controller and nodes refuse any other key. If it changes, the page shows both keys. Check the new one on the backup server, then choose Trust the new key:
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
A destination can only be removed once it has no locations and holds no backups.
Backup plans
Catalog → Backup plans. A package has a default plan, and staff or a reseller can set a different plan on one server.
| Field | Options | Default |
|---|---|---|
| Name | 2–48 characters | |
| Description | Shown to customers, up to 160 characters | |
| How often | Every 1, 2, 3, 4, 6, 8 or 12 hours; daily; weekly; monthly (days 1–28) | Daily |
| Time | HH:MM in UTC, for daily, weekly and monthly | 03:00 |
| Type | Incremental, or Full every time. Monthly plans are always full. | Incremental |
| A full backup every | 1–31 days, for incremental plans | 7 |
| Keep | 1–180 restore points | 14 |
| On-demand backups | How many full backups customers may keep with Back up now, 0–10 | 2 |
| Compression | zstd (fast, recommended), gzip (smaller, slower), or none | zstd |
| Store in | Each location’s backup storage, or one destination | Each location’s |
| Customers can move the time and pause | On | |
| Billing product | Optional, e.g. whmcs:addon:5 |
How plans run
- At each plan’s time, every server on it gets one backup: full, or an incremental on the newest chain.
- At most two scheduled backups run at once per node; the rest wait their turn.
- Missed times while the controller was down collapse into one run.
- A suspended server’s time is skipped. A busy server (reinstalling, moving) runs as soon as it’s free.
- Once a run completes, retention removes what the plan no longer keeps. Incremental backups are removed a whole chain at a time, so everything kept can still be restored.
Editing a plan applies to every server on it from the next run. Archiving a plan moves its packages and servers to no plan and keeps their existing backups.
Backup add-ons
A billing panel sells a backup add-on with PUT /servers/{id}/backups/plan, with a plan ID, null for no backups, or "package" for the package’s default.
Encryption
Each backup storage has its own root key, kept with the controller’s encrypted secrets. Nodes compress, then seal every chunk and manifest with AES-256-GCM, bound to its name, before upload. Chunks are named by HMAC, not plain SHA-256, so the storage provider can’t tell what’s inside. Every piece starts with PNL1.
The root key never leaves the controller. Each server’s backups, and each custom image, are sealed with a key of their own, derived from the root key and their folder in storage (servers/<server ID>/ or images/<image ID>/). A node gets only the key of the task it’s running: the server it backs up or restores, or the image it saves or installs.
What a node can reach
If someone breaks into a node, they get the keys of the servers and images that node has worked with, and nothing else. They can’t read other servers’ backups, and they can’t change one so that it still restores: a restore checks every piece against that server’s key and stops at anything changed.
How much of the storage itself they can reach depends on its kind:
- S3-compatible: nodes never get the access key. For each piece a task needs, the controller hands the node a signed link to that one object, valid for that task. This is the stronger choice.
- SFTP: the node never gets the storage’s login. It reads and writes through the controller, with links that only reach the files of the task it’s running, for as long as that task runs; the controller signs in to SFTP itself. A broken-into node can’t list, delete or overwrite other servers’ backups.
Even so, give the panel an SFTP account that can reach the backup directory and nothing else on that machine, for example with ChrootDirectory and ForceCommand internal-sftp in its sshd_config. Keep a copy of important backups somewhere the nodes can’t reach, or use S3-compatible storage with versioning or object lock if the provider has it.
What’s checked when a backup is restored or downloaded
Whoever can write to the storage could put files there of their own. So a backup is only read the way the controller recorded it being made:
- Sealed, always. Once a storage has a key, a backup or image without its seal is refused, even one from an earlier version. A plain manifest there was written by someone without the key.
- The same disk. The controller keeps each backup’s SHA-256 from when it was made. A restore stops if the manifest names another one, and once the node has written the disk it reads it back and fails the restore if it doesn’t match. On file storage (qcow2) the disk is checked before it replaces the server’s; on block storage such as LVM or ZFS it’s checked after, so a failed check leaves the server off with the copy it was given. Restore another backup then.
- Downloads too. The controller checks the stream against the same SHA-256 and holds back the last piece until it matches. One that doesn’t is cut short, so it never arrives whole, and the controller’s log says why.
- No piece unpacks past its size. A chunk can’t decompress to more than 4 MiB. One that tries is treated as damaged rather than filling the node’s or controller’s memory.
Custom images saved by earlier versions have no SHA-256 recorded, so they’re checked piece by piece only.
Backups made before per-server keys
Backups and images saved by earlier versions were sealed with one key for the whole storage. They still restore and download. To restore one, the controller has to give that storage-wide key to the node doing the restore, for that task only, so until those backups age out, restoring one carries the old risk: a broken-into node that restores an old backup could read the other old backups in that storage. It can’t read anything made since, which uses the new root key.
After the upgrade, each server’s next backup is a full one, even when an incremental is due, because the node can’t build on a backup it doesn’t have the key for. Incrementals carry on as usual after that. Old backups are removed by retention as before, and nothing has to be done by hand.
Backups from before backups were encrypted at all (October 2026) were plain. In storage that has had a key since, they can’t be restored or downloaded any more: the controller can’t tell one of them from a plain backup someone put there in place of a sealed one. Take a new backup of those servers. Storage that has never had a key reads them as before.
What customers can do
On a server’s Backups tab: see the plan and backup chains, Back up now, restore in place or onto another of their servers (with at least as much disk), and download a backup as a qcow2 image through a signed link that lasts an hour. When the plan allows it, they can move the time or pause the schedule.