All Docs
FeaturesMaking Tax DigitalUpdated March 7, 2026

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

MetricTargetBasis
RPO (Recovery Point Objective)< 1 hourNeon WAL archive is near-continuous; practical RPO is minutes
RTO (Recovery Time Objective)< 4 hoursStep-by-step recovery procedures documented below
Data Retention7 yearsHMRC 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:

  1. Open the Neon Console
  2. Select the production project
  3. Go to Settings → Backups
  4. 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)

  1. Log in to Neon Console
  2. Navigate to Branches → main → Restore
  3. Select a timestamp before the data loss event
  4. Neon creates a new branch at that point in time
  5. Run the verification queries to confirm data integrity
  6. Promote the restored branch to primary (Branches → [restored branch] → Set as Primary)
  7. Update DATABASE_URL in your deployment environment if the connection string changed, then redeploy
  8. Notify affected customers if HMRC submission data was lost

Expected RTO: 1–2 hours

Scenario 2 — Catastrophic Loss (database unrecoverable)

  1. Create a new Neon project (or branch) in the same region
  2. 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 .dump from S3 and restore:
      pg_restore -d $NEW_DATABASE_URL --no-owner --no-privileges backup-YYYY-MM.dump
      
  3. Apply any schema migrations that postdate the archive:
    npm run db:push
    
  4. Verify row counts with the verification queries
  5. Update DATABASE_URL and redeploy
  6. 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

  1. Immediately isolate the environment — revoke all database credentials
  2. Identify the last known-good timestamp from application logs / Sentry
  3. Follow Scenario 1 or 2 depending on the age of the corruption
  4. Rotate all secrets: DATABASE_URL, NEXTAUTH_SECRET, encryption keys
  5. 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

CheckWhat it validates
organisation_countAt least one org record exists (DB not wiped)
transaction_countFinancial records present with most-recent transaction date
hmrc_submission_countQuarterly summary records intact, broken out by status
property_countProperty records present
audit_log_continuityAudit log has entries within the last 72 hours
neon_branch_restoreCreates 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

VariableRequiredPurpose
NEON_API_KEYOptionalNeon Management API key — enables the branch test-restore check
NEON_PROJECT_IDOptional (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:

  1. In Neon Console, create a branch from a timestamp 7 days ago
  2. Run the verification queries below against the restored branch
  3. Confirm row counts match expected values
  4. Delete the test branch
  5. 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_URL and 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

SeverityConditionAction
P1 — CriticalProduction database unresponsive or data loss confirmedEngineering on-call immediately; notify CTO within 30 minutes
P2 — HighData anomaly detected by verification functionEngineering lead investigates within 2 hours
P3 — MediumBackup verification warning (row count drift)Engineering team investigates within 1 business day

Compliance Mapping

RequirementHow it's met
ISO 27001 A.17.1.2 — Implementing continuityNeon PITR + monthly archive + documented RTO/RPO
ISO 27001 A.17.1.3 — Verify continuityWeekly automated verification + quarterly manual test restore
GDPR Article 5(1)(e) — Storage limitation7-year retention for HMRC records; automated purge for operational data
GDPR Article 32 — Security of processingEncryption at rest (Neon AES-256), encryption in transit (TLS 1.3), access control via Neon IAM
HMRC 7-year data retentionMonthly archive to S3/Glacier with 7-year lifecycle policy