Skip to content

cmd backup

github-actions[bot] edited this page Oct 15, 2026 · 25 revisions

nself backup

Backup operations: create, list, restore, verify, prune, config, status, init-key.

Synopsis

nself backup <subcommand> [flags]

Description

Backup, restore, verify, and schedule ɳSelf project data.

backup stream and backup schedule do not require MinIO credentials, even when MinIO is enabled in staging or production: the stream contains PostgreSQL data only. They still load the project's normal environment cascade. Operations that access MinIO retain the enabled-service credential check.


Destination kinds

backup create --remote, backup list --remote, backup restore-remote --from and backup config accept three kinds of destination. The kind follows from the URI.

Kind URI Needs
rclone s3://, r2://, minio://, b2://, gcs://, az://, or a configured rclone remote such as s3:bucket/prefix rclone on PATH
path path:///mnt/backups (absolute directory, local or mounted disk) nothing
host host://<server>/srv/backups (a server from .nself/control-plane.yaml, absolute directory) ssh, scp, a pinned host key

rclone behaviour is unchanged.

Bare paths (backup stream and backup schedule only: --to, BACKUP_REMOTE, --heartbeat-to, NSELF_BACKUP_HEARTBEAT_REMOTE). nSelf v1.4.13 passed the value to rclone, which wrote a bare path (/mnt/backups, ./backups, backups: no : before the first /) to local disk. A value that starts with / is always a path; a relative value shaped like inline credentials (a , before a :) is never treated as one. In v1.4 mode a bare path still works: it is streamed as path:// plus the absolute directory (a relative path is resolved against the working directory, which for backup schedule is the project directory, the unit's working directory; a plain word such as mybucket with no : is a local folder too, as it was for rclone) and one notice line tells you what to write instead. With NSELF_V15=1 a bare path is refused before anything runs. The path:// rules below then apply, which differ from rclone on purpose: files are written 0600 in 0700 directories and checked by sha256 where rclone used your umask, and a symlink below the destination directory is refused (the destination directory itself may be a symlink; it is followed, as rclone did). backup check and backup drill read a heartbeat only from path://, host:// or an rclone remote, as in v1.4.13, so write the heartbeat remote as path:///<dir> if you check it.

path:// refuses .., a destination directory that is itself a symlink (use the real path), and any symlink below the directory, so a link cannot send a write outside it. A file is written to <key>.tmp, fsynced, renamed into place and re-read to compare its sha256. Directories are created 0700, files 0600.

host:// goes through the shared SSH funnel (sdk/go/remote): one ssh or scp argv builder, remote paths limited to [a-zA-Z0-9/_.-] with no .., and every unsafe name rejected before any connection. The server name must be in the inventory with a host (user@host); ssh_key_ref names an environment variable holding the key path. Host keys are pinned: ssh runs with StrictHostKeyChecking=yes against ~/.config/nself/backup_known_hosts (override with NSELF_BACKUP_KNOWN_HOSTS), and the file must hold a key for the alias nself-ci-<server>. A server with no pinned key is refused. A file is copied to <key>.tmp, its sha256 compared, then moved into place.

The pin file is refused if it holds a line nSelf cannot represent, or if it or its directory is group or world writable or owned by someone other than you or root. A directory above it that breaks the same rule prints one warning in v1.4 mode and is refused with NSELF_V15=1 (fix it with chmod go-w <dir>). That includes running with sudo while the pin directory belongs to your user. A writer that holds the pin lock for over 30 seconds makes the command stop with an error. Rules and fixes: Deploy tiers, pinned host key files.

# Back up to a mounted disk, no rclone installed
nself backup create --remote path:///mnt/backups
nself backup list --remote path:///mnt/backups
nself backup restore-remote --from path:///mnt/backups/myproject_full_20261005.dump --yes

# Back up to an inventory server
nself backup create --remote host://backup1/srv/nself-backups

nself backup config lists the three kinds and marks the one the configured remote selects. backup stream, backup schedule and the heartbeat remote still take rclone remotes only.


backup stream

