Skip to content

Juiced

Juiced keeps your SSH connections, identities, and configuration synchronized across all your devices with end-to-end encryption. Whatever storage you point it at only ever holds an unreadable encrypted blob — it never sees your plaintext data.

ZestSSH sync is multi-backend. Think of the encryption as a sealed safe that ZestSSH packs on your device: the storage you choose is just the shelf the safe sits on, and it never has the combination. You pick where the encrypted data lives, and the client does all the cryptography before anything leaves the device. There are two flavours:

  • Hosted ZestSSH Cloud — the zero-setup, paid option. Requires the Juiced entitlement. (Holding both the Squeezed and Juiced entitlements simply puts you on the top “Juiced” tier.)
  • Bring-your-own stores — point sync at storage you already own. These are free; ZestSSH charges nothing because it never touches the data.
BackendSetupCostAuthNotes
ZestSSH CloudZero — sign in and goJuicedGoogle / Apple sign-inHosted at api.zestssh.com; the default for every install
WebDAVServer URL + credentialsFreeBasic auth (app password)Nextcloud, ownCloud, Synology, mailbox.org, Koofr, a NAS…
S3-compatibleEndpoint, bucket, keysFreeAccess key + secretCloudflare R2, Backblaze B2, Wasabi, self-hosted MinIO…
Google DriveOne OAuth sign-inFreeGoogle sign-in (drive.appdata)A single file in a hidden app-data folder ZestSSH cannot see past
Custom ZestSSH serverBase URL (+ optional token)FreeAccount sign-in or static bearer tokenA self-hosted server that speaks the ZestSSH sync protocol

You choose and configure the backend under Settings > Sync > Sync storage. Switching backends disconnects this device from the old store (nothing is deleted remotely) and prompts you to set sync up again on the new one. Whichever you pick, the encryption below is identical.

Sync vs. Backup. Sync keeps a single, continuously-updated encrypted manifest that all your devices merge into. That is different from a backup destination, which writes dated .zest snapshot files you restore from manually. WebDAV and Google Drive can serve in both roles, but the two use separate folders and separate files — they do not interfere with each other.

ZestSSH sync uses a zero-knowledge encryption design regardless of which backend you choose. See Zero-Knowledge Encryption for the full technical details. The key points:

  • Your sync password is used to derive a Master Encryption Key (MEK) via Argon2id (128 MB, 3 iterations, 4 parallelism). This is the current parameter set (“v2”); accounts created before v1.6 used a 64 MB “v1” profile and keep working — the parameters travel with the blob so any device can re-derive the key.
  • A random Data Encryption Key (DEK) encrypts the actual data with AES-256-GCM.
  • The DEK is wrapped by the MEK and stored encrypted alongside the blob.
  • A separate recovery key wraps the DEK independently for account recovery.
  • The store only ever holds encrypted blobs, salts, wrapped keys, and verification hashes.
  • On bring-your-own stores there is no server to check your password, so verification happens client-side: ZestSSH tries to unwrap the DEK, and a wrong password fails the AES-GCM authentication tag. The DEK, MEK, sync password, and recovery key never leave the device on any backend.
DataSyncedNotes
ConnectionsYesHost, port, label, group, protocol, keep-alive, startup command, WoL settings, environment variables, agent forwarding, home-card size/pin/note, sort order
IdentitiesYesUsername, auth method, key type, public key fingerprint/data, default flag
Identity secretsYesPasswords and private keys (encrypted in the sync blob, also written to secure storage on restore)
Connection groupsYesName, color, sort order, expanded state
SnippetsYesLabel, command, category, confirm-before-run flag, default flag, sort order
Port forward rulesYesConnection ID, type, bind address/port, remote host/port, label (auto-start and non-loopback binds are reset on restore — see note)
NetworksYesName, color, icon, triggers (subnet/SSID/cellular), fallback flag, VPN bring-up, sort order
Host network overridesYesPer-host, per-network address/port/username/identity/jump-host overrides
Port-forward network overridesYesPer-rule, per-network on / off / inherit state
Deletion tombstonesYesPropagate deletes for connections, groups, identities, snippets, snippet categories, networks, and port-forward rules/overrides (see Conflict Resolution)

