WarmHawk
Docs / Self-hosting / Backups & Redis durability

A backup you’ve never restored is a guess, not a plan.

Two independent durability stories: Postgres backs up nightly to infrastructure you control, and Redis — which holds the live send queue, not just a cache — is configured for crash-safe durability by default.

WarmHawk backs up Postgres nightly (pg_dump, gzip, your own local path or rclone remote — never WarmHawk’s infrastructure) with a full restore procedure below, and runs Redis with AOF durability (appendfsync everysec, noeviction) because Redis backs the real BullMQ send queue — losing unflushed writes there means a lead silently never gets emailed.

How nightly Postgres backups work

scripts/backup-postgres.sh runs on a nightly schedule inside your instance: pg_dump, piped through gzip, written to a destination you configured — a local path by default, or your own rclone remote (your own S3/B2 bucket) if you set one up during install. Enabled via a one-time install prompt (default yes), 14-day retention by default, configurable directly in the script.

API details (Tier 0 / self-hosters) · Run a backup right now, before a risky change▸
docker compose exec api /app/scripts/backup-postgres.sh
ls -lh /var/backups/warmhawk/

Restoring from a backup

  1. Stop the services that write to Postgres.
    API details (Tier 0 / self-hosters) · Stop dependent services▸
    docker compose stop api worker
  2. Restore the gzipped dump.
    API details (Tier 0 / self-hosters) · Restore from a gzipped dump▸
    gunzip -c /var/backups/warmhawk/warmhawk-2026-08-20.sql.gz \
      | docker compose exec -T postgres psql -U warmhawk -d warmhawk
    Restoring onto a database with existing (possibly corrupted) data: drop and recreate it first so the restore starts clean.
  3. Restart the API and worker.
    API details (Tier 0 / self-hosters) · Bring services back up▸
    docker compose start api worker
  4. Verify it actually worked.
    API details (Tier 0 / self-hosters) · Verify health, then spot-check data▸
    docker compose ps
    docker compose exec postgres psql -U warmhawk -d warmhawk -c "SELECT count(*) FROM leads;"

Redis durability: why it isn’t just a cache

Redis backs the BullMQ dispatch queue — mailbox rotation, cadence/jitter scheduling, every job waiting to send. Losing unflushed writes on a crash or restart means a lead silently never gets emailed, with no error surfaced anywhere else in the system. That’s why the bundled ops/redis.conf configures real durability instead of Redis’s cache-friendly defaults:

API details (Tier 0 / self-hosters) · ops/redis.conf — the durability-relevant lines▸
appendonly yes
appendfsync everysec
aof-use-rdb-preamble yes

save 900 1
save 300 10
save 60 10000

maxmemory-policy noeviction
  • AOF, fsync every second — every write is logged to an append-only file, flushed at most once a second, so a crash loses at most a second of writes instead of everything since the last RDB snapshot.
  • RDB snapshots as a second safety net, layered on top of AOF, not instead of it.
  • noeviction — Redis refuses new writes rather than silently evicting queue data under memory pressure. A refused write is loud and recoverable; a silently evicted job is neither.
  • A crash-recovery reconciliation cron cross-checks ExecutionLog rows against what BullMQ actually has queued on every restart, and re-enqueues anything that looks lost — a second line of defense behind AOF, not a substitute for it.

Redis’s password (REDIS_PASSWORD) is passed via --requirepass on the command line rather than written into the conf file, so it never ends up committed anywhere — generated once by install.sh and stored only in your own .env.

A backup you’ve never restored is a guess, not a plan. Run the restore procedure above once against a spare box or a disposable instance — not production — so you know firsthand it works before an actual incident makes that the first time you’ve tried it.

Rolling back a bad update rather than a data problem? See warmhawk update failures — a rollback to the previous image tag may be all you need, without touching the database at all.

Questions

Backups & Redis durability: questions worth answering up front

Does WarmHawk ever receive or store a copy of my backups?+

No. Backups are written entirely to your own configured destination — a local path by default, or your own rclone-driven remote (your own S3/B2 bucket) if you set one up. WarmHawk never receives or stores a customer’s backup on any WarmHawk-operated infrastructure.

Why does Redis durability matter if Postgres is the real database?+

Redis backs the BullMQ dispatch queue itself, not just a cache — losing unflushed writes on a crash or restart means a lead silently never gets emailed, with nothing else in the system surfacing an error. That is why it runs AOF (append-only file), not the eviction-friendly defaults.

Should I actually test a restore before I need one?+

Yes, and not optionally. Run the restore steps below against a spare box or a disposable instance at least once so you know the exact commands work, before an actual incident forces you to learn them under pressure.

What happens to in-flight queue jobs if Redis crashes anyway?+

A crash-recovery reconciliation job cross-checks ExecutionLog rows against the queue on restart and re-enqueues anything that looks lost — a second line of defense behind AOF durability, not a replacement for it.