Stream a live backup to S3, R2, Backblaze B2, GCS, or Azure Blob. No temp files written.

Pipeline

pg_dump (streaming) | age (encrypt) | rclone rcat (multipart upload)

The database password reaches pg_dump (and the psql row-estimate query) through PGPASSWORD in the child's environment, never on its command line, so ps on the host does not show it. A DSN whose password cannot be split off (a password= query parameter or keyword form) is refused.

Usage

nself backup stream --to <url> [--recipient <key>] [--heartbeat-to <remote>] [--heartbeat-required] [--dry-run]

Flags

Flag Default Description
--to BACKUP_REMOTE Destination: rclone remote, path:///<dir> or host://<server>/<dir> (a bare /dir works in v1.4 mode with a notice, see Destinations)
--recipient NSELF_BACKUP_RECIPIENT Encryption recipient: age key, SSH key, or github:<user> (repeatable)
--heartbeat-to NSELF_BACKUP_HEARTBEAT_REMOTE Remote (rclone, path:// or host://) that receives <project>/backup.json after a successful upload
--heartbeat-required false Exit non-zero when the heartbeat cannot be written. The backup itself is kept either way
--dry-run false Preview without running

A stream backup that fails or uploads zero bytes exits non-zero and deletes the partial object from the remote, so nothing that picks the newest object can select it. If that delete fails, the error says the object is still there and the command still exits non-zero.

Heartbeat (contract:cli.backup-heartbeat v1)

With a heartbeat remote set, a successful upload is followed by one small object, <project>/backup.json, on that remote. It lets freshness be checked off the box. It is written only after the backup upload succeeded; a failed backup writes nothing. A heartbeat failure is a warning unless --heartbeat-required is set. Use a separate remote or bucket for heartbeats.

The object is JSON with 2-space indent, sorted keys and a trailing newline:

Field Type Value
schema_version string "1"
kind string "backup"
project string project name
at string RFC 3339 UTC time the upload finished
result string "ok" (a failed backup writes nothing)
backup_key string object key of the backup, without the remote
bytes int size of the uploaded (encrypted) object
encrypted bool age recipients were used
cli_version string version of the writing binary
approx_rows object or null {"<schema>.<table>": n_live_tup} from pg_stat_user_tables, read just before the dump. null when the estimate could not be read (needs psql on PATH)
restored_rows null drill only
mismatches string[] drill only; [] for a backup

The object never holds a secret, hostname or bucket name. Resumed backups (backup resume) do not write a heartbeat.

Examples

# Stream encrypted backup to S3
nself backup stream --to s3:mybucket/backups --recipient age1abc123

# Use GitHub SSH keys for encryption
nself backup stream --to r2:mybucket/backups --recipient github:myusername

# Use env-configured destination (no flags needed)
nself backup stream

# Dry run to confirm destination
nself backup stream --to b2:mybucket --dry-run

# Write the freshness heartbeat to a separate remote after the upload
nself backup stream --to r2:mybucket/backups --recipient age1abc123 --heartbeat-to r2hb:heartbeats

Requirements

  • pg_dump on PATH
  • rclone on PATH (configured with target remote)
  • age on PATH (only required when encryption recipients are specified)
  • psql on PATH (only used to read approx_rows when a heartbeat remote is set; without it approx_rows is null)

Environment variables

Variable Description
BACKUP_REMOTE Default destination for backup stream and backup schedule (NSELF_BACKUP_DESTINATION is not read)
NSELF_BACKUP_RECIPIENT Default age/SSH public key (space-separated for multiple)
NSELF_BACKUP_NO_AUTO_KEY 1 keeps the refusal when no recipient is configured instead of creating an identity (v1.5)
NSELF_BACKUP_HEARTBEAT_REMOTE Default heartbeat remote (the --heartbeat-to flag wins). Environment only: nself.yaml has no config keys.
NSELF_BACKUP_CHUNK_MB Multipart chunk size in MB (default: 64, handled by rclone)
AWS_ACCESS_KEY_ID S3/R2/B2 access key
AWS_SECRET_ACCESS_KEY S3/R2/B2 secret key

Zero-config encryption key (v1.5)

With NSELF_V15=1, backup stream and backup create (encryption on) work on a project with no backup configuration. When no recipient is configured, nSelf creates an age identity once and encrypts to its recipient:

  • Identity file: ~/.config/nself/<project>-age.key, mode 0600, directory 0700. The same path nself backup init-key uses.
  • It is created the first time only. An existing identity is never overwritten, and two runs starting together end with one key. A looser mode on an existing file or directory is tightened, and a symlink or a file that is not an age identity is refused (E222).
  • The first run prints where the identity lives, its public recipient, and how to back it up. The secret is never printed or logged.
  • A recipient from --recipient, BACKUP_AGE_RECIPIENTS or NSELF_BACKUP_RECIPIENT always wins and creates no file. --no-encrypt keeps its exact meaning.
  • NSELF_BACKUP_NO_AUTO_KEY=1 turns automatic creation off. With no recipient the command then refuses (E224 in v1.5, the usual refusal in v1.4).
  • Without NSELF_V15=1 (v1.4 behaviour) the command refuses a missing recipient exactly as before. --dry-run never creates the file.
NSELF_V15=1 nself backup stream --to r2:mybucket/backups
# restore finds the identity by itself
nself backup restore-remote --from r2:mybucket/backups/<object>.age

If the key is lost

A lost identity makes every backup encrypted to it unrecoverable. The recipient (public key) cannot decrypt, the storage provider cannot, and nSelf cannot. There is no recovery path.

  1. Right after the first backup, copy ~/.config/nself/<project>-age.key somewhere that is not this machine: a password manager attachment or an offline drive.
  2. Optionally run touch ~/.config/nself/<project>-age.key.backed-up to record that you did. The marker is an empty file you create yourself and nothing checks that the copy is real. While an auto-created identity exists without the marker, nself doctor prints an advisory line saying so, with the touch command as the fix. The hint never changes the exit code.
  3. Prove the copy once with nself backup drill --from <remote> --identity <copy>. It restores into a throwaway container. Do not use restore-remote or restore for this: they overwrite the project database.

In v1.5, backup restore, restore-remote and backup drill look for the identity in this order when --decrypt-key, --key or --identity is not given (an explicit path is used as given). A default-lookup identity must be a regular file (not a symlink) that parses as an age identity, otherwise E222 names the path: ~/.config/nself/<project>-age.key, <project>-backup-age.key, then age-key.txt. If none exists they stop with E223.

Threat model

Threat With the auto identity Hardened setup
Lost or deleted key Backups unrecoverable Keep an off-host copy and test a restore
Storage provider reads your bucket Sees only age ciphertext Same
Host compromise The attacker holds the identity next to the recipient, so they can decrypt every backup the host can reach Move the identity off the host and keep only the recipient (BACKUP_AGE_RECIPIENTS=age1...); production works this way
Backup uploaded without the key Not possible: no recipient means a refusal, never plaintext Same

The auto identity trades host-compromise resistance for a first backup that works with zero setup. For production, generate the identity on your own machine (age-keygen), put only its recipient on the server, and keep the identity in your vault.

Replacing a hand-rolled backup script

A cron script that runs pg_dump ... | aws s3 cp - s3://bucket/x or rclone copy has no encryption, no heartbeat and no restore proof. Replace it:

Hand-rolled nSelf
pg_dump piped to aws s3 cp or rclone copy nself backup stream --to <remote> (age-encrypted, no temp file)
a crontab line nself backup schedule
"did it run?" --heartbeat-to <remote> and nself backup status
a manual restore test nself backup drill --from <remote> --identity <key>

Destinations are the three kinds above; for stream and schedule use an rclone remote (S3, R2, B2, GCS, Azure) today. An advisory nself doctor hint for a project that still holds such a script is written but not yet wired (P7-SURF-11).


backup restore-remote

Restore a backup directly from a remote URL. No local disk space required.

Pipeline

rclone cat <from> | age --decrypt | pg_restore

Usage

nself backup restore-remote --from <url> [--key <identity-file>] [--yes]

Flags

Flag Default Description
--from — Source URL: rclone remote path, path://<dir>/<object> or host://<server>/<dir>/<object>
--key v1.5: ~/.config/nself/<project>-age.key, then <project>-backup-age.key, then age-key.txt (E223 when none). v1.4: ~/.config/nself/age-key.txt Path to age identity file, used as given
--yes false Skip confirmation on production

Examples

# Restore encrypted backup from S3
nself backup restore-remote --from s3:mybucket/backups/myproject_stream_20260423.sql.age \
  --key ~/.config/nself/age-key.txt

# Restore unencrypted backup
nself backup restore-remote --from r2:mybucket/backups/myproject_stream_20260423.sql

backup resume

Resume a previously interrupted streaming backup.

Since rclone rcat uploads are not resumable at the protocol level, resume re-streams the full backup and overwrites the partial remote object at the same key.

Usage

nself backup resume <backup-id>

Resume state is stored in ~/.nself/backup-state/<id>.json.


backup schedule

Install a systemd timer to run nself backup stream on a cron schedule.

Usage

nself backup schedule --cron "0 2 * * *" --to <url> [--recipient <key>]... [--heartbeat-to <remote>] [--env-file <path>] [--dry-run]

Run it from the project directory (it must hold a .env plus an nSelf marker: nself.yaml, nself.yml or a docker-compose.yml written by nself build; a directory with only .env or only nself.yaml is refused, which is stricter than before; already installed units are untouched). The unit runs in that directory (WorkingDirectory) and calls the absolute path of the nself binary that is running now (symlinks resolved), so a different nself on PATH can never be picked up. The unit has no EnvironmentFile line unless --env-file is given; the project .env is read from the working directory as usual. The unit carries Environment=ENV=<env>: the ENV the command was started with, else ENV= in .env, else dev. The nightly run then loads the same .env cascade. Re-run backup schedule after changing ENV.

Flags

Flag Default Description
--cron — Cron expression (e.g. 0 2 * * *)
--to BACKUP_REMOTE Destination, as for backup stream; written into the unit as given
--recipient — Encryption recipient: age key, SSH key, or github:<user>. Repeatable; each one is written into the unit
--heartbeat-to — Heartbeat remote written into the unit as --heartbeat-to (see backup stream)
--env-file — Absolute path of an environment file for the unit. No default: without it the unit has no EnvironmentFile line
--unit-dir /etc/systemd/system Systemd unit directory
--dry-run false Print unit files without writing

Examples

# Schedule nightly encrypted backup at 02:00 UTC
nself backup schedule --cron "0 2 * * *" --to s3:mybucket/backups --recipient age1abc123

# Preview the systemd units
nself backup schedule --cron "0 2 * * *" --to r2:mybucket --dry-run

# Two recipients and a heartbeat remote, from the project directory
nself backup schedule --cron "30 2 * * *" --to r2:mybucket/backups --recipient age1abc123 --recipient age1def456 --heartbeat-to r2hb:heartbeats

Status

After scheduling, check the timer with:

nself backup status
systemctl status nself-backup-stream.timer

backup create

Create a local backup (written to BACKUP_DIR, default ./backups).

The minio target (also included in all) uses the S3 API at http://127.0.0.1:MINIO_PORT with MINIO_ROOT_USER, MINIO_ROOT_PASSWORD and MINIO_REGION. It needs no host mc binary or configured alias. Every visible bucket, including empty buckets, is saved under minio_<timestamp><tag>/<bucket>/ with directories mode 0700 and files mode 0600. The directory appears only after all downloads succeed. Zero-byte keys ending in / become directories. Keys ending in / with nonempty contents fail. Keys that cannot map faithfully to local paths (for example ../secret, repeated slashes or backslashes) and file/directory collisions fail the backup; incomplete downloads and objects whose size changes during the backup are refused and removed. This target saves current object contents, not object versions, metadata or bucket policies.

nself backup create [--type full|wal|metadata|minio|all] [--encrypt] [--tag <label>] [--dry-run]

backup restore

Restore from a local backup file.

nself backup restore <backup-id|latest> [--only pg,minio,metadata] [--decrypt-key <file>] [--yes]

Without --decrypt-key, v1.5 searches ~/.config/nself/<project>-age.key, <project>-backup-age.key, then age-key.txt (E223 when none); v1.4 uses ~/.config/nself/age-key.txt. The decrypted dump is a unique 0600 temp file beside the backup, removed afterwards; an existing <backup>.dec is never touched.


backup verify

Verify backup integrity, optionally running a restore test in a temporary container.

nself backup verify <backup-id|latest> [--restore-test] [--cleanup] [--keep]

Without --restore-test, verification checks only that the file exists and is non-empty. Use --restore-test --cleanup to restore a .dump or .dump.age backup in a test Postgres container and run smoke and sentinel checks. Encrypted dumps use the same default identity search as restore; verification fails if the host cannot decrypt the backup. Temporary plaintext is removed after the check, including on failure. Production-tier remote deploys and promotions require this restore test before applying.


backup list

List backups in the local backup directory.

nself backup list [--remote <name>] [--since 24h] [--format table|json]

backup prune

Remove backups beyond the retention policy.

nself backup prune [--keep-daily 7] [--keep-weekly 4] [--keep-monthly 12] [--dry-run] [--yes]

Prune deletes backups for good. In v1.4 it warns when called without --yes. In v1.5 (NSELF_V15=1) it refuses with [E403] unless you pass --yes or answer y on a terminal. --dry-run only previews, so it never needs --yes.


backup status

Show backup subsystem status: last run, next scheduled run, retention policy, and, when a heartbeat remote is set, how fresh the newest off-box backup and restore drill are.

nself backup status [--format json] [--heartbeat-to <remote>] [--max-age 26h] [--max-drill-age 35d] [--project <name>]
Flag Default Meaning
--heartbeat-to NSELF_BACKUP_HEARTBEAT_REMOTE Remote that holds <project>/backup.json and <project>/drill.json (rclone, path:// or host://).
--max-age unset Exit non-zero with [E217] when the newest off-box backup is older (26h, 1d12h), missing, or reports a failed backup.
--max-drill-age unset Exit non-zero with [E218] when the newest restore drill is older (35d), missing, or reports result: failed.
--project the current project Project name that selects the heartbeat objects. With --heartbeat-to no project directory is needed and only the offbox object is printed (the owner-machine jobs run from $HOME).

The check fails closed. A missing, stale, failed, unparseable, wrong-version, wrong-project or future-dated heartbeat is never OK, and its age is always shown. A heartbeat that cannot be fetched or parsed exits with [E219] whether or not a threshold is given. A missing object (rclone exit code 3 or 4, or no such file for path:// and host://) is told apart from an unreachable or misconfigured remote by exit code, never by words in the output: a missing object is [E217]/[E218] "none found", anything else is [E219] with its cause. Every remote read is cut off after 60 seconds.

A remote is s3://, r2://, minio://, b2://, gcs://, az://, path://, host://, or a configured rclone remote written exactly name:path. Anything that could be read as an rclone option or connection string (a leading -, :backend,opts:path, file://, http(s)://) is refused before rclone starts. --max-age or --max-drill-age with no heartbeat remote also gives [E219].

Existing fields are unchanged. --format json appends the offbox object (contract:cli.backup-status v1); the text form gains two lines, Off-box backup: and Off-box drill:, only when a heartbeat remote is set.

{"offbox": {"source": "heartbeat", "last_backup": {"...": "backup.json"}, "last_drill": {"...": "drill.json"},
  "backup_age_seconds": 7200, "drill_age_seconds": 1301460, "max_age_exceeded": false, "max_drill_age_exceeded": false,
  "problems": []}}
Field Meaning
source heartbeat, or none when no heartbeat remote is set
last_backup, last_drill the heartbeat objects (contract:cli.backup-heartbeat), null when none was found
backup_age_seconds, drill_age_seconds seconds since at, null when none was found
max_age_exceeded --max-age was given and the backup is stale, missing or failed
max_drill_age_exceeded --max-drill-age was given and the drill is stale, missing or failed
problems additive: one string per missing, stale, failed or unreadable heartbeat ("[E219] drill heartbeat is unreadable: ..."), [] when there is none. A JSON-only consumer reads the reason here; the exit status still carries the verdict
nself backup status --project nself-web --heartbeat-to r2:nself-backup-heartbeat --max-age 26h --max-drill-age 35d --format json

In v1.5 --json mode a stale, failed or unreadable heartbeat (E217-E219) prints the error envelope (error.code, no data member) and exits non-zero; the off-box state goes to stderr. Only a passing check prints the data document above. backup drill --json behaves the same: a failed drill prints the error envelope, not a data document.


backup drill

Restore the most recent backup into a scratch database, measure how long the restore took against an RTO target, and smoke-check the result.

nself backup drill [--file <backup>] [--rto-hours 4] [--dry-run] [--json]
Flag Default Meaning
--file most recent backup Backup file to drill against.
--rto-hours 4.0 RTO target in hours. 0 disables the gate.
--dry-run false Validate inputs and exit without restoring.
--json false Emit the drill result as JSON on stdout.

Off-box drill: --from

nself backup drill --from <remote> [--identity <age key>] [--key <object>] [--heartbeat-to <remote>] [--project <name>] [--json]

Restores the newest <project>_stream_* backup of a remote (rclone, path:// or host://) into a throwaway postgres:16-alpine container, so a backup is proven by a restore and never by its existence. Run it on the owner machine only, the one that holds the age identity. Production never holds a key that decrypts its backups.

Flag Default Meaning
--from none Remote that holds the backups. Selects the off-box drill.
--identity ~/.config/nself/<project>-age.key, then <project>-backup-age.key, then age-key.txt (E223 when none; the drill is new in v1.5, so it has no v1.4 default) age identity file that decrypts the object. Only its path is passed to age.
--key newest by name Object to drill instead of the newest.
--heartbeat-to NSELF_BACKUP_HEARTBEAT_REMOTE Remote that receives <project>/drill.json. Without one nothing is written.
--project the current project Project name for the object prefix and heartbeat keys. With --from no project directory is needed.

What it does:

  1. Preflight: Docker reachable, age installed for .age objects, free disk of twice the download size. Any miss exits with [E220] and writes no drill.json.
  2. Downloads the object through the destination interface and decrypts it into a private (0700) temp directory, removed on every exit.
  3. Starts a throwaway container: random name nself-drill-<16 hex> and label org.nself.drill, random password passed through the environment (never printed, never in argv), no published port and no network. It never touches the project database or containers; removal refuses any name that is not a throwaway.
  4. Restores by content, not extension: a file that starts with PGDMP is a custom-format archive and goes through pg_restore --no-owner --no-acl; anything else is plain SQL and goes through psql. Stream objects are custom-format archives named .sql.
  5. Counts rows per table (count(*)) and compares them with the source counts in <project>/backup.json (approx_rows): a table the backup counted non-empty must have rows, and a table may fall short of its count by at most 10% (the counts are n_live_tup estimates). Without source counts the drill cannot verify: no --heartbeat-to, no readable backup.json, or a backup_key that is not the object restored (the drill warns) all fail with [E218] "cannot verify" and result: failed. It never passes on "some table has rows". Every pg_restore line that contains error: also fails the drill, unless it is on the (currently empty) allowlist in internal/backup/drill_verify.go.
  6. Interrupts: SIGINT and SIGTERM stop the drill, remove the container and the decrypted files, and write no drill.json. After a SIGKILL or a crash, the next drill run starts by removing drill containers (label org.nself.drill, throwaway name only) and nself-drill-* temp directories older than 6 hours, owned by the same user. Other containers are never touched. The remote download is bounded (60 seconds plus one second per MiB of the backup).
  7. Writes <project>/drill.json (kind: drill, restored_rows, mismatches, result: ok or failed), then removes the container. A failed drill still writes it, and exits with [E218].
Code Meaning
E217 Off-box backup stale, missing or failed (backup status --max-age)
E218 Restore drill stale, missing or failed (backup status --max-drill-age), or a drill that did not match its backup
E219 Heartbeat unreadable, or no heartbeat remote for a threshold
E220 Drill cannot start: Docker, age or free disk (exit class 2 under NSELF_V15=1)

--file and --dry-run do not apply with --from. Without --from the drill behaves as before, as described below.

Critical tables

After the restore, the drill checks that a list of critical tables exists by name in the scratch database. The list comes from BACKUP_CRITICAL_TABLES (comma-separated) when set, and otherwise falls back to the built-in np_-prefixed default: np_users, np_licenses, np_audit_log, np_plugins, np_billing.

Two things about this check are worth knowing before you rely on it:

  • It tests presence only. A table that exists but holds no rows counts as present. The drill does not compare row counts against the source database.
  • A missing table does not fail the drill. The verdict stays PASS and the missing names are listed as an advisory. Treat the drill as a restore-path smoke test, not as a data-completeness gate.

Deployments whose schema does not use the np_ prefix will see every default name reported missing until they set BACKUP_CRITICAL_TABLES.

backup init-key

Generate an age encryption keypair for backup encryption.

nself backup init-key

Outputs the public key to add to .env as BACKUP_AGE_RECIPIENTS.


Commands that need --yes

nself backup prune [--dry-run] [--yes]
nself backup pitr restore --to <time> [--yes]

backup prune and backup pitr restore destroy data. In v1.4 they warn when called without --yes. In v1.5 (NSELF_V15=1) they refuse with [E403] unless you pass --yes or answer y on a terminal. prune --dry-run only previews, so it never needs --yes. An agent or HTTP caller gets a confirmation round trip first; the CLI adds --yes once the caller confirms.


Related

Flags

Name Shorthand Type Default Env Escalates to JSON override Description
--help -h bool false Show help

Subcommands

Name Description
config View backup configuration
create Create a new backup
drill Run a DR drill: restore latest backup into scratch DB and measure RTO
init-key Generate age encryption keypair for backups
list List available backups
pitr Point-in-time recovery: enable, disable, status, base-backup, restore
prune Remove old backups by retention policy
restore Restore from a backup
restore-remote Restore a backup directly from a remote URL
resume Resume an interrupted streaming backup
schedule Schedule recurring streaming backups via systemd timers
status Show backup subsystem status
stream Stream an encrypted backup directly to a remote destination
verify Verify backup integrity

Contract

  • Side effect: read
  • Output: document
  • JSON: envelope (data schema)
  • Exit codes: none declared

Examples

# Create a local backup
nself backup create

# List available backups
nself backup list

# Stream an encrypted backup straight to S3, no temp files
nself backup stream --to s3:mybucket/backups --recipient age1abc123

# Check backup subsystem status: last run, next scheduled, retention
nself backup status

See Also

← Commands | Home →

Home


Getting Started


Commands


Features


Configuration


Plugins (87 + 10 monitoring)

Free (25)
Pro (62)
Planned (26)
  • plugin-audit
  • plugin-blog
  • plugin-checkout
  • plugin-commerce
  • plugin-drm
  • plugin-export
  • plugin-flow
  • plugin-import
  • plugin-ldap
  • plugin-mailgun
  • plugin-media
  • plugin-oauth-providers
  • plugin-pages
  • plugin-postmark
  • plugin-rate-limit
  • plugin-reports
  • plugin-saml
  • plugin-scheduler
  • plugin-sendgrid
  • plugin-sso
  • plugin-subscription
  • plugin-thumb
  • plugin-transcoder
  • plugin-twilio
  • plugin-waf
  • plugin-watermark

Guides


Architecture


Reference


Machine interface


Licensing


Security


Brand


Operations


Contributing


Admin


Changelog


All commands (21)

Clone this wiki locally