Skip to content

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:

environment:
    - NODE_ENV=production
    - LOG_LEVEL=debug
    - LOG_FORMAT=json

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.