# Backup & disaster recovery

One database holds your durable state — back it up well and recovery is straightforward.

## What it is

HelixIAM's persistent state lives in a single **PostgreSQL** database: realms, users, clients, keys, policy and audit history. helix-iam-server itself holds nothing that needs backing up beyond that database — protecting your deployment comes down to protecting that one database (plus, in one case below, your keystore, and Redis if you rely on session continuity across a Redis restart).

## Back up and restore the database

HelixIAM ships no backup scripts of its own (the Helm chart has an example `pg_dump` CronJob in `examples/`): use PostgreSQL's own tools, or your managed database's snapshots and point-in-time recovery. A logical backup in PostgreSQL's custom format:

```bash
# Backup (custom format, compressed)
pg_dump --host "$DB_HOST" --username "$DB_USERNAME" --dbname helixiam --format=custom --file helixiam.dump

# Restore into an empty database
createdb --host "$DB_HOST" --username "$DB_USERNAME" helixiam_restore
pg_restore --host "$DB_HOST" --username "$DB_USERNAME" --dbname helixiam_restore --no-owner helixiam.dump
```

On the Docker Compose stack the same tools run inside the `postgres` container:

```bash
docker compose exec -T postgres pg_dump -U helix -d helixiam --format=custom > helixiam.dump
```

Schedule it with whatever runs your other database jobs (a Kubernetes CronJob, your cloud's managed backups), and test restores.

!!! danger "DB_ENCRYPTION is part of your backup"
    Sensitive columns are encrypted with `DB_ENCRYPTION`. A database backup is **useless without the exact same key**, and the key must never change after first boot. Store `DB_ENCRYPTION` securely and separately from the database dump, back it up, and never rotate it. See [security hardening](security.md).

!!! warning "Back up /jks if you seed keys from files"
    If you seed the master-realm signing key from files, include the `/jks` keystore in your backup. Without it, a restored database will not match the published JWKS and tokens will fail to verify. See [Realm keys](realm-keys.md).

## Realm export — portable config backup

The `pg_dump` above is your authoritative, byte-for-byte backup. Alongside it, Helix can export a **single realm** as a self-contained JSON document — realm settings, clients, roles, users, flows, identity providers and more. It is the right tool for migrating a realm between environments, snapshotting config into version control, or seeding a fresh install; it complements (does not replace) the database backup. The examples below assume you have set `$HELIX_URL` / `$REALM` and logged in — see [Authenticating to the API](../getting-started/api-authentication.md).

### Export a realm — `GET /admin/realms/{realm}/export`

```bash
curl -s -b cookies.txt "$HELIX_URL/admin/realms/$REALM/export" > $REALM-export.json
jq 'keys' $REALM-export.json
```
```json
[
  "accountConsole", "adminRoles", "agents", "applications",
  "authorizationServices", "clientProtocolMappers", "clientRoles",
  "clientScopes", "clients", "flows", "formatVersion", "groups",
  "identityProviders", "messageTemplates", "messagingProviders",
  "organizations", "realm", "resourceIndicators", "roles", "samlClients",
  "scimTargets", "serviceAccountRoles", "users", "webhooks", "workloadIdentity"
]
```

The document carries a `formatVersion` and a `requiredEnv` list naming the secrets the target environment must supply (identity-provider, webhook, SCIM-target, messaging and CAPTCHA secrets, as `HELIX_<REALM>_…` variables) — the export never contains those secret **values**. `requiredEnv` is `null` when the realm has none; for a realm `gov` with a webhook named `audit`:

```bash
jq '{formatVersion, requiredEnv}' $REALM-export.json
```
```json
{
  "formatVersion": 2,
  "requiredEnv": [ "HELIX_GOV_WEBHOOK_AUDIT_SECRET" ]
}
```

### Re-import — `POST /admin/realms/{realm}/import`

Restore or seed a realm by POSTing the exported document back (a write, so refresh the CSRF token first):

```bash
curl -s -b cookies.txt -c cookies.txt "$HELIX_URL/admin/realms/$REALM/export" >/dev/null
CSRF=$(awk '($6=="__Host-XSRF-TOKEN"||$6=="XSRF-TOKEN")&&$7~/^r1\./{t=$7} END{print t}' cookies.txt)

curl -s -b cookies.txt \
  -H "Content-Type: application/json" -H "X-XSRF-TOKEN: $CSRF" \
  --data-binary @"$REALM-export.json" \
  "$HELIX_URL/admin/realms/$REALM/import"
```

!!! note "Config, not durable state"
    Realm export is a **configuration** backup, not a substitute for the database dump: it does not carry event history, encrypted credential material, or live sessions. Use it for migration and config-as-code; use `pg_dump` for full disaster recovery. A Keycloak realm JSON can be imported via `POST /admin/realms/{realm}/import/keycloak`.

## RPO and RTO

- **RPO (recovery point objective)** equals your **backup interval** — the most data you can lose. A nightly backup means up to ~24 h of loss. Tighten the RPO toward zero with **WAL archiving / point-in-time recovery (PITR)**.
- **RTO (recovery time objective)** is how long restore takes: provision PostgreSQL, `pg_restore` the dump, restore `/jks` if used, and start helix-iam-server and the console. Because the server carries no state outside the database, it returns to service as soon as the database is up.

## How to recover

1. Provision a PostgreSQL instance.
2. `pg_restore` the latest dump into it (see above), or restore your managed snapshot.
3. Restore the `/jks` keystore if you seed keys from files.
4. Ensure `DB_ENCRYPTION` matches the original.
5. Start helix-iam-server, then the console. With several replicas, restart them all: a replica only reloads identity providers whose version went up, and a restore can lower it.

## See also

- [Realm keys](realm-keys.md)
- [Upgrades & migrations](upgrades.md)
- [Security hardening](security.md)
- [Configuration](../getting-started/configuration.md)