Networks and their overrides sync as of v1.6. A change made only to a network profile or an override is enough to trigger a push, so context-aware connection behavior now follows you between devices.

Restored port forwards are re-armed safely. On restore, every synced rule has auto-start forced off and any non-loopback bind address rewritten to 127.0.0.1. This prevents a synced rule from silently opening a LAN-reachable listener on a new device before you have re-approved it in the UI.

The sync blob includes a schema_version field (currently 11) and a synced_at timestamp. This allows schema migrations without breaking existing synced data. An older build that does not understand a newer blob refuses to restore it (rather than silently dropping fields and pushing truncated data back) and asks you to update the app.

DataReason
Host-key local-verification statusWhether THIS device has locally confirmed a host key is device-local and never travels. The host keys themselves DO sync as of blob v11 — with per-record provenance, and a conflicting key (same host, different fingerprint) is never silently overwritten — but each device still confirms trust on its own first local connect.
Terminal transcripts / session recordingsToo large; device-specific
Automation history / audit logsDevice-specific execution records
App settings and preferencesTheme choices, font sizes, UI preferences are personal to each device
Terminal theme customizationsStored locally in SharedPreferences
Auto-backup passwordsDevice-specific secure storage

The steps below describe the ZestSSH Cloud / custom-server protocol. Bring-your-own stores follow the same contract over a single encrypted manifest object — see Backend Differences for where they diverge.

  1. Choose your backend under Settings > Sync > Sync storage. For ZestSSH Cloud, sign in with Google or Apple; for a bring-your-own store, enter its credentials and run Test connection.
  2. ZestSSH verifies your Juiced entitlement (ZestSSH Cloud only — bring-your-own stores skip this).
  3. Choose a sync password (minimum length enforced).
  4. A random DEK is generated.
  5. The MEK is derived from the password + random salt.
  6. A recovery key is generated and displayed (save it securely).
  7. The DEK is wrapped with both the MEK and recovery KEK.
  8. All local data is encrypted and pushed.
  1. Gather all local data (connections, identities, groups, snippets, port-forward rules, Networks, and their overrides) plus identity secrets from secure storage.
  2. Encrypt the data blob with the DEK.
  3. Include the salt, verification hash, wrapped DEKs, and Argon2id parameters.
  4. Store the blob with an expected_version for conflict detection (a POST /sync/push on the ZestSSH server; a compare-and-swap write on bring-your-own stores).
  5. On success, the new version number is returned.
  1. Fetch the encrypted blob (GET /sync/pull on the ZestSSH server; a read of the manifest object on bring-your-own stores).
  2. The store returns the encrypted blob, salt, version, wrapped DEKs, and Argon2id parameters.
  3. Derive the MEK from the stored password + salt.
  4. Unwrap the DEK using the MEK.
  5. Decrypt the blob using the DEK.
  6. Restore data as an upsert plus tombstones: incoming records overwrite matching local records (or are inserted if new), and the blob’s deletion tombstones remove local records whose deletion they carry (see Conflict Resolution).

Two mechanisms operate at different layers.

Push lock (server/store-side): optimistic concurrency with version numbers:

  1. Each push includes an expected_version, the version the client last saw.
  2. If the stored version matches, the push succeeds and the version increments.
  3. If the stored version has advanced (another device pushed), the write is rejected with a SyncConflictException. On the ZestSSH server this is an HTTP 409; on bring-your-own stores it is an ETag / revision precondition failure.
  4. The client then pulls the latest data, merges local changes, and retries the push. The auto-sync cycle does this for you.

This serializes writes; it is not the merge itself.

