Skip to content

Command-line tools

Every NGBackup program lives in /opt/backup/bin/.

ProgramRolePort
backup-dirDirector — schedules and orchestrates9101
backup-sdStorage Daemon — reads/writes volumes9103
backup-fdFile Daemon — agent on protected machines (flag-driven)9102
ProgramPurposeBacula equivalent
backup-consoleInteractive console (run/restore/status)bconsole
backup-cliDrive the Director from scripts
backup-copyCopy/migrate volumes between poolsbcopy
backup-extractExtract files directly from a volumebextract
backup-lsList the contents of a volumebls
backup-scanRebuild the catalog from volumesbscan
backup-dbcheckCheck and repair the catalogdbcheck
gdd-fsckOffline Global Deduplication store recovery (runs with the SD stopped)
backup-dirjson · backup-sdjson · backup-fdjson · backup-consjsonDump any config as JSON

Inside backup-console you drive the Director with the same verbs as Bacula’s bconsole, and the text output is byte-faithful to Bacula 15.3 — existing runbooks and screen-scrapers keep working.

VerbShows
show <kind>[=name]A configured resource (Director / Client / Storage / Pool / Catalog / Content / Schedule / Job / Messages). show job=NAME recursively inlines the referenced resources; show all dumps everything in stock order. Flat Kind: name=value form (the --> sub-resource shape), not .conf syntax.
status dirDirector status — header (Jobs: / Crypto: / Heap: / Res:), then the Scheduled Jobs (N/50) / Running Jobs / Terminated Jobs sections. status storage=NAME / status client=NAME proxy to the SD / FD. While a job runs, the FD’s Running Jobs: block reports live counters — Files= / Bytes=, AveBytes/sec + LastBytes/sec, files examined vs backed up, and the Processing file: the walk is on — updated on every poll.
list <kind>Catalog rows as +---+-bordered tables (list jobs / clients / pools / volumes / `jobmedia [jobid=N
llist jobsThe long / vertical (key: value) form of list jobs, with the joined columns (clientname, poolname, content, …).
status tenantLists the configured Tenant {} resources and their quota directives — maxclients / maxjobs / maxstorages / maxvolbytes / warnvolbytes / isolated / enabled — or No Tenant resources defined. when none are configured. Also reachable as item 6 of the interactive status menu.
cancel <jobid=N | all>Cancels one running job, or every running job with cancel all. Bare cancel opens an interactive running-job picker.
purge <files | jobs | volume=NAME>Drops catalog records without touching volume data. purge files jobid=N | job=NAME | client=NAME removes File rows; purge jobs client=NAME removes Job rows; purge volume=NAME purges a volume. Destructive — catalog rows are gone, though the bytes on the volume remain until overwritten.
delete <jobid=N | client=NAME | volume=NAME | pool=NAME>delete client=NAME removes a client and cascades to its dependent catalog rows. delete jobid=N removes a single job record. delete volume=NAME cascade-deletes every job on the volume, then the volume record. delete pool=NAME removes an empty pool record (refused while it still owns volumes). Catalog-only; does not reclaim volume space.
update <slots [storage=NAME] | pool=NAME | volume=NAME> …update slots re-reads the autochanger’s barcode inventory and syncs each matching volume’s Slot/InChanger catalog columns (volumes no longer in the changer are un-flagged; unknown barcodes are reported, never auto-created). update pool=NAME pushes retention/limit fields into the pool’s catalog row; update volume=NAME edits one volume’s mutable fields (volstatus=, slot=, volretention=, …).
update <client=NAME | storage=NAME> version=X [package=NAME] [phase=all|precheck|stage|install] [downgrade=yes] [ignoreorder=yes]Fleet update (NGFLEET): installs a signed .ngpkg from the repo cache onto the target daemon, inside a catalog-visible Update job (list jobs type W). Default phase=all runs precheck → stage (signed push, re-verified on the daemon) → install: the root apply unit re-verifies the signature, extracts to /opt/backup/releases/<pkg>-<ver>/ and atomically flips the binary symlink; the daemon restarts itself and the Director confirms the new version banner into the fleet inventory. Refuses while jobs run on the target, when the director ≥ sd ≥ fd order would break (ignoreorder=yes overrides), and for downgrades without downgrade=yes. Needs the package in the repo cache (repo sync / repo import) and the ngbackup-apply-update systemd units on the host.

Every verb also accepts output=json for a stable machine-readable envelope (what the Web and Desktop consoles consume). The job log (list joblog jobid=N) renders Bacula’s multi-line backup-summary report — Build OS, JobId, Level, Client, FileSet, Pool, Storage, times, files/bytes written, rate, and termination status.

Commands prefixed with a . are the programmatic / introspection surface — the machine-facing counterpart to the operator verbs above. They’re what the Web and Desktop consoles, the REST API, and tab-completion call under the hood: short, stable, output=json-first, and routed through a dedicated dispatcher (a .-command never falls through to the human verb table). Stock Bacula ships the same .-command family; NGBackup keeps the names byte-faithful and adds a few of its own.

Enumerate resources & names — used to populate pickers and completions:

CommandReturns
.clients / .pools / .storage / .schedule / .jobs / .filesets / .catalogs / .msgsThe configured resource names of that kind, one per line (or a JSON array).
.defaultjobsThe Job resource names eligible as run targets.
.types / .levels / .jobstatus / .volstatus / .actiononpurgeThe valid enum values for that field (Job types, backup levels, job-status codes, volume-status codes) — what a dropdown offers.
.locationsConfigured storage locations.

Introspect config & state as JSON:

CommandReturns
.dirjson / .fdjson / .sdjsonThe Director / File-daemon / Storage-daemon config as typed JSON (round-trips with config set).
.status / .jobs / .jobstatus / .jobprogressLive status / job list / per-job progress in machine form.
.pluginobjectsThe plugin-emitted object catalog (VMs, mailboxes, databases) a plugin backup enumerated.
.sql <SELECT …>A read-only SQL query against the catalog, rows as JSON.
.api [1|2]Switch the session’s API/output mode (stock’s .api — the machine-mode toggle).

Browse live client filesystems:

CommandDoes
.ls client=NAME path=/dirBrowse a live directory on the client’s File Daemon — a non-recursive readdir of one path on the running node, for building a FileSet or previewing what a path holds. Streams one entry per line as <type> <size> <name> (d dir / f file / l symlink / s special). Distinct from restore browsing, which navigates the catalog file-tree of a completed backup, not the live filesystem. Read-only (a Monitor = yes Director may run it); output=json returns a structured entries array the web console’s file-picker consumes. An unreadable or missing path returns an empty listing, never a session-aborting error.

Config-as-data lifecycle (the auditable edit pipeline — see Config browse & edit):

CommandDoes
.config-show / .config-diff / .config-history / .config-driftShow the active revision, diff pending vs active, list the revision history, or report whether the on-disk .conf drifted from the active revision.
.config-import / .config-commit / .config-activate / .config-deleteStage an edited config, commit it as a new revision, activate (validate + atomically write + reload), or delete a resource.
.validate-configLint the config without applying — returns an errors + warnings array.
.reconfigure / .restartAsk a daemon to hot-reload or restart to apply.

Enrollment tokens (locked-down onboarding — see Enrollment):

CommandDoes
.enroll-token client=NAME [ttl= cidr= profile= kind=client|storage]Mint a single-use bearer token (shown once) for a node to join when AllowOpenEnrollment = no.
.enroll-token-list / .enroll-token-revoke token=IDList issued tokens with live/used/revoked/expired state, or revoke one.

.help lists everything the connected Director supports.

list events reads the append-only Events catalog table — a tamper-evident audit log the Director writes as jobs and operator commands run. Rows come back newest-first (default limit=100, override with limit=N) in a seven-column table: code, type, time, daemon, source, ref, and text. The Director records DJ0001 when a job is allocated a JobId and DJ0002 when it finalizes, so a single job leaves a clear start/finish pair:

*list events
+--------+------+---------------------+------------+--------+------------+--------------------------+
| code | type | time | daemon | source | ref | text |
+--------+------+---------------------+------------+--------+------------+--------------------------+
| DJ0002 | job | 2026-06-30 18:21:07 | *Director* | | JobId=1080 | Job 1080 terminated OK |
| DJ0001 | job | 2026-06-30 18:20:55 | *Director* | | JobId=1080 | Job Daily (1080) created |
+--------+------+---------------------+------------+--------+------------+--------------------------+

The termination text folds in the outcome — terminated OK / with warnings / cancelled / with errors — so the audit row shows the result inline.

output=json returns the same fields (code / type / time / daemon / source / ref / text) as a stable list envelope.

The Director keeps a per-daemon version inventory (the FleetInventory catalog table) so you can see at a glance what every FD/SD on the fleet is running:

*version client=NAME # poll one client's FD and record version + platform
*version storage=NAME # poll one Storage's SD
*version all # poll everything (and the Director itself)
*list fleet # the recorded inventory; flags daemons never seen

version without a target still prints the Director’s own banner. Polls use the normal authenticated DIR↔FD/DIR↔SD channels — no extra ports or agents — and each job dispatch also refreshes the client’s row automatically (at most once per hour per client), so list fleet stays current on a busy Director without manual polls. Both verbs accept output=json (kind: "fleet" list envelope). Stock Bacula daemons don’t implement the poll and are reported UNREACHABLE/never seen.

The Director owns a local package cache synced from the official repository (or a mirror) — the fleet only ever installs from this cache, never from the internet. Configure it with a Repository {} resource in backup-dir.conf (URL, User/Password entitlement credential, Channel = lts|innovation, CacheDirectory, optional Platforms selective-sync filter), then:

*repo sync # fetch + signature-check the channel index, ingest new packages
*repo list # cached packages (name, version, channel, platform, signature state)
*repo status # resolved Repository config + cache freshness
*repo import file=PATH # air-gapped ingest of a .ngpkg from removable media

All four accept repository=NAME (defaults to the first configured) and output=json. Every ingest — HTTPS sync or repo import — is verified before it enters the cache: the channel index must carry a valid Ed25519 signature (an unsigned index is refused), each download must match the index sha256, and every .ngpkg must verify against the repository public key with a manifest identity that matches the index entry. The official key is built into backup-dir; a mirror or test repository can override it with PublicKeyFile = /path/key.pub in its Repository {} resource. Packages are signed in the release pipeline with the ngpkg tool (keygen / pack / verify / sign-index), which is not shipped in release packages.

Every build ships a signed community grant (10 objects / 10 TB, perpetual) so a fresh Director is licensed with no config. Paid entitlement comes three ways, resolved in this order:

  1. LicenseFile = /path/grant.lic on the Director {} resource — a single signed grant file (legacy, still supported and wins when set).
  2. A License {} resource with OfflineFile = /path/grant.lic — the air-gap path; the signed grant is read locally, no network call.
  3. A License {} resource with Serial + ApiUrlonline activation: the Director POSTs the serial + a machine fingerprint to the license server, caches the returned signed grant, and refreshes it daily (the server sets the cadence). Every network failure is fail-open — the daemon keeps the last good grant, so the license server being unreachable never blocks backups.
License {
Name = Acme
Serial = "NGB-XXXX-XXXX-XXXX"
ApiUrl = "https://license.ngbackup.com/"
# OfflineFile = "/opt/backup/etc/grant.lic" # air-gap alternative
}

The trust root is the signed token in all three cases — the Director verifies every grant (file or online) against the embedded public key, so the license server can never grant more than the offline signer. Console verbs:

*license # edition, usage vs caps, expiry, state, and source
*license activate # force an immediate online activation/refresh (after a new serial)

license gains a source line (community | offline-file | online:<serial-tail>); both accept output=json. The endpoint contract the license server implements is docs/v2/LICENSE-API-CONTRACT.md.

Replication runs as a first-class job type: Type = Replicate in a Job {} resource makes each cycle a real catalog Job (type code E) — schedulable, listable and ACL-checked like any backup — while the plugin named in the job’s Content does the data movement. The Director tracks every protected pair in the ReplicationPair catalog table, refreshed automatically when a cycle ends:

*list replication # every pair: source client, role, last-cycle state and lag
*replication status # same pairs plus an at-risk count (RPO dashboard)
*replication status pair=ReplPg # one pair joined with its restore points and failover history
*list restore-points pair=ReplPg # the pair's restore-point rotation, newest first

All four accept output=json. list replication and the no-pair replication status return the pair set (lag_seconds and rpo_at_risk are computed at read time from the last completed cycle); replication status adds an at_risk_count. replication status pair=NAME returns that pair joined with its restore_points and failover_events in one reply (the console pair-detail pane); list restore-points pair=NAME returns just the restore-point rotation (seq_no, created_at, kind seed/delta/compacted, bytes, verify_state). An unknown pair returns a result: "error" envelope.

Type = Failover is reserved for the failover/failback orchestrator and is not yet dispatchable; the RPO-driven cycle scheduler and the mutating failover/failback verb family arrive with later NGREPL phases.

One queue engine, three lanes — backup, replication, system — each with its own concurrency budget, so a burst of replication cycles can never starve backups (or vice versa). Jobs are assigned by type: Replicate/Failover ride the replication lane, Admin the system lane, everything else the backup lane. Configure the budgets on the Director resource:

Director {
MaximumConcurrentJobs = 20 # global ceiling, unchanged
MaximumConcurrentReplicationJobs = 10 # replication-lane budget
MaximumConcurrentSystemJobs = 5 # system-lane budget
}

Unset lane budgets default to the global ceiling (no isolation until you configure them), and a lane budget above MaximumConcurrentJobs is flagged at boot — it could never be reached. list queue gains a lane column and a filter: list queue lane=replication, and status director output=json reports per-lane effective concurrency under jcrs.lanes (each lane’s active count vs its budget).

Lane budgets also exist per Client and per Storage, so one busy FD or SD can’t let its replication cycles starve its backups:

Client { MaximumConcurrentReplicationJobs = 2 } # this FD: at most 2 replication cycles at once
Storage { MaximumConcurrentReplicationJobs = 4 } # this SD: at most 4

A per-resource lane budget above that resource’s own MaximumConcurrentJobs is flagged at boot and by .validate-config (it could never be reached). Unset (0) means no per-resource lane cap.

Give a Type = Replicate job an RPO and the Director schedules its cycles continuously — no calendar Schedule needed. Each cycle is started early enough (based on a moving average of recent cycle durations) to complete within the objective, not merely begin at it:

Job {
Name = "Replicate-ERP"
Type = Replicate
Client = pve1-fd
Content = "ERP-VMs"
RPO = 15 min # arms the continuous scheduler
ReplicationWindow = "18:00-06:00" # optional time gate (wraps midnight)
}

A pair whose last completed cycle is older than its RPO is at risk: the next cycle is escalated to the highest priority, a warning fires once per breach, and list replication flags the pair (RPO-AT-RISK in text, "rpo_at_risk": true in JSON). Calendar Schedules keep working as before — the RPO scheduler is additive.

Replicate jobs also take RestorePoints = <n> (rotation depth on the target) and ConsistencyGroup = <name> (pairs that fail over together). All four replication directives are typed end-to-end: emitted by .dirjson, editable with config set job=NAME ..., and linted by .validate-config — a valid config now returns a warnings array that flags a replication-lane budget above MaximumConcurrentJobs and any replication directive on a job whose Type is not Replicate (dead configuration).

The transfer verb moves a volume between two storage slots through the autochanger, without loading it into a drive. It is forwarded from the console through the Director to the named SD, which drives the changer (the native engine or the ChangerCommand script).

*transfer sd=NAME slot=<src> dst=<dst> [device=NAME] [drive=N]
  • sd=NAME — the Storage daemon (required).
  • slot=<src> — source slot number, ≥ 1 (required).
  • dst=<dst> — destination slot number, ≥ 1 (required).
  • device=NAME / drive=N — pick the changer’s transfer arm when the library has more than one (optional; defaults to drive 0).

On success the SD replies 3000 OK transfer slot=<src> dst=<dst>; a bad request, a non-changer device, or a changer error returns 3902 Bad transfer cmd — <reason>. Add output=json for the standard envelope (verb: "transfer"). The companion mount / unmount / status slots / label barcodes verbs operate the same library.

Send reload directly to a running Storage or File daemon (over its console connection) and it re-reads its .conf, then hot-applies the directives that are safe to change while jobs run — a running backup is never disturbed. Directives fall into exactly three buckets, and the reply enumerates every changed directive under one of them (text and output=json):

  • LIVE — applied immediately; takes effect on the next admit / connection / session.
  • RESTART — boot-immutable; reported under restart_required, change takes effect only after a restart.
  • REFUSE — unsafe to flip mid-flight; the change is rejected with a typed reason (not silently ignored).

Storage daemon reload:

DirectiveBucket
MaximumConcurrentJobs, HeartbeatInterval, ClientConnectTimeout, ClientConnectWait, CommCompression, MessagesLIVE
TLS material (TlsEnable/Require/VerifyPeer/Certificate/Key/CaCertificateFile)LIVE — acceptor swapped, rotated certs take effect
SdPort / SdAddress, Device {} registry, DedupDirectory, DedupIndexDirectory, WorkingDirectory, PidDirectory, PluginDirectory, ScriptsDirectoryRESTART
FipsRequire, Director {} table (peers/passwords)REFUSE

File daemon reload:

DirectiveBucket
MaximumConcurrentJobsLIVE
HeartbeatInterval, MessagesRESTART (LIVE pending the FD config-snapshot work)
FdPort / FdAddress(es) / FdSourceAddress, WorkingDirectory, PidDirectory, PluginDirectory, TLS / PKI keystoreRESTART
FipsRequire, Director passwordsREFUSE

2000 OK reload applied lists the LIVE / restart_required / refused directives; an unchanged config returns 2000 OK reload: config re-read, no directive changes. Lowering MaximumConcurrentJobs only throttles new admits — already-admitted jobs keep running.

Edit one directive remotely — config set

Section titled “Edit one directive remotely — config set”

config set edits a single directive in a daemon’s .conf at the text level (comments and layout survive), validates the result, and persists it — over the authenticated, secure console. It is fail-closed: refused on a plain TCP session (requires TLS / TLS-PSK).

*config set [sd=<NAME>|fd=<NAME>] <kind>=<NAME> <Directive>=<value>
  • Without sd=/fd= it edits the Director’s own config.
  • sd=NAME proxies to that Storage daemon, which validates + atomically persists + audits the edit (takes effect on SD restart).
  • fd=NAME proxies to that File daemon (takes effect on FD restart).

List-valued directives use the spelling of the operator: Directive= replaces, Directive+= appends a repeatable line, Directive-= removes one. Append/remove are supported for the FileSet IncludeFile list and the PluginProfile Setting list; FD Setting-= (remove) is not supported — edit the file directly and reconfigure.

*config set fd=web01-fd filedaemon=web01-fd MaximumConcurrentJobs=8
*config set fd=pve1-fd PluginProfile=dr-pair-pve Setting+="dr_host=dr.example.com"

When a Global Deduplication store’s index or extent-state map is corrupt, the Storage daemon may not start — so recovery cannot be a console verb. gdd-fsck is a standalone binary that operates the store offline. Every mutating subcommand takes an advisory flock on <store>/.gdd-store.lock and refuses while the SD is running — stop the SD first.

Terminal window
gdd-fsck check --store <dir> [--index <dir>] # non-mutating consistency check
gdd-fsck rebuild-esm --store <dir> # rebuild esm0/esm1 from container headers
gdd-fsck rebuild-index --store <dir> [--index <dir>] # rebuild the redb index from manifests
gdd-fsck scrub --store <dir> [--index <dir>] # read every chunk back + verify its CRC

--index defaults to <store>/index. check and scrub exit non-zero on drift / a bad chunk (scriptable for monitoring). A rebuild-index understates refcounts, so it marks the index refcounts-degradeddedup … vacuum refuses until refcounts are reconstructed.

Terminal window
backup-dir -t -c /opt/backup/etc/backup-dir.conf
backup-sd -t -c /opt/backup/etc/backup-sd.conf
backup-fd -t -c /opt/backup/etc/backup-fd.conf

-t tests and exits; combine with the *json tools to feed automation or the Web Console.

Console config commands (remote management)

Section titled “Console config commands (remote management)”

The Web and Desktop consoles drive everything through the text console, so config can be read remotely over the authenticated console connection — no SSH, no co-located files. These are .-prefixed (dot) commands; run them in backup-console or any console client.

CommandReturns
.dirjson [resource=KIND] [name=NAME]The Director’s config as JSON (same format as backup-dirjson). Optional resource= / name= filters narrow to one kind / resource.
.sdjson storage=NAME [resource=KIND] [name=NAME]A Storage daemon’s config as JSON — proxied to that SD, which serializes and redacts its own config (including Cloud credentials). Read remote SD config without logging into the SD host.
.fdjson client=NAME [resource=KIND] [name=NAME]A File daemon’s config as JSON — proxied to that FD, which serializes and redacts its own config (Director password, TLS / PKI keys). Read remote FD config without logging into the agent host.
.validate-config data=<base64>Parse and semantically validate a proposed Director config — without applying it. Returns {valid, stage, error} or a resource summary. The dry-run safety gate before a config change.
.reconfigure data=<base64>Apply a new Director config and reload — atomically and rollback-safe. Requires a secure (TLS / TLS-PSK) session.
.reconfigure storage=NAME data=<base64>Push a new config to a Storage daemon — proxied to that SD, which validates + atomically persists + audits it. Takes effect on SD restart (a live device reload mid-job is unsafe).
.reconfigure client=NAME data=<base64>Push a new config to a File daemon — proxied to that FD, which validates + atomically persists + audits it. Takes effect on FD restart.
.restart storage=NAME / .restart client=NAMERestart an SD / FD (via systemd) so a persisted config takes effect. Secure session required. The Director health-probes the daemon afterwards and reports whether it came back. The SD refuses while jobs are active.

.validate-config checks both syntax (stage: "parse", with the offending line) and cross-resource invariants (stage: "semantic" — e.g. exactly one Director, no duplicate resource names, required directives present), exactly as the daemon does at boot. The config is base64-encoded in data= (the console is line-oriented); inputs over 4 MiB are rejected.

Console ACLs apply: a named Console{} needs .dirjson (or dirjson) in its CommandACL to run it. See the Console resource reference.