This plan defines how EMStool LLC will maintain or restore Cadence operations in the event of a major system failure, data loss event, or prolonged outage. It establishes recovery objectives, procedures, and responsibilities.
1. Recovery Objectives
| Metric | Target | Notes |
| Recovery Time Objective (RTO) | 4 hours | Maximum time to restore service from outage declaration |
| Recovery Point Objective (RPO) | 24 hours | Maximum acceptable data loss; daily backups provide this guarantee |
| Maximum Tolerable Downtime (MTD) | 48 hours | Beyond this, customer SLA breach and HIPAA notification required |
2. System Architecture Overview
Each client runs Cadence on its own dedicated hardware — an EMStool-provided appliance or the client's own server, in the client's own facility — fully isolated from every other client. There is no shared multi-tenant database.
- Application Server: per-client hardware — Unraid or equivalent, SWAG reverse proxy, PHP/Docker
- Database: one dedicated MariaDB container per client deployment
- Tunnel: Cloudflare Zero Trust tunnel — routes external traffic to that client's hardware, wherever it is physically plugged in
- Backups: encrypted .sql.gpg; destination is the client's own choice (Backblaze B2, local NAS/rsync, etc.)
- Code: GitHub repo mrjacobarussell/mih-scheduler, deployed via deploy.sh
EMStool also operates its own non-client host (Bender, 192.168.0.13) for software development and a public demo/trial instance (scheduler.emstool.com, seed/dummy data only). The scenarios below apply identically to any Cadence deployment, client or EMStool's own.
3. Backup Procedures
Database Backups
- Daily automated mysqldump of Cadence DB
- Encrypted with GPG before upload (key stored offline)
- Uploaded to Backblaze B2 bucket (cadence-backups)
- Retention: 30 days of daily backups
- Backup verification: monthly restore test to confirm backup integrity
Code Backups
- All code maintained in GitHub with full git history
- Production state is always deployable from latest main branch
4. Disaster Scenarios and Response Procedures
Scenario A: Database Corruption or Loss
- Stop SWAG container to prevent further writes
- Download latest encrypted backup from Backblaze B2
- Decrypt:
gpg --decrypt backup.sql.gpg | mysql -u root -p cadence_db
- Start MariaDB container and verify data integrity
- Run any pending migrations:
./deploy.sh migrate
- Restart SWAG container; verify application operational
- Notify affected customers of data recovery and any data loss window
Scenario B: Server Hardware Failure
Applies to Bender (EMStool's own dev/demo host) or any single client's own hardware — each deployment is independent, so a hardware failure on one never affects another.
- Provision a replacement Unraid server or Docker host
- Install SWAG and the MariaDB container with same configuration
- Restore the database from the configured backup (Backblaze B2, local NAS, or whatever destination that deployment uses — see Scenario A)
- Clone code from GitHub and run deploy.sh
- Update Cloudflare tunnel connector to point to new server
- Verify DNS and SSL function correctly
Scenario C: Cloudflare Tunnel Failure
- SSH to Bender (192.168.0.13) and check cloudflared container status
- Restart tunnel container:
docker restart Unraid-Cloudflared-Cadence
- If tunnel token expired, regenerate at Cloudflare dashboard and update container env
- If tunnel service is globally degraded, monitor Cloudflare status page and wait
Scenario D: Security Incident (Breach/Ransomware)
- Immediately disconnect affected systems from network
- Follow Incident Response Runbook (IRP-2026-01)
- Restore from last known-clean backup
- Rebuild compromised containers from scratch
- Rotate all credentials before bringing systems back online
5. Communication During Outage
| Stakeholder | When | How |
| Cadence customers | Outage > 30 minutes | Email to account admins |
| Internal | Immediately on detection | Direct communication |
| Customers (data loss) | If RPO exceeded | Formal written notification with scope of loss |
6. Recovery Test Schedule
- Backup restore test: Monthly — restore a backup to a test DB and verify table counts
- Full DR drill: Annually — simulate a complete server failure and perform full recovery
- Tunnel failover test: Quarterly — restart tunnel container and confirm recovery within 5 minutes
7. Restore Test Log
| Date | Instance | Method | Result |
| July 16, 2026 |
scheduler.emstool.com |
Decrypted the day's live encrypted backup (GPG), restored into a fresh, isolated, throwaway MariaDB container — not the production database or any existing container — then dropped the container entirely. Encryption-at-rest clause stripped from the dump for this data-integrity test only; the encryption keyfile itself is backed up and recoverable separately (see Risk Analysis, Asset 5). |
Pass. 77/77 tables restored (matches backup manifest); spot-checked row counts against the live database at time of test — patients 262/262, sched_users 22/22, appointments 2349/2349. Full data integrity confirmed. |
Security Officer Signature
Jacob Russell — Security Officer, EMStool LLC
Date Reviewed
June 29, 2026