Backup & Restore
Audience: Ops. This chapter describes what is in a backup, how to trigger manual and automatic backups, how restore works and what the backup does not catch. For initial setup see deployment.md, for version upgrades upgrade.md.
1. What does a backup contain
A GateControl backup is a single JSON file (format version 3 since v1.35). Contents:
- peer_groups — names, colours, descriptions
- peers — name, public key, encrypted private/preshared keys,
allowed_ips, DNS, keepalive,enabledflag, tags,expires_at, group reference by name - routes — domain, target IP/port, peer reference by name, all feature flags (HTTPS, Basic-Auth, ACL, rate limit, retry, backends, sticky sessions, circuit breaker, mirror, debug, bot blocker, custom header), ACL peer list by name
- settings — complete key-value store (SMTP, security, alerts, branding, monitoring, DNS override, split tunnel, auto backup config, …)
- webhooks — URL, events, description, enable flag
- route_auth — email/TOTP auth configs per route (incl. encrypted TOTP secret)
What is not included:
- Caddy TLS storage (
/data/caddy/) — certificates are fetched anew after restore. With Let's Encrypt that's OK, but stays within the rate limits (50 certs/week per domain). For a short split-apart no problem; for the collapse of a whole domain list in short succession, a rate limit threatens. - Access logs (
/data/caddy/access.log), audit log tableactivity_log, traffic snapshots, login attempts, API token plaintext (are only stored hashed after issue), session cookies. - SQLite WAL files, the auto-backup archive under
/data/backups/itself. - WireGuard server keypair (
/data/wireguard/wg0.conf). That must be backed up separately (see section 6). Without the server key all existing peer configs fail because the public key no longer matches. - License token (
/data/.license-token). Is fetched anew on re-validation, providedGC_LICENSE_KEYstays set.
2. Manual backup
Via UI
Settings → Backup → Create backup now. The browser downloads
a file gatecontrol-backup-YYYY-MM-DD.json.
Via API
curl -u admin@token:<token> \
https://gate.example.com/api/v1/settings/backup \
-o backup-$(date -u +%F).json
-u admin@token:<token> uses an API token with scope settings:read.
Alternatively with session cookie see API.md.
The response is the JSON backup directly.
The endpoint itself (GET /api/v1/settings/backup) delivers the backup object
regardless of the query parameter; ?format=download additionally sets the
Content-Disposition header, but is optional for curl.
3. Auto-Backup-Jobs
Setup in the UI
Settings → Backup → section Automatic backups:
- Enabled (toggle)
- Interval —
6h,12h,daily,3d,weekly - Retention —
1to100versions. Older ones are deleted automatically after every run.
The backups land in the container under /data/backups/ as
gatecontrol-YYYYMMDD-HHmmss.json. Via the UI you can
view the list, download or delete individual files.
API endpoints
# Aktuelle Job-Konfig abrufen
GET /api/v1/settings/autobackup
# Konfig updaten (Body: { enabled, schedule, retention })
PUT /api/v1/settings/autobackup
# Sofort manuell auslösen (nutzt die konfigurierte Retention)
POST /api/v1/settings/autobackup/run
# Verzeichnis-Listing
GET /api/v1/settings/autobackup/list
# → [{ filename, size, created }]
# Einzeldatei runterladen / löschen
GET /api/v1/settings/autobackup/download/:filename
DELETE /api/v1/settings/autobackup/:filename
Filename format is strict: gatecontrol-YYYYMMDD-HHmmss.json. Anything else
is rejected by the API (protects against path traversal).
Failure-Handling
If autobackup_run fails:
- Entry in the
activity_logwithseverity=error. - If in Settings → Alerts an alert email is configured AND SMTP
is configured, a mail with subject
[GateControl] Automatic backup failedis sent. - The scheduler continues — at the next slot a new attempt is made.
Feature flag: Auto-backup only runs with scheduled_backups — Community build
has that on, for license restrictions see concepts/licensing.md.
4. Restore
Via UI
Settings → Backup → Restore:
- Choose JSON file.
- Preview — shows the summary (number of peers/routes/settings/webhooks/…). The preview route validates, but runs read-only.
- Confirm restore. The mode is always replace — existing peers, routes, settings, webhooks and route auth entries are completely replaced. A "merge" does not currently exist.
- After successful restore, the CSRF token rotates and you are logged in again in the UI.
Via API
curl -u admin@token:<token> \
-F "backup=@gatecontrol-backup-2026-04-20.json" \
https://gate.example.com/api/v1/settings/restore
Optional preview endpoint (without side effects):
curl -u admin@token:<token> \
-F "backup=@..." \
https://gate.example.com/api/v1/settings/restore/preview
Response { ok: true, summary: { version, created_at, peers, routes, … } }.
What happens server-side
-
Validation — structure, version compatibility (2 or 3), peer names, domains, ports, webhook URLs.
-
Decrypt check — each encrypted private/preshared key is decrypted as a trial with the current
GC_ENCRYPTION_KEY. If that fails for any peer, the restore is aborted with a clear error message, before the DB is touched:Peer "nas-home": cannot decrypt private key — the backup was created with a different GC_ENCRYPTION_KEY -
Transaction — a single SQLite transaction block deletes
route_peer_acl,route_auth_otp,route_auth_sessions,route_auth,routes,peers,peer_groups,settings,webhooksand writes them anew. On error: automatic rollback, the old state is intact. -
WireGuard — after successful commit
wg0.confis regenerated from the peer set and the interface reloaded viawg syncconf. -
Caddy — new config JSON is built and pushed via the admin API.
5. Special case: gateway peers after restore
Home gateways register against the server using a per-gateway
issued JWT. The signing key of these tokens is a derivative of
GC_SECRET.
-
Same server, same volumes →
GC_SECRETstays, gateway tokens remain valid. No action needed. -
Restore on a freshly set-up instance → new
GC_SECRET, gateway tokens from old.envfiles on the NAS are rejected. Then:- Open peer in the UI → Regenerate gateway pairing.
- Have a new
.envgenerated for the gateway companion and deploy it to the NAS/RPi. - There run
docker compose up -d --force-recreate gateway.
Alternatively: carry GC_SECRET over from the old volume — then no
re-pairing is needed. This is the recommended path if you simply move to a new
VPS: take the old .env + /data/.session_secret + /data/.encryption_key
with you, then restore on the new instance.
6. Offsite strategy & full volume strategy
The JSON backup is a logical backup. For full disaster recovery you must additionally back up the physical volumes.
Recommended setup
Daily (automatic):
- Auto-backup job runs, writes into
/data/backups/. - An external system (uptime server, monitoring host) fetches every night via
API token
GET /api/v1/settings/backupand archives the JSON in Git or object storage.
Weekly (manual or via cron on the host):
-
Snapshot the Docker volume
gatecontrol-dataviatar:docker run --rm \ -v gatecontrol-data:/data:ro \ -v /var/backups/gatecontrol:/out \ alpine \ tar czf /out/gatecontrol-volume-$(date -u +%F).tar.gz /data -
Rotation: 4 weekly + 12 monthly snapshots.
S3 / B2 / rsync target
The UI currently offers no direct S3 upload target for auto backups — that's a roadmap feature in the backlog. Workaround:
# Cron auf dem Host, läuft nach dem Auto-Backup-Fenster
0 4 * * * aws s3 sync /var/lib/docker/volumes/gatecontrol-data/_data/backups/ \
s3://company-backups/gatecontrol/
Or fetch from outside via API token and push to S3:
curl -sS -H "Authorization: Bearer $GC_API_TOKEN" \
https://gate.example.com/api/v1/settings/backup \
| aws s3 cp - s3://company-backups/gatecontrol/backup-$(date -u +%F).json
7. Testing a backup
A backup that you haven't regularly tested is not a backup.
Minimal test recipe:
- Spin up a second empty VPS with an identical Docker stack (see deployment.md).
- Enter the old
GC_ENCRYPTION_KEYandGC_SECRETfrom the prod volume into the test.env(otherwise the decrypt check fails). - Start the container.
- Upload the backup JSON via Settings → Backup → Restore.
- Check peer list, routes, settings.
- Connect an existing peer with the config and force a handshake — if that works, the key material is okay.
- Throw away the VPS again.
Recommendation: do this every 3–6 months or if you changed the backup format (schema migration).
8. What backup does not catch
| Scenario | JSON backup | Volume snapshot | Fix |
|---|---|---|---|
| Admin accidentally deletes all peers | yes | yes | Restore from last auto backup |
| Disk failure / volume corruption | partially | yes | Volume snapshot restore, then refetch Caddy certs |
| Compromised admin account | no | no | Revoke API tokens, password + 2FA, check audit log |
Lost GC_ENCRYPTION_KEY |
NO | no | Private keys lost → roll out all peers anew |
| Docker image corruption after bad push | no | no | Pin image tag to last known version |
| Destroyed host OS | no | no | Rebuild VPS, replay volume tarball |
| Compromised VPS (host root) | no | no | Completely rebuild, rotate secrets, audit |
Rule of thumb: JSON backup secures the configuration. Volume snapshot secures secrets + TLS storage. Both together make disaster recovery realistic.
For incident debugging during restore also see troubleshooting.md, section "Database".