Command-line tools
Every NGBackup program lives in /opt/backup/bin/.
Daemons
Section titled “Daemons”| Program | Role | Port |
|---|---|---|
backup-dir | Director — schedules and orchestrates | 9101 |
backup-sd | Storage Daemon — reads/writes volumes | 9103 |
backup-fd | File Daemon — agent on protected machines (flag-driven) | 9102 |
Operator tools
Section titled “Operator tools”| Program | Purpose | Bacula equivalent |
|---|---|---|
backup-console | Interactive console (run/restore/status) | bconsole |
backup-cli | Drive the Director from scripts | — |
backup-copy | Copy/migrate volumes between pools | bcopy |
backup-extract | Extract files directly from a volume | bextract |
backup-ls | List the contents of a volume | bls |
backup-scan | Rebuild the catalog from volumes | bscan |
backup-dbcheck | Check and repair the catalog | dbcheck |
gdd-fsck | Offline Global Deduplication store recovery (runs with the SD stopped) | — |
backup-dirjson · backup-sdjson · backup-fdjson · backup-consjson | Dump any config as JSON | — |
Console verbs
Section titled “Console verbs”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.
| Verb | Shows |
|---|---|
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 dir | Director 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 jobs | The long / vertical (key: value) form of list jobs, with the joined columns (clientname, poolname, content, …). |
status tenant | Lists 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.
Dot commands
Section titled “Dot commands”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:
| Command | Returns |
|---|---|
.clients / .pools / .storage / .schedule / .jobs / .filesets / .catalogs / .msgs | The configured resource names of that kind, one per line (or a JSON array). |
.defaultjobs | The Job resource names eligible as run targets. |
.types / .levels / .jobstatus / .volstatus / .actiononpurge | The valid enum values for that field (Job types, backup levels, job-status codes, volume-status codes) — what a dropdown offers. |
.locations | Configured storage locations. |
Introspect config & state as JSON:
| Command | Returns |
|---|---|
.dirjson / .fdjson / .sdjson | The Director / File-daemon / Storage-daemon config as typed JSON (round-trips with config set). |
.status / .jobs / .jobstatus / .jobprogress | Live status / job list / per-job progress in machine form. |
.pluginobjects | The 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:
| Command | Does |
|---|---|
.ls client=NAME path=/dir | Browse 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):
| Command | Does |
|---|---|
.config-show / .config-diff / .config-history / .config-drift | Show 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-delete | Stage an edited config, commit it as a new revision, activate (validate + atomically write + reload), or delete a resource. |
.validate-config | Lint the config without applying — returns an errors + warnings array. |
.reconfigure / .restart | Ask a daemon to hot-reload or restart to apply. |
Enrollment tokens (locked-down onboarding — see Enrollment):
| Command | Does |
|---|---|
.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=ID | List issued tokens with live/used/revoked/expired state, or revoke one. |
.help lists everything the connected Director supports.
Audit trail — list events
Section titled “Audit trail — list events”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.
Fleet inventory
Section titled “Fleet inventory”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 seenversion 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.
Package repository (Director cache)
Section titled “Package repository (Director cache)”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 mediaAll 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.
Licensing
Section titled “Licensing”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:
LicenseFile = /path/grant.licon theDirector {}resource — a single signed grant file (legacy, still supported and wins when set).- A
License {}resource withOfflineFile = /path/grant.lic— the air-gap path; the signed grant is read locally, no network call. - A
License {}resource withSerial+ApiUrl— online 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 jobs (NGREPL)
Section titled “Replication jobs (NGREPL)”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 firstAll 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.
Scheduling lanes
Section titled “Scheduling lanes”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 onceStorage { MaximumConcurrentReplicationJobs = 4 } # this SD: at most 4A 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.
RPO-driven cycles
Section titled “RPO-driven cycles”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).
Storage daemon operations
Section titled “Storage daemon operations”Move a tape between slots — transfer
Section titled “Move a tape between slots — transfer”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.
Hot-reload a daemon config — reload
Section titled “Hot-reload a daemon config — reload”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:
| Directive | Bucket |
|---|---|
MaximumConcurrentJobs, HeartbeatInterval, ClientConnectTimeout, ClientConnectWait, CommCompression, Messages | LIVE |
TLS material (TlsEnable/Require/VerifyPeer/Certificate/Key/CaCertificateFile) | LIVE — acceptor swapped, rotated certs take effect |
SdPort / SdAddress, Device {} registry, DedupDirectory, DedupIndexDirectory, WorkingDirectory, PidDirectory, PluginDirectory, ScriptsDirectory | RESTART |
FipsRequire, Director {} table (peers/passwords) | REFUSE |
File daemon reload:
| Directive | Bucket |
|---|---|
MaximumConcurrentJobs | LIVE |
HeartbeatInterval, Messages | RESTART (LIVE pending the FD config-snapshot work) |
FdPort / FdAddress(es) / FdSourceAddress, WorkingDirectory, PidDirectory, PluginDirectory, TLS / PKI keystore | RESTART |
FipsRequire, Director passwords | REFUSE |
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=NAMEproxies to that Storage daemon, which validates + atomically persists + audits the edit (takes effect on SD restart).fd=NAMEproxies 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"Offline dedup-store recovery — gdd-fsck
Section titled “Offline dedup-store recovery — gdd-fsck”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.
gdd-fsck check --store <dir> [--index <dir>] # non-mutating consistency checkgdd-fsck rebuild-esm --store <dir> # rebuild esm0/esm1 from container headersgdd-fsck rebuild-index --store <dir> [--index <dir>] # rebuild the redb index from manifestsgdd-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-degraded — dedup … vacuum refuses until refcounts are reconstructed.
Validate a config
Section titled “Validate a config”backup-dir -t -c /opt/backup/etc/backup-dir.confbackup-sd -t -c /opt/backup/etc/backup-sd.confbackup-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.
| Command | Returns |
|---|---|
.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=NAME | Restart 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.