Configuration¶
Both the server and the agent are configured through a config.yaml and a handful of
environment variables. This page is the reference for both; the installation itself is on
Installing the Server and
Installing a Client Agent.
config.yaml is validated against a schema at startup, and an invalid value aborts the
start with a message naming the field — a configuration error is not something to discover
on the first tunnel lease hours later. Keys the schema does not know are preserved, so
anything you add by hand survives the next save; the server logs a warning for each one at
startup (Unknown key in config.yaml -- ignored), so a misspelled key does not leave its
default in force unnoticed. The file is created with defaults on
first start; jwtSecret and secretKey are generated before the check runs.
Environment variables¶
Logging¶
| Variable | Values | Default | Description |
|---|---|---|---|
LOG_LEVEL |
debug, info, warn, error |
info |
Verbosity of the logs. |
LOG_FORMAT |
pretty, one-line, json |
auto | pretty for single-line, coloured logs (the default in development). json for structured output (the default in production). one-line is an accepted alias of pretty — the transport always sets singleLine, so there is no separate multi-line mode for it to switch off. |
NODE_ENV |
development, production |
development |
Controls general behaviour such as the logging defaults. |
In Compose these go into the service's environment: block:
Client-only overrides¶
| Variable | Description |
|---|---|
PBCM_CLIENT_PORT |
Overrides listenPort from config.yaml. Needed with network_mode: host when 3001 is taken — the same port must then appear in the client's target address on the server. |
PBCM_CLIENT_CONFIG |
Path of the agent's config.yaml (default: client/config.yaml). |
PBCM_CLIENT_DATA_DIR |
Where the agent keeps its jobs, schedule state and run history (default: client/data, the client-data volume in the image), and its identity.json. For an agent that runs outside the container. |
PBCM_REGISTRATION_SECRET |
Outbound mode: a secret the dashboard's outbound wizard accepts in place of the setup PIN from the agent's log, for a rollout where nobody reads that log. Remove it once the agent is registered. |
PBCM_REGISTRATION_SECRET_FILE |
The same, read from a file (e.g. /run/secrets/…). Setting both variables, or a file that cannot be read or is empty, ends the start. |
Server-only overrides¶
| Variable | Description |
|---|---|
PBCM_SERVER_PORT |
Overrides port from config.yaml. An unusable value ends the start. The container's health check follows the port either way, whether it comes from here or from config.yaml (see Health). |
PBCM_TRUSTED_PROXIES |
Overrides security.trusted_proxies, comma separated (e.g. 172.16.0.0/12,::1). An unusable entry ends the start. |
PBCM_STATUS_TOKEN, PBCM_STATUS_TOKEN_FILE |
Overrides statusToken; _FILE names a file that holds it (a Docker secret). Setting both, or a value shorter than 32 characters, ends the start. |
Server¶
server/config.yaml — in the container at /app/server/config.yaml, bind-mounted from
server-config.yaml on the host. A copy to start from is
server/config.example.yaml.
Listening¶
| Key | Description |
|---|---|
port |
Port the server listens on (default: 3000), overridden by PBCM_SERVER_PORT. The published port: EXPOSE, the compose port mapping and every agent's serverUrl have to follow it. |
logLevel |
pino log level, overridden by LOG_LEVEL. |
Authentication¶
| Key | Sub-key | Description |
|---|---|---|
oidc |
enabled |
Offers login through the OIDC provider next to the local login (default: false). While it is false, the four fields below may stay empty; once it is true, all four are required and a missing or malformed one stops the start. |
issuer |
The provider's issuer URL. Its /.well-known/openid-configuration is read on start. |
|
client_id |
Client id of this application, as registered at the provider. | |
client_secret |
Client secret of this application, as registered at the provider. | |
redirect_uri |
Where the provider sends the browser back after login: https://<this server>/api/auth/callback. Register exactly this URL at the provider. |
|
jwtSecret |
(root) | Signs the session tokens. Generated automatically if absent. Losing it invalidates every session. |
secretKey |
(root) | Encrypts every secret the server stores and has to read back: the SSH keys of the tunnels, the PBS token secrets of the repositories and the auth tokens of outbound clients. Generated automatically if absent; do not copy it between installations. Losing or changing it means entering every repository secret and tunnel key again and registering every outbound client again. See below. |
jwtExpiresIn |
(root) | How long a login stays valid, in any span @fastify/jwt accepts (default: 12h). Before this had a default, a token signed without one never expired. |
statusToken |
(root) | What a reader without a session sends as Authorization: Bearer <token> to read GET /api/v1/status. At least 32 characters, e.g. openssl rand -base64 32. Opens that route and no other, and only reads. Unset (default), the route needs a session. PBCM_STATUS_TOKEN (_FILE) wins; a value from the environment is never written into config.yaml. Read at startup only. |
Retention¶
The settings: block below documents the defaults rather than changing them — these are
the same values the server falls back to when a key is missing, and writes into the file
at startup.
| Key | Default | Description |
|---|---|---|
settings.retention_job_history_days |
90 |
Days a job history entry stays in the database. 0 means no age limit. |
settings.retention_job_history_count |
50 |
Keep at least this many history entries per client (at least 1). |
settings.job_history_cleanup_interval_hours |
24 |
Hours between two automatic job history cleanups. 0 switches the timer off. |
The cleanup can also be run from its tab on the settings page, which shows its last run, the next one and its result. The first run after a restart comes one interval after the last run, not at startup.
What 0 means depends on the key
An interval of 0 switches the timer off. A job history retention of 0 days only drops
the age limit; the per-client count still applies.
Removed keys
token_retention_days, token_cleanup_interval_hours and the older
retention_invalid_tokens_days / retention_invalid_tokens_count are gone: a
registration token is a property of its client and leaves nothing to clean up. The
server ignores them and logs them as unknown keys at startup. Remove them from
config.yaml by hand.
Security¶
| Key | Description |
|---|---|
security.allowed_networks |
CIDR networks an agent may connect to /ws/agent from. Empty (default) allows every address. See Address checks. |
security.trusted_proxies |
Reverse proxies whose X-Forwarded-For and X-Forwarded-Proto the server believes: IP addresses, CIDR networks (v4 or v6), or loopback, linklocal, uniquelocal. Empty (default) believes no one. Behind a proxy, list it — see Reverse proxy. Read at startup only; overridden by PBCM_TRUSTED_PROXIES. |
security.allow_self_signed_agent_certificates |
Accept an agent certificate this server cannot verify when dialling an outbound agent over wss:// (default: false). Applies to every outbound agent alike, and only to target addresses written wss://host:port. Usual for a self-signed agent certificate on a home network; leave it off when the agent's certificate comes from a CA this host trusts. The PBS certificate is not affected — it is pinned by its fingerprint. See TLS to an outbound agent. |
security.hsts |
Send Strict-Transport-Security (default: false). Only switch this on behind TLS. The header tells browsers to refuse http:// for this host from then on, they remember it for months, and turning the header off again does not undo it — on a plain-HTTP installation it locks your users out. |
Stored secrets¶
secretKey is generated per installation; sharing the file would share one key across every
installation that copied it. It replaces tunnel.keySecret, which encrypted only the SSH keys
of the tunnels. A tunnel.keySecret left in an older file is no longer read and can be
removed — tunnel keys stored with it have to be entered again.
The file has to be writable on first start. A key that is generated and cannot be written
back would be gone after the next restart, and everything encrypted with it in between. The
server therefore refuses to encrypt with such a key: the upgrade that encrypts the existing
secrets stops the start, and storing a new secret fails, until config.yaml can be written.
Tunnel¶
The tunnel: section is written with its defaults on first start, which is why
config.example.yaml does not carry it. The parameters are documented in
SSH Reverse Tunnel.
Client¶
client/config.yaml — in the container at /app/client/config.yaml, bind-mounted from
client-config.yaml on the host. A copy to start from is
client/config.example.yaml.
Identity and connection¶
| Key | Description |
|---|---|
serverUrl |
HTTP(S) URL of the management server (e.g. https://backup-server:3000). The agent derives the WebSocket address itself: http/https become ws/wss and /ws/agent is appended. Leave unset for outbound mode — its absence is what puts the agent into it. |
listenPort |
TCP port of the local Web UI and, in outbound mode, of the /ws/register and /ws/agent endpoints the server dials (default: 3001). A changed port must also appear in the client's target address on the server. Overridden by PBCM_CLIENT_PORT. The container's health check follows the port either way. |
tls.cert / tls.key |
Serve the agent's web server over TLS: paths to the certificate and its private key (default: unset, plain HTTP). Relative paths resolve against the agent's directory. An unreadable file stops the start — there is no fallback to HTTP. In outbound mode the client's target address on the server must then be written wss://host:port. See TLS to an outbound agent. |
allowSelfSignedCertificates |
Accept a PBCM server certificate that does not validate (self-signed), for registration and the WebSocket connection (default: false). The PBS certificate is not affected; it is pinned by its fingerprint. |
allowedNetworks |
Outbound mode only: CIDR networks the server may dial this agent from, checked on /ws/register and /ws/agent. Empty (default) allows every address. The local Web UI on the same port is not restricted by it — it is guarded by the setup PIN instead. |
enableStatusPage |
Serve the status page at /status (default: true). |
enableRegisterPage |
Serve the register page at /register and POST /api/register behind it (default: true). Both close by themselves once the agent is registered; switch it off for an agent that should only ever be registered by the server (outbound). Without it, a setup PIN is printed only for outbound mode (no serverUrl, no PBCM_REGISTRATION_SECRET). |
Job execution¶
| Key | Description |
|---|---|
executable |
Path to the proxmox-backup-client executable (default: proxmox-backup-client). In the container image the CLI is already on PATH. |
backupParams |
Static arguments appended to every backup job, as a flat list of alternating flag and value (e.g. ["--all-file-systems", "true"]). Must not contain --backup-time: the agent sets the snapshot time itself, and a run with it is refused. |
restoreParams |
The same for restore jobs. |
preScript / postScript |
Optional scripts run before and after a job. Arguments: $1 = operation (backup/restore), $2 = job name. A non-zero exit from preScript aborts the operation. The path is inside the container, so the script has to be mounted in. |
queueDelaySeconds |
Seconds to wait before restarting a queued job (default: 5). |
tunnelAcquireJitterSeconds |
Random delay (0..N seconds) before requesting the SSH reverse tunnel, so that many jobs firing in the same minute do not hit the tunnel limit at once. Outbound clients only; 0 disables it (default: 30). |
Storage and logging¶
| Key | Description |
|---|---|
logLevel |
Verbosity, overridden by LOG_LEVEL (default: info). |
logCapBytes |
Bytes of stdout and stderr kept per run, each channel separately (default: 262144, i.e. 256 KB). Head and tail are kept with the middle dropped and marked. Values below 1024 are ignored. |
There is no setting for how long the history is kept. The agent keeps every run until the
server has acknowledged it (at most 500 of them), and of those the newest 50; see Data Files in
the client architecture. A retentionTime left over in an older config.yaml is ignored
with a warning.
Network and identity¶
Address checks for agent connections¶
security.allowed_networks, a client's allowed IP or network and the agent's
allowedNetworks decide where an agent connection may come from. What each of the three
asks, and when to leave the per-client check off, is in
Security.
Client identity¶
A client is identified by a pair: the clientId and the authToken. Both are issued by the
server during registration and stored together in identity.json in the agent's data
directory -- not in config.yaml, which holds only what the operator sets. The agent never
picks either for itself, and an older agent that still has them in config.yaml moves them
over on its next start. The file holds the token in plain text, so the data volume deserves
the same protection as the host's other secrets. Every agent connection presents both
(/ws/agent?clientId=…&token=…), and the server only admits it if the two name the same
client. Neither half is sufficient on its own — and the id in particular is not a secret,
since the same value is the PBS --backup-id and can be read from any snapshot name.
The clientId also decides which snapshots the UI shows under a client. It must therefore
never be edited by hand: a changed id starts a new snapshot group in PBS and orphans
everything backed up so far.
Registering the agent — through its Web UI or from the server in outbound mode — requires the setup PIN. While the agent has no identity it prints one to its log on every start:
──────────────────────────────────────────────
Setup PIN: 7K4M-9QX2
Web UI: http://<this-host>:3001/register
Outbound: enter it in the server's Add Client wizard
The PIN is required to register this agent.
──────────────────────────────────────────────
Read it with docker compose logs pbcm-client and enter it alongside the server URL and
the registration token. Without it the endpoint answers 403 — otherwise anyone able to
reach listenPort could point an unregistered agent at a server of their own, since the
caller supplies both the URL and the token. The PIN is held in memory only, is rotated
after five failed attempts, and stops existing once the agent is registered. The Web UI line
appears only with the register page, the Outbound line only without a serverUrl.
In outbound mode enter the PIN in the dashboard's outbound wizard instead; the server
presents it when it dials the agent, and allowedNetworks restricts who may dial at all. For
an unattended rollout set PBCM_REGISTRATION_SECRET (or PBCM_REGISTRATION_SECRET_FILE) on
the agent and enter that value instead of the PIN; the agent then prints no PIN unless its
register page is enabled.
An agent that already holds an identity refuses to register again — its register page and
POST /api/register are closed (404), and /ws/register closes with 4003 Already
registered in outbound mode. To re-register a host on
purpose, delete identity.json from its data directory first, and delete the client's old
row in the UI afterwards. A restarted agent prints a fresh setup PIN.