The restore uses an upsert-plus-tombstones strategy:

  • Existing records are updated (overwritten wholesale) with the synced data.
  • New records are inserted.
  • Deletions propagate. Deleting a connection, group, identity, snippet, snippet category, network, or port-forward rule/override on one device writes a deletion tombstone carrying a deletedAt timestamp. On the next sync, other devices apply that tombstone and remove their local copy too.
  • Edit-versus-delete conflicts are resolved by timestamp: a tombstoned record survives only if its own updatedAt is newer than the tombstone’s deletedAt (a genuine edit made after the delete wins); records with no updatedAt are delete-wins. Ordinary edit-versus-edit conflicts are resolved by push order (the push lock above), not by comparing timestamps.
  • Tombstones are retained for two years before garbage collection. A device that has not synced in longer than that is warned and asked to reconcile rather than silently losing or resurrecting data.

Because of DEK indirection, changing your sync password does not require re-encrypting all data:

  1. Derive a new MEK from the new password + new salt.
  2. Re-wrap the existing DEK with the new MEK.
  3. Push the updated wrapped DEK to the store.
  4. The encrypted data blob remains untouched.

This works identically on every backend, because only the small wrapped-key metadata changes — the large data blob is never re-encrypted.

If you forget your sync password:

  1. Enter your recovery key.
  2. ZestSSH derives the recovery KEK from the key + stored recovery salt.
  3. The recovery hash is verified (server-side on ZestSSH Cloud; client-side by unwrapping the DEK on bring-your-own stores).
  4. The DEK is unwrapped using the recovery KEK.
  5. Data is decrypted using the DEK.
  6. You can then set a new sync password (re-wraps the DEK with a new MEK).

To delete all sync data from the store:

  1. Go to sync settings and select Purge Data.
  2. Enter your sync password for verification.
  3. On the ZestSSH server the verification hash is sent to /sync/purge; on a bring-your-own store the encrypted manifest object is deleted directly (possession of the store credentials is the authorization).
  4. All encrypted data is permanently deleted from the store.

On server-backed backends the verification-hash requirement prevents data destruction by an attacker who only holds the account auth token.

Deleting your ZestSSH server account:

  1. Navigate to account settings.
  2. Select Delete Account.
  3. Enter your sync password for verification.
  4. The server deletes the account and all associated data via /account/delete.

Bring-your-own stores have no ZestSSH account, so “delete account” is equivalent to a data purge — the encrypted object is removed and the store itself remains yours to manage.

Every backend implements the same encrypted-sync contract, so the crypto, merge strategy, and conflict handling above are identical. Where they differ:

ConcernZestSSH CloudCustom ZestSSH serverWebDAVS3-compatibleGoogle Drive
AuthenticationFirebase (Google / Apple), Authorization: Bearer <ID token>Account sign-in or a static bearer token you setHTTP Basic (app password recommended)Access key + secret (SigV4)Google OAuth, drive.appdata scope
EntitlementRequires JuicedNone (free)None (free)None (free)None (free)
Storage layoutServer-managed rowsServer-managed rowsOne manifest file under /ZestSSH/sync/One object under your bucket (optional key prefix)One file in the hidden app-data folder
Optimistic lockHTTP 409 version checkHTTP 409 version checkETag precondition (If-Match / If-None-Match)ETag preconditionRevision precondition
Password checkServer-verifiedServer-verifiedClient-side (unwrap DEK)Client-side (unwrap DEK)Client-side (unwrap DEK)

All backends require a valid TLS certificate — ZestSSH refuses to sync secrets over an unverified or plaintext connection.

MistakeWhat happensFix
Losing both the sync password and the recovery keyNobody — including ZestSSH — can decrypt the blobStore the recovery key somewhere safe at setup
Pointing sync at a WebDAV/HTTP URL without TLSZestSSH refuses to save the backendUse an https:// endpoint with a valid certificate
Reusing the /ZestSSH/backups/ folder for syncSync and backups fight over the same locationLeave the defaults — sync uses /ZestSSH/sync/, backups use /ZestSSH/backups/
Assuming host-key trust carries overNew devices re-prompt to trust each hostExpected — known-hosts are deliberately not synced (see What Doesn’t Sync)
Not syncing a device for a very long timeThat device’s tombstone set may be past the two-year retention window, so it can no longer safely suppress records deleted while it was awayZestSSH detects the over-window condition and asks you to merge, adopt the cloud state, or export first