Back up and restore
pfCentral’s identity lives in two places that must match: PostgreSQL and the state volume. Back them up together, from the same moment. With both, you can restore the server and every firewall reconnects on its own. With only one, you start over.
What to back up
Section titled “What to back up”| Item | Where | Why |
|---|---|---|
| PostgreSQL database | Your database | Users, roles, devices, hashed secrets, NATS account records, sessions, audit events |
| State volume | /data/state in the container | NATS operator and account keys, internal credentials, the NATS CA, JetStream data |
| Token pepper | PFCENTRAL_TOKEN_PEPPER_* | Without it no stored secret can be checked |
| Email encryption key | PFCENTRAL_EMAIL_ENCRYPTION_KEY | Without it queued emails cannot be sent |
| Your deployment files | compose.yaml, manifests, proxy config | To rebuild the same setup |
The firewalls’ own configurations are not part of this server backup. As an admin or owner, use General Setup → Backup Config to download the complete saved active configuration from each online firewall. Protect the XML: it can contain passwords and private keys. Restore firewall configurations in pfSense (Diagnostics → Backup & Restore); pfCentral does not implement restore.
What happens if you lose one half
Section titled “What happens if you lose one half”| Lost | Result |
|---|---|
| State volume, database kept | The server refuses to start (desynchronised state). Recovery means a fresh install: new owner, and every firewall added and enrolled again. |
| Database, state volume kept | The server refuses to start for the same reason. Same recovery. |
Only state/nats/js/ (JetStream) | Harmless. Recent telemetry and caches are rebuilt. |
Only state/nats/tls/ | The server makes a new CA on start. Every agent must download it again (Save in Services → pfCentral → Settings). |
Take a backup
Section titled “Take a backup”The keys in the state volume do not change after the first start, so a short pause is enough for a consistent copy.
-
Dump the database.
Terminal window docker compose exec -T postgres pg_dump -U pf_central -Fc pf_central > pf_central-$(date +%F).dump -
Archive the state volume. The image has no shell, so copy it with a helper container:
Terminal window docker run --rm -v pf-central_state:/data:ro -v "$PWD":/backup alpine \tar czf /backup/pf_central-state-$(date +%F).tgz -C /data state --exclude=state/nats/jsReplace
pf-central_statewith your volume name (docker volume ls). Excludingjs/keeps the archive small; it only holds telemetry. -
Encrypt and store both files together, with the date in the name, away from the server. They contain private keys.
Run this daily, and again before every upgrade.
Restore
Section titled “Restore”-
Stop pfCentral:
docker compose stop pf-central. -
Restore the database into an empty database:
Terminal window docker compose exec -T postgres pg_restore -U pf_central -d pf_central --clean --if-exists < pf_central-2026-10-17.dump -
Restore the state volume:
Terminal window docker run --rm -v pf-central_state:/data -v "$PWD":/backup alpine \sh -c 'rm -rf /data/state && tar xzf /backup/pf_central-state-2026-10-17.tgz -C /data && chown -R 65532:65532 /data/state' -
Set the same token pepper and email key as before, and start:
docker compose up -d pf-central. -
Check the log for
server listeningwith nodesynchronisederror. Firewalls reconnect within a minute.
Devices enrolled after the backup are not in it. Enroll them again.
Moving to a new host
Section titled “Moving to a new host”Restoring onto a new host is the same procedure. Keep the same DNS name, so agents find the new server without changes. If the name must change, every firewall needs a new NATS URL, and with TLS the new name must be in PFCENTRAL_NATS_TLS_HOSTS.
Start over
Section titled “Start over”If you have no matching pair, stop the server, delete the state volume and the database, start again, create the owner, and re-enroll every firewall. On each firewall, paste a new token in Services → pfCentral → Settings and Save.