Skip to content

Self-Hosted Sync

Sync keeps servers, keys, snippets, dashboards, vault items and other configuration in step across your devices through a PocketCmd Server you run yourself. Records are encrypted on the device with AES-256-GCM before upload. The server stores the ciphertext plus the metadata it needs to route and authorize it. Sync requires PocketCmd Pro.

PocketCmd does not operate a sync service. The services PocketCmd does run are the app stores’ billing, the license server that activates and checks Pro license keys and the push relay that carries alert notifications. None of them receives your configuration.

Every synced item belongs to a vault:

  • Personal vault. Syncs to every paired server you turn it on for, with Synchronize personal vault on the server’s entry under Teams. Only your own devices can read it. Turning it on sets up your personal vault on that server, or unlocks it if you already have one.
  • Team vaults. Live on the team’s server and sync to the members who have access. Access levels and sharing are covered in Secrets Vault.

The server accepts these record types (internal/datasync/store.go):

Record type Contents
task Saved servers: connection settings, with passwords and key passphrases in the separately encrypted credentials field.
task_group, task_tag Server groups and tags.
ssh_key SSH keys. The private key travels in the encrypted credentials field.
sftp_bookmark SFTP bookmarks.
workspace Workspaces: server, root folder, name and settings. Open tabs and unsaved drafts stay on the device.
snippet, snippet_folder Snippets and their folders.
command_template Command templates.
layout_config Split-screen layout presets.
port_forward_rule Port forwarding rules.
dashboard, alert_config Dashboards and their alert settings.
settings App theme, theme mode (system, light or dark) and terminal font size.
secret_item Vault items: logins, notes, API keys and the other item types.
vault_wrap, vault_meta Vault keys wrapped for each recipient device and vault metadata.
device_vault_key, enroll_request, device_revocation The per-device vault key directory: public keys, pending device approvals and permanent revocations. Public material only.
files_enrollment Which devices enrolled a key for System File Manager access on which server, so your other devices can clean up after a removed device. Contains no secrets.
monitoring_config The team’s approved metric commands and their alert rules. Plaintext, see below.

Quick links, the SFTP transfer queue, local metric history and other device preferences are not synced.

  1. Each device has its own vault key pair, generated in the device’s secure element (Secure Enclave, Android StrongBox or TEE, TPM 2.0) where one is available. Only the public half is published, as a device_vault_key record.
  2. Each vault has a random 256-bit key. It is wrapped separately for every device that may read the vault, so adding or removing a device changes the wraps, not the records.
  3. Your app password is stretched with Argon2id (64 MiB, 3 passes, 1 lane) and mixed into the key that opens your own wraps. Opening the vault takes the device and the password; the password never leaves your devices.
  4. Each record is encrypted with AES-256-GCM under its vault key with a fresh 96-bit nonce, then pushed to the server.
  5. Other devices pull the record, unwrap the vault key with their own device key and decrypt locally.

Removing a device or a team member rotates the keys of the vaults it could read. Records are re-encrypted under the new key generation on their next push.

The server never holds a key derived from your password. A copy of the server’s database gives an attacker nothing to guess a password against offline.

  • Sync metadata: record type, record ID, size, update time, the ID of the device that wrote it, the owning user and vault membership. The server needs these fields to route records, check permissions and resolve conflicts.
  • monitoring_config: stored in plaintext because the server executes the metric commands and evaluates the alert rules in it. It is shared by the whole team. Never put secrets in metric commands or labels.
  • Team directory: display names, device names, roles and permissions.

Record contents (hostnames, usernames, key material, snippet bodies, vault items) are opaque to the server.

Layer Primitive
Password stretching Argon2id, 64 MiB, 3 passes, 1 lane
Device keys Non-exportable where a secure element exists: Secure Enclave, StrongBox or TEE, TPM 2.0
Record encryption AES-256-GCM, 96-bit nonces
Transport TLS 1.3, mutual TLS, session token bound to the client certificate

There is no background timer. A sync pass runs on:

  • App start, after unlock.
  • An edit, 5 seconds after the last change.
  • Returning to the app, when the last successful pass is more than 30 minutes old.
  • Opening the vault and pull-to-refresh in the vault.
  • Pairing a server and turning on personal vault sync for it.

A failed pass is retried once after 30 seconds. After that, the next trigger in the list above starts a new attempt.

When Pro ends, sync stops and no timers stay armed. Local edits keep being recorded. When Pro is active again, one full pass pushes everything that changed in the meantime and pulls what other devices pushed.

The newest edit wins. Each record carries the time of its last local edit, and the server rejects a push whose timestamp is not newer than the stored one. The newer version reaches the device on its next pull. A timestamp more than 5 minutes ahead of the server’s clock is refused, so a device with a wrong clock cannot win every conflict. The server records which device and user wrote each record itself and ignores what the device claims.

The app pushes and pulls in batches. When the server refuses a record for a reason that resending won’t fix, such as missing vault access or a permission the member doesn’t hold, the app stops pushing that version and tries again only after the record is edited.

A deletion is stored as a tombstone: the record keeps its ID and type, its contents are cleared and it is marked deleted. Tombstones are kept for 365 days so that a device that was offline for a long time cannot bring a deleted record back, then removed by a daily cleanup. A device offline for more than a year can still restore a record deleted in the meantime.

A record cannot move between vaults while it exists. Moving an item deletes it from the old vault and creates it in the new one.

Limit Value
Push request size 10 MB
Record size 1 MB for the data and 1 MB for the credentials field
Records per push 10,000
IDs per pull 10,000

All endpoints require mTLS, a session token and the X-PocketCmd-Api-Version header.

Endpoint Method Purpose
/api/sync/manifest GET Record IDs with type, update time and deleted flag, limited to the records the caller may read.
/api/sync/pull POST Fetch records by ID.
/api/sync/push POST Write new and changed records. The response lists accepted records and the reason for every rejected one.

Each member sees their own records, the team’s monitoring_config, the public device directory and the team-vault records they have access to.

Sync records live in the sync_records table of ~/.pocketcmd/state.db. monitoring_config is the exception: it is split into its own tables so the scheduler can read it.

CREATE TABLE sync_records (
id TEXT NOT NULL,
type TEXT NOT NULL,
schema_version INTEGER DEFAULT 1,
updated_at INTEGER NOT NULL,
device_id TEXT DEFAULT '',
deleted INTEGER DEFAULT 0,
data TEXT DEFAULT '{}', -- ciphertext
credentials TEXT DEFAULT '', -- ciphertext
user_id TEXT NOT NULL DEFAULT '',
vault_id TEXT NOT NULL DEFAULT '',
PRIMARY KEY (type, id)
);

Each device holds its own vault key, so pairing again does not recover anything on its own. To add a device, approve it from a device that is already enrolled: the enrolled device shows a six-digit code that you type on the new one. If you forget the app password, unlock on another enrolled device and enroll this one again. If no enrolled device is left, the vault cannot be recovered. See App Password & Vault.