Backup & Recovery
Backup & Recovery
ISO 27001 Control: A.17.1 — Information Security Continuity
Compliance gap closed: ISO-12
Introduced in: v1.0.319
Overview
The platform stores HMRC-submitted financial records subject to a 7-year statutory retention requirement under HMRC data access obligations and GDPR Article 5(1)(e). This page describes how the platform meets its backup, recovery, and continuity obligations.
The full internal strategy document lives at docs/BACKUP-RECOVERY.md in the repository.
Database Platform: Neon Postgres
Production data is hosted on Neon Postgres, which provides:
- Point-in-Time Recovery (PITR) — continuous WAL archiving to S3
- Copy-on-write branching — instant isolated branches for safe test restores
- Multi-AZ redundancy — available on the Business plan
Required plan
The production Neon project must be on the Business plan to ensure a 30-day PITR retention window. Lower plans (Free, Launch, Scale) provide only 1–7 days, which is insufficient for this platform's compliance requirements.
Verify your plan at: Neon Console → Project Settings → Plan
Recovery Objectives
| Metric | Target | Basis |
|---|---|---|
| RPO (Recovery Point Objective) | < 1 hour | Neon WAL archive is near-continuous; practical RPO is minutes |
| RTO (Recovery Time Objective) | < 4 hours | Step-by-step recovery procedures documented below |
| Data Retention | 7 years | HMRC statutory requirement for submitted financial records |
Backup Layers
1. Neon PITR (Primary — 30-day window)
Neon automatically archives WAL data continuously. No manual schedule is required. This covers recovery from any incident discovered within 30 days.
To verify WAL archiving is active:
- Open the Neon Console
- Select the production project
- Go to Settings → Backups
- Confirm WAL archiving is enabled and the retention window is 30 days
2. Monthly pg_dump Archive (Long-Term — 7-year retention)
Neon PITR only covers 30 days. To meet the HMRC 7-year requirement, a monthly pg_dump export to a durable object store (AWS S3 with Glacier lifecycle, or equivalent) must be configured separately.
Recommended setup:
- GitHub Actions workflow on
schedule: cron('0 2 1 * *')(1st of each month, 02:00 UTC) - Dump format: PostgreSQL custom format (
pg_dump -Fc) - Storage: S3 bucket with Versioning + Object Lock (WORM) enabled
- Lifecycle rule: transition to Glacier after 90 days, expire after 7 years
# Example archive command
pg_dump $DATABASE_URL -Fc -f backup-$(date +%Y-%m).dump
aws s3 cp backup-*.dump s3://your-backup-bucket/monthly/
⚠️ Action required for operators: This monthly archive workflow is not yet automated in the platform. Configure it before going live in production.
Recovery Procedures
Scenario 1 — Accidental Data Deletion (within 30 days)
- Log in to Neon Console
- Navigate to Branches → main → Restore
- Select a timestamp before the data loss event
- Neon creates a new branch at that point in time
- Run the verification queries to confirm data integrity
- Promote the restored branch to primary (Branches → [restored branch] → Set as Primary)
- Update
DATABASE_URLin your deployment environment if the connection string changed, then redeploy - Notify affected customers if HMRC submission data was lost
Expected RTO: 1–2 hours
Scenario 2 — Catastrophic Loss (database unrecoverable)
- Create a new Neon project (or branch) in the same region
- Restore from the most recent clean state:
- Via Neon PITR: restore WAL archive to the last known-good timestamp
- Via monthly archive: download the latest
.dumpfrom S3 and restore:pg_restore -d $NEW_DATABASE_URL --no-owner --no-privileges backup-YYYY-MM.dump
- Apply any schema migrations that postdate the archive:
npm run db:push - Verify row counts with the verification queries
- Update
DATABASE_URLand redeploy - Notify affected users and file an HMRC incident report if submitted data was affected
Expected RTO: 2–4 hours (PITR) | 4–6 hours (monthly archive)
Scenario 3 — Corruption or Ransomware
- Immediately isolate the environment — revoke all database credentials
- Identify the last known-good timestamp from application logs / Sentry
- Follow Scenario 1 or 2 depending on the age of the corruption
- Rotate all secrets:
DATABASE_URL,NEXTAUTH_SECRET, encryption keys - Conduct a full security audit before restoring service
Automated Weekly Verification
An Inngest function (backup-verification) runs every Monday at 05:00 UTC to perform automated data integrity checks against the production database.
Checks performed
| Check | What it validates |
|---|---|
organisation_count | At least one org record exists (DB not wiped) |
transaction_count | Financial records present with most-recent transaction date |
hmrc_submission_count | Quarterly summary records intact, broken out by status |
property_count | Property records present |
audit_log_continuity | Audit log has entries within the last 72 hours |
neon_branch_restore | Creates and deletes a test Neon branch to confirm PITR/branching is operational |
The neon_branch_restore check only runs when NEON_API_KEY and NEON_PROJECT_ID are set. All other checks always run.
Results
Verification results are written to the audit_log table with action = 'backup.verification_complete'. If any check fails, an admin notification is sent.
To query recent verification results:
SELECT created_at, metadata
FROM audit_log
WHERE action = 'backup.verification_complete'
ORDER BY created_at DESC
LIMIT 10;
Environment variables
| Variable | Required | Purpose |
|---|---|---|
NEON_API_KEY | Optional | Neon Management API key — enables the branch test-restore check |
NEON_PROJECT_ID | Optional (with NEON_API_KEY) | Neon project identifier |
Obtain NEON_API_KEY from Neon Console → Account Settings → API Keys.
Obtain NEON_PROJECT_ID from Neon Console → Project Settings.
Manual Quarterly Verification
Every quarter, a member of the engineering team must perform a manual test restore:
- In Neon Console, create a branch from a timestamp 7 days ago
- Run the verification queries below against the restored branch
- Confirm row counts match expected values
- Delete the test branch
- Record the outcome in the incident log
Verification Queries
Run these against any restored branch to confirm data integrity:
-- 1. Transaction count sanity check
SELECT COUNT(*) AS total_transactions,
MIN(date) AS earliest_transaction,
MAX(date) AS latest_transaction
FROM transactions;
-- 2. HMRC submission integrity
SELECT status, COUNT(*) AS count
FROM quarterly_summaries
GROUP BY status;
-- 3. Property records
SELECT status, COUNT(*) AS count
FROM properties
GROUP BY status;
-- 4. Organisations
SELECT COUNT(*) AS total_orgs FROM organizations;
-- 5. Audit log continuity (last 30 days)
SELECT DATE(created_at) AS day, COUNT(*) AS entries
FROM audit_log
WHERE created_at > NOW() - INTERVAL '30 days'
GROUP BY DATE(created_at)
ORDER BY day DESC
LIMIT 30;
-- 6. HMRC credentials present
SELECT COUNT(*) AS total_connected_orgs
FROM hmrc_credentials;
Incident Response
Data loss checklist
- Identify scope: which tables, which organisations, time range affected
- Determine root cause before restoring (prevent re-corruption)
- Choose recovery method (PITR vs monthly archive)
- Restore to a test branch first — verify before promoting to primary
- Update
DATABASE_URLand redeploy if connection string changed - Notify affected customers within 72 hours (GDPR breach notification obligation)
- If HMRC-submitted data was affected: contact the HMRC Compliance Team
- Write post-incident review within 5 business days
- Update RPO/RTO baselines if actual recovery time exceeded targets
Escalation path
| Severity | Condition | Action |
|---|---|---|
| P1 — Critical | Production database unresponsive or data loss confirmed | Engineering on-call immediately; notify CTO within 30 minutes |
| P2 — High | Data anomaly detected by verification function | Engineering lead investigates within 2 hours |
| P3 — Medium | Backup verification warning (row count drift) | Engineering team investigates within 1 business day |
Compliance Mapping
| Requirement | How it's met |
|---|---|
| ISO 27001 A.17.1.2 — Implementing continuity | Neon PITR + monthly archive + documented RTO/RPO |
| ISO 27001 A.17.1.3 — Verify continuity | Weekly automated verification + quarterly manual test restore |
| GDPR Article 5(1)(e) — Storage limitation | 7-year retention for HMRC records; automated purge for operational data |
| GDPR Article 32 — Security of processing | Encryption at rest (Neon AES-256), encryption in transit (TLS 1.3), access control via Neon IAM |
| HMRC 7-year data retention | Monthly archive to S3/Glacier with 7-year lifecycle policy |