VPS Snaps

Feature catalogue

Everything it does.
And where each thing stops.

38 capabilities across six categories, every one of them shipped and checked against the source. Where something is bounded, the bound is written next to it rather than left for you to find at 3am.

Six categories

Skip to the part you came for. Everything below is a capability that exists in the product today; the last section is the list of things that do not.

Seven different ways to capture a system

Each one talks to a different thing — a provider's API, a shell over SSH, the Kubernetes API, or an agent you run yourself. Five are job types you pick from; detection creates jobs for you, and the Agent is how a job reaches a machine we cannot. They share a schedule engine, a storage destination and a run history, and nothing else.

Provider snapshots
One connection to DigitalOcean, Hetzner Cloud, Vultr, Linode, AWS EC2, Google Compute Engine or Microsoft Azure covers every instance in that account. Each run calls the provider's own snapshot API, so the image is created inside your account and stays there whether or not you keep paying us.
Where it stops: Crash-consistent, not application-consistent — nothing reboots or freezes the machine to image it. Linode captures a single disk per job, so you choose which one.
Database backups
pg_dump for PostgreSQL, mysqldump for MySQL and MariaDB, mongodump for MongoDB, executed on the server itself over SSH. The SQL dumps are piped through gzip; mongodump compresses its own archive. Either way the compressed stream goes straight into your bucket as it is produced.
Where it stops: The dump runs as the database user you supply, and the client tool has to already be installed on the server. We do not connect to your database over the network.
Docker volume backups
Every named volume is archived by a throwaway alpine container — docker run --rm -v volume:/data … tar czf — so nothing has to be stopped. Your docker-compose.yml files and docker inspect output for named containers are collected alongside the volume data.
Where it stops: A volume that fails to archive is recorded as a warning and the run continues with the rest. Read the log, not just the status badge.
File backups
Any absolute path on the server — /etc, a web root, an uploads directory, a certificate store — tarred and gzipped over SSH with your own tar --exclude patterns applied. A path that has moved or been deleted is logged and skipped instead of failing the whole run.
Application detection
Scan a server and VPS Snaps searches /var/www, /home/*/public_html, /home/*/htdocs, /home/master/applications/*/public_html, /home/forge/* and /srv/www for wp-config.php, .env and config.production.json. A recognised install becomes a paired file job and database job, with the credentials read out of the application's own config file.
Where it stops: WordPress, Laravel, Craft CMS and Ghost are recognised by name and have their database details extracted. Anything else is still offered as a file job — you fill in the database side yourself.
Kubernetes backups
Connect a cluster with a service-account token; there is nothing to install. Per namespace we export ConfigMaps, Secrets, Services, Deployments, StatefulSets, DaemonSets, Ingresses, Jobs, CronJobs and PersistentVolumeClaims to your bucket, then attempt a CSI VolumeSnapshot for each PVC whose StorageClass has a matching VolumeSnapshotClass.
Where it stops: If Velero is already running in the cluster we drive its Backup CRD instead, because it moves volume data and we do not. Volume snapshots are best-effort: a PVC with no matching snapshot class is skipped and logged.
Agent for private infrastructure
For machines with no inbound path at all. A small Docker container you run on the box holds an outbound WebSocket to us — no public IP, no port forward, no firewall exception — takes the job, runs it locally, and uploads directly to your bucket.
Where it stops: Database, Docker and File jobs only. Provider snapshots and Kubernetes go through their own APIs and never needed a way in.

One scan, matched file and database jobs

Application detection exists because the tedious part of setting up a WordPress or Laravel backup is not the schedule — it is copying a database name, user and password out of a config file without a typo, twice, for every site on the box.

The scan reads wp-config.php with its PHP defines, .env for Laravel and Craft (including Craft’s CRAFT_DB_* prefixes), and Ghost’s config.production.json, then proposes a file job for the document root and a database job for what it found. Sensible excludes come with it — vendor, node_modules, storage/logs.

More on application detection

When it runs, and what gets thrown away

One scheduler ticks every minute for every job type. Retention is set per job, in days, and applied after a run succeeds — never speculatively, and never to something we did not create.

Seven cadences from one dropdown
Manual only, every 5 minutes, hourly, every 12 hours, daily, weekly, or monthly. Daily, weekly and monthly jobs also take a time of day.
Where it stops: How fast you are allowed to go is a plan limit, checked when you save the job rather than silently ignored at run time.
Anchored to your timezone
Daily, weekly and monthly runs fire at a wall-clock time in an IANA timezone you choose, and the next run is recomputed against that zone each time. 02:00 stays 02:00 through a daylight-saving change instead of quietly sliding an hour.
Intervals measured from the last run
Every-5-minutes and every-12-hours are a plain elapsed interval, not a clock alignment. A run that takes eleven minutes pushes the next one out rather than stacking a queue behind it.
Overlap guard
A job that already has a pending or running execution is skipped on the next tick and the reason is logged. A backup that takes longer than its own interval cannot start on top of itself.
Run on demand
Trigger any job by hand before a migration or a risky deploy. It uses exactly the same code path as the scheduled run, which is what makes it a real test of the credentials, not a dry run.
Pause without deleting
Disable a job and the scheduler stops picking it up. Its configuration, run history and everything it has already written stay exactly where they are.
Retention that prunes the provider side
Choose a window between 1 and 365 days. After each successful run, older copies are deleted through the provider's own API: DigitalOcean snapshots, Hetzner images, Vultr snapshots, Linode disk images, EC2 AMIs together with their backing EBS snapshots, and GCE machine images. Kubernetes VolumeSnapshots we created are pruned the same way, and a Velero backup is handed a matching TTL so its own controller expires it.
Where it stops: Archives in your own S3 bucket are not deleted by us. The .sql.gz and .tar.gz objects are yours, in your account, under your keys — put a lifecycle rule on the bucket if you want them aged out.

The whole schedule fits on one form

Pick a type, name the job, choose a cadence, a retention window in days, a time of day and a timezone. That is the entire schedule surface — there is no separate policy object, no schedule library, and no cron syntax to get wrong.

Retention is a property of the job rather than of the plan, which means a nightly snapshot kept for seven days and a weekly archive kept for ninety can live side by side on the same server.

Your bucket, your keys, your bill

There is no hosted storage tier and no plan that includes one. Every archive lands in an S3-compatible bucket you own, which is also why cancelling VPS Snaps does not take your backups with it.

Seven storage targets
AWS S3, Cloudflare R2, Backblaze B2, DigitalOcean Spaces, Wasabi and UpCloud Object Storage each have a preset, and a custom S3-compatible option takes any other endpoint. Path-style addressing is used automatically for custom endpoints.
Tested before it is trusted
Testing a destination writes a small object with your credentials and deletes it again, then stamps the destination as verified with the time it happened. An unverified destination is visible on the list rather than a surprise at 2am.
Streamed, never staged
Database, Docker and file jobs pipe the remote command's stdout straight into an S3 multipart upload. Peak memory is the size of the multipart queue — tens of megabytes — not the size of the backup, and nothing is written to disk on our side or left behind on yours.
Checksummed in flight
Every archive is MD5-hashed as the bytes stream past. The digest, the exact byte count and the full object key are stored on the run, so you can verify what actually landed instead of trusting a green tick.
Predictable object keys
Objects always land at prefix/job-id/filename, with an ISO timestamp in the filename — for Docker, File and Kubernetes jobs that filename also carries the job type, e.g. docker-job-id-timestamp.tar.gz. Sortable, greppable, and specific enough to write a bucket lifecycle rule against.
Credentials encrypted at rest
Access key and secret key are sealed with AES-256-GCM before they touch the database and are never rendered back into a form after saving.

Several destinations, different jobs

Destinations are separate records, so a nightly database dump can land in cheap object storage on one provider while a weekly full archive goes somewhere else entirely — which is the only version of “off-site” that survives losing an account.

Every destination carries its bucket, region, optional prefix and — for custom endpoints — the endpoint URL, plus the timestamp of its last successful test.

Told where you already look

Alerting is per job and per event, so a chatty five-minute job does not have to shout on every success while a nightly database dump stays silent when it breaks.

Email on failure or success
Set independently on each job: notify on failure (on by default), notify on success (off by default), or neither. Failure mail carries the error text the run actually recorded.
Four chat platforms, on every plan
Paste a Slack, Microsoft Teams, Google Chat or Discord incoming-webhook URL. The payload carries both a text and a content field, which is precisely why one message format works across all four without you telling us which is which. Free plan included.
Custom webhook endpoint
Any other HTTPS receiver, on the Agency plan. Same JSON body, ten-second timeout, non-2xx treated as a delivery failure.
Owner fallback
Configure no destinations at all and a failed run still emails the workspace owner. There is no configuration in which a failure goes nowhere.
Public status page
The /status page publishes component health, active and recent incidents, and daily uptime history. The scheduler writes its heartbeat only after a tick that actually finished enqueuing work, so a process that stays alive while failing cannot certify itself as healthy.
Missed-run alert
If an enabled job is more than 15 minutes past its scheduled time with nothing dispatched, you get an alert on the same channels as a failure — a stuck run blocking the next dispatch, a disconnected agent, and a scheduler outage all trip it, since they leave the same symptom. It re-alerts at most once a day per job while the gap stays open, and clears itself the moment the job runs again.

What holds the keys

Running backups means holding credentials for everything you own. The parts of the system that store, hash and expire those credentials are worth being specific about.

AES-256-GCM on every secret
Provider API tokens and OAuth refresh tokens, SSH private keys, server passwords, database passwords, storage access and secret keys, cluster CA certificates and service-account tokens, TOTP secrets. All encrypted before insert, none returned in plain text afterwards.
SSH keys generated or brought
Create a keypair in the dashboard or paste an existing private key. The public key and its fingerprint are shown so you know exactly what to authorize on the far end, and password authentication is available where a key is not an option.
Two-factor authentication
TOTP on a 30-second period with six digits, plus single-use backup codes stored as bcrypt hashes. The intermediate token issued after a correct password expires on its own if the second factor never arrives.
Tokens stored as hashes
API keys are kept as a SHA-256 digest alongside a twelve-character display prefix and an optional expiry date; agent tokens use the same scheme. The raw value is shown once, at creation, and cannot be recovered from us afterwards.
Rate-limited authentication
Sign-in and registration are capped at ten attempts per IP address per fifteen minutes, and password reset at five per hour.
Administrative audit trail
Actions taken through the admin surface — plan edits, subscription changes, incident publishing — are written to an audit log with the actor, the target, the IP address and a timestamp.

Proof it ran, and evidence when it didn't

A backup product is only worth what its failure reporting is worth. Every run keeps its own record and its own log, and both survive long after the night in question.

Full run history
Status, start and finish time, duration, byte size, checksum, the exact storage path, the provider's own snapshot identifier where there is one, and what triggered the run — kept per job, not rolled up into a counter.
Step-by-step run logs
Each run writes timestamped info, warn and error lines as it goes: the SSH connection, each volume archived, each path skipped, the byte size on completion, the retention deletions. A failure keeps the underlying error text — the provider's or the shell's — instead of replacing it with a generic message.
Connection tests everywhere
Servers, storage destinations, Kubernetes clusters and provider connections each have a test that performs the real operation and records the result with a timestamp. Kubernetes tests also detect whether Velero is present and remember the answer.
Resource browser per provider
Once a provider is connected, the instances in that account are listed for you to pick from, so a job is built by choosing a server rather than pasting an ID you looked up somewhere else.
A job list that reads like an ops board
Type, schedule, retention window, last run status, next scheduled run and whether the job is paused — visible on one row, for every job, without opening anything.
Guided first setup
A three-step checklist — connect a provider or server, add a storage destination, create a backup job — that marks itself off as each piece lands, so a half-configured account is obvious rather than dormant.

What a run actually records

Status, duration, size and MD5 checksum sit above the storage key the archive was written to, and below that is every line the worker emitted, in order, with its level intact.

The warn line in that log is the interesting one: a path that no longer exists was skipped and the run carried on. You are told, the backup still happened, and you can decide whether the missing directory matters — which is a better outcome than a red cross at 2am for a folder someone deleted last month.

Where we stop

The other half of a catalogue. These are real gaps in the product today, written down so you can rule us out quickly if one of them is a dealbreaker.

Restore covers database and file backups only

Database and file runs can be restored from the dashboard onto any server in your workspace, with a check-only mode that downloads and verifies the archive without changing anything. Docker and Kubernetes runs cannot yet, and a provider snapshot is rebuilt from your provider’s console rather than here. A database restore replaces the tables the dump contains, so a table created after the backup survives it — you get the backup’s contents back rather than a byte-exact reset. Everything remains an ordinary .sql.gz or .tar.gz with the manual commands in the help centre, which is why your recovery path still does not depend on us being online.

Retention never touches your bucket

Provider snapshots, Linode images, EC2 AMIs and Kubernetes VolumeSnapshots we created are pruned automatically. Objects in your S3 bucket are not. If nothing else deletes them, they accumulate — a bucket lifecycle rule is the right tool and it belongs to you, not to us.

API keys exist; the public API does not yet

You can mint, name, expire and revoke keys in settings today, and they are stored as hashes. The versioned endpoints they are meant to unlock are not published yet, so a key currently reaches nothing. We would rather write that here than let you discover it after building against it.

Cron is not in the dashboard

The schedule dropdown covers seven cadences and handles the overwhelming majority of jobs. A job on a raw cron expression is displayed but cannot be edited from the schedule form, and cron is evaluated in UTC — unlike the presets, which are timezone-aware.

SSH host keys are not pinned

Connections authenticate with your key or password, and the credentials are encrypted at rest, but we do not yet pin or verify the server's host key between runs. If that is part of your threat model, it is not covered today.

Snapshots also are not off-site from the provider that holds them, and are not portable between providers. The provider pages spell out what a snapshot is and is not, per provider.

Read the detail, or just start

Every backup type has its own page covering the commands it runs, the failure modes and the trade-offs. Or skip all of it and connect a server — the free plan needs no card.

Chris Bennett
We manage a mix of VPS instances, Docker workloads, and traditional Linux servers, so backups used to be scattered across several different processes. VPS Snaps brought all of that into one dashboard and made it much easier to see what ran, what failed, and what needs attention.
Chris BennettSystems Administrator

Pick the capabilities you need. Ignore the rest.

One server, backed up free, forever — no card, no trial clock. Add the second when the first one has proven itself.

No credit card required. Cancel anytime.