Connections (Bring-Your-Own-Keys)

Wire HostSSH to your own storage and platform accounts — BYO buckets, choose how your keys are held, and unlock DNS cutover, web-SSH, alerts, and metrics.

Connections (Bring-Your-Own-Keys)

A Connection is one credential you hand to HostSSH so it can talk to something you already own — an object-storage bucket, a DNS zone, a VPS provider, a Slack channel. Two things to know up front:

  • You bring the keys. BYO-keys is the default posture. Your encrypted .hsi images land in your bucket; cutovers happen in your DNS zone; provisioning uses your provider account. Managed storage and managed provisioning exist as paid add-ons, but nothing is locked behind them.
  • One Connection is required, the rest are upgrades. A single storage backend is all you need to start capturing. Every other Connection — DNS, access, alerts, metrics, git — is additive: it unlocks a capability without being a prerequisite.

There are two classes of Connection: storage backends (where images are written) and platform integrations (everything else). This guide covers both, plus how your keys are held (custody), what each Connection unlocks, and how retention ties in.

No storage Connection means no capture. Add storage first; layer integrations as you need them.

Add a storage backend

Storage is BYO. Pick a provider, scope a credential to one bucket/prefix, and add it. Every image is encrypted before it leaves the host (see Security), so a leaked bucket is not leaked data — bucket-side encryption is redundant and only AWS S3 exposes it below.

# Cloudflare R2 (the default — zero-egress restores)
hostssh connections add storage \
  --provider r2 \
  --account-id <cf_account_id> \
  --access-key-id <key> --secret-access-key <secret> \
  --bucket my-hsi-images --prefix prod/

hostssh connections test <id>     # live probe: writes, reads, deletes a test object
hostssh connections list          # status of every Connection (masked hints only)

Supported backends

restic is the substrate underneath, so any restic-compatible target works. These are the first-class, validated ones. Scope every credential to a single bucket/prefix.

Provider--providerWhat it needsLeast-privilege tip
Cloudflare R2 (default)r2account_id, access_key_id, secret_access_key, bucket, prefixR2 API token scoped Object R/W on one bucket; restores have zero egress fees
AWS S3s3region, access_key_id, secret_access_key, bucket, prefix, optional sse (AES256 | aws:kms | off)IAM user limited to s3:*Object* on bucket/prefix* only
S3-compatibles3_compatibleendpoint, region, access_key_id, secret_access_key, bucket, prefix, force_path_styleCovers DO Spaces (<region>.digitaloceanspaces.com), Vultr Object Storage (<region>.vultrobjects.com), IDrive e2, Storj, and more
MinIOminioendpoint, access_key, secret_key, bucket, force_path_style=true, secure (TLS)Dedicated MinIO service account + bucket policy
Backblaze B2b2endpoint (s3.<region>.backblazeb2.com), access_key_id (keyID), secret_access_key (appKey), bucket, prefixApplication key restricted to one bucket
Wasabiwasabiendpoint (s3.<region>.wasabisys.com), access_key_id, secret_access_key, bucket, prefixSub-user scoped to one bucket
SFTPsftphost, port, user, ssh_key | password, pathDedicated restic user, chrooted to path
Local filesystemlocalpathFor cross-box copies on hardware you own

DO Spaces and Vultr Object Storage use the s3_compatible row with the endpoints above. All eight backends work at every tier — what's tiered is the number of targets you can run, not which providers (see Tiers).

Pick your storage region/provider to match your data-residency needs — keeping images in an EU bucket (or US, or on your own hardware) is just a choice of backend.

Choose how your keys are held — custody

Separate from the storage credential is the image encryption key — the secret that actually decrypts a .hsi. You choose how HostSSH holds it. This is the single most important decision you'll make, because whoever holds an image and its key owns a full copy of your server.

Custody shapeWhere the key livesWe can read your data?Recovery if you lose the key
agent_localEncrypted only on your server — the control plane stores existence + status + a masked hint, nothing decryptableNoNone — the key never left your box; treat it like an air-gapped secret
zero_knowledgeA passphrase you hold; the control plane keeps ciphertext but no key referenceNo, everNone — lose the passphrase, lose the backups (we warn hard, like restic's RESTIC_PASSWORD)
escrowWrapped by the control-plane KMS so we can assistOnly to perform a recovery you ask forOne-click assisted recovery even if you forget the passphrase

How to read this:

  • agent_local is for air-gapped or maximum-isolation installs. The encrypted key blob lives on the Agent host and nowhere else; the control plane can't decrypt anything, and restores run on the Agent with locally-held key material.
  • zero_knowledge is maximum security with a managed control plane: we hold the encrypted image metadata but can never read your data. Restores still run on the Agent with your passphrase. The trade-off is real — there is no "forgot password."
  • escrow trades a little of that for convenience: the key is wrapped by our KMS, so the dashboard's one-click recovery console can restore for you. Opt-in, documented.

Whatever you choose, the rules below always hold:

  • Write-only over the API. You can set or rotate a credential; you can never read one back. The dashboard and CLI show a masked last-4 hint only.
  • Never logged. Credentials never appear in Agent or control-plane logs, and probe errors are scrubbed before they're stored or returned — a token fragment can't leak through an error message.
  • Per-brand keys. Credentials are encrypted at rest with a key scoped to your org (and, for resellers, per brand), so sibling brands never share a secret.

See Security for the full custody contract and threat model.

Platform integrations — what each one unlocks

Integrations are optional and stack on top of storage. Each one advertises a capability that the scheduler and migration engine key off. Add only what you want; prefer the most restricted token a provider offers.

# Example: a Cloudflare DNS Connection scoped to just your cutover zones
hostssh connections add dns \
  --provider cloudflare \
  --api-token <Zone:DNS:Edit token> \
  --zone-ids zone_abc,zone_def
IntegrationProvidersWhat it needsLeast-privilege tip
DNSCloudflare, Route53CF: api_token (Zone:DNS:Edit) + zone_ids[]. R53: access_key_id, secret_access_key, hosted_zone_ids[]Scope the token to only the zones used for cutover
Provider (VPS)Hostinger, Hetzner, DigitalOcean, Vultr, Railway, AWS EC2, generic SSH / bare metalapi_token (+ project_id/team_id where it applies); generic SSH: host, port, user, ssh_key. Optional spend guard: max_monthly_spend, max_instances⚠️ Billing-capable. Prefer project-scoped tokens; Hostinger is account-wide and can spend money — use a dedicated sub-account
AccessCloudflare Tunnel, TailscaleCF: tunnel_token. TS: auth_keyTailscale: an ephemeral, tagged key; CF tunnel scoped to the dashboard / web-SSH only
NotificationSlack, Discord, SMTP, webhook, PagerDuty, TelegramSlack/Discord: webhook_url. SMTP: host,port,user,password,from. webhook: url,secret. PD: routing_key. TG: bot_token,chat_idWebhook URLs are themselves secrets — they're stored encrypted and never logged
Monitoringhealthchecks.io, Datadog, Grafana/Prometheus, BetterStackHC: ping_url. DD: api_key. Grafana: remote_write_url,token. BS: tokenDatadog: a scoped app key, not an org-wide API key
GitGitHub, GitLabpat (+ repo)Fine-grained PAT, single repo, contents-write only

Provider-native DNS on Hostinger/DO/Vultr is reached through that provider Connection — you don't add a separate DNS row for it. Dedicated DNS Connections are Cloudflare and Route53.

The capability matrix

Each Connection carries one or more capability tags. This is the practical answer to "what do I get for adding this?"

ConnectionCapability tag(s)What it turns on
Storage backendrestic_backendRequired to capture at all. Off-site copy = disaster recovery; on R2, zero-egress restores
DNSdns_cutoverOne-click DNS flip when you migrate or relocate a server
VPS providerfull_vps, object_store, dns_cutoverManaged migration (Mode 3), provisioning, and ephemeral restore-drill targets
CF Tunneltunnel, web_sshZero-port dashboards plus browser web-SSH with no exposed :22
Tailscalemesh_accessReach the Agent and dashboard over an overlay network — no public ports
NotificationalertsBackup success/failure alerts and scheduled restore-drill reports
Monitoringmetrics_export, heartbeatPush backup metrics and heartbeats to your own observability stack
Gitconfig_as_codeExport your Coolify / infrastructure definitions to a repo

Two capabilities worth calling out, since they're what most people add storage next for:

  • web_ssh + tunnel (Cloudflare Tunnel) give you a browser shell with ephemeral, short-TTL keys and no open SSH port. See Web-SSH & access.
  • dns_cutover (a DNS Connection) is what makes the migrate/relocate flow flip your domain automatically instead of leaving you to update records by hand. See Backups & recovery.

Retention ties to your storage target

Retention — "how many images to keep before older ones are deleted" — is set per server × storage target, so a Connection and a retention policy go hand in hand. The house default is keep everything (keep_all); auto-deletion is always explicit opt-in, and every prune is logged and previewable before it runs.

hostssh retention set --target <storage-id> --keep-last 30   # keep the 30 newest
hostssh retention preview --target <storage-id>              # dry-run: what would be pruned
hostssh retention get --target <storage-id>
hostssh retention clear --target <storage-id>                # back to keep_all
SettingMeaning
keep_last NKeep the N most-recent images (the plain "image count before deletion")
keep_hourly/daily/weekly/monthly/yearlyGrandfather-father-son retention buckets
keep_withinKeep everything newer than a duration
min_keepHard floor — never prune below this (default ≥ 1)
quota / min_free_spaceCapacity-based pruning: oldest unpinned image first, never below min_keep
locked / pinned imagesLegal-hold — never auto-deleted; removing a lock is explicit and audited
dry_run_defaultPreview deletions before applying (on by default)

Emergency restore is never gated — not by retention, not by quota, not by license state, not by a disabled Connection. Any reachable storage credential is restore-eligible. We will not hold your data hostage. See Security.

What each tier can add

All eight storage backends work at every tier. Gating is on the count of targets and on which integrations you can attach.

ConnectionFree / TrialSoloProAgency / MSP
One storage backend
Multiple storage targets
Notifications / Monitoring
Access (CF Tunnel / Tailscale)
Git (config-as-code)
DNS automation + Provider / provisioning
Reseller / billing (Stripe Connect)

The single source of truth for tiers, seats, and feature gating is Licensing.

Phase note. The packaged plans are being finalized as a five-tier lineup — solo, team, business, msp, enterprise. They map onto the gating above (e.g. team/business track today's Pro, msp/enterprise track Agency/MSP); exact seat counts and pricing land with the published price sheet. Until then, the table above is authoritative for what each Connection unlocks.

SaaS: enter your own platform keys to unlock more

If you run HostSSH through the hosted control plane, entering your own platform keys is what upgrades a basic install into a full operations surface. Concretely:

  • Add a DNS Connection and migrations gain one-click cutover instead of manual record edits.
  • Add a Provider Connection and the dashboard can provision fresh VPS targets for managed migration and spin up throwaway restore-drill targets on demand.
  • Add a Monitoring Connection and your backup metrics and heartbeats flow into your own Datadog / Grafana / healthchecks — alongside the built-in telemetry dashboard.
  • Add a Notification Connection and backup outcomes and restore-drill reports route to your Slack, PagerDuty, or inbox.

Every key you add is encrypted at rest, write-only over the API, scoped to your org (or brand), and shown only as a masked hint — the same custody and redaction rules as your storage credentials.

Manage Connections anywhere

Connections are first-class on every surface — read and delete exist everywhere, nothing is dashboard-only.

hostssh connections add <class> …    # storage | dns | provider | access | notification | monitoring | git
hostssh connections test <id>        # live probe; sets status pending|valid|invalid|disabled
hostssh connections list             # all Connections with masked hints + status
hostssh connections get <id>
hostssh connections rotate <id>      # replace the secret in place
hostssh connections rm <id>

hostssh retention set|get|list|preview|clear --target <storage-id>

The lifecycle is the same on all surfaces: add → validate → store (encrypted) → periodic health re-check → rotate → revoke/disable. Validation is a live probe — storage writes/reads/deletes a test object; other integrations make a cheap authenticated call — and a periodic re-check surfaces drift when an upstream key is rotated or expires (it flips to invalid so you can rotate before a backup fails).

The same actions are available in the local admin panel, the SaaS Connections / Integrations page (with per-server overrides), the REST API (mTLS + scoped keys), and the hostssh-mcp MCP server.

See also

  • Install on any VPS — get the Agent on a server first
  • Backups & recovery — capture, restore, clone, relocate
  • Web-SSH & access — browser shells over CF Tunnel / Tailscale
  • Licensing — tiers, seats, and what each plan includes
  • Connections spec · Security · Provisioning — the internal source-of-truth specs