Guide · AWS Backup · DynamoDB · EFS · RDS · Data Protection
AWS Backup for MCP Servers — Protecting DynamoDB, EFS, and RDS Data
An MCP server that goes down temporarily is annoying. An MCP server whose data is gone permanently is a catastrophe. AWS Backup gives you a single control plane to schedule, retain, and restore backups for every AWS resource your MCP server depends on — DynamoDB tables storing tool schemas and session state, EFS volumes holding shared artifacts, RDS databases used by tool implementations, and EBS volumes attached to your backend EC2 instances. This guide covers the core building blocks: backup vaults (encrypted containers for recovery points), backup plans (schedules and retention rules), resource assignments (tag-based or explicit ARN selection), and recovery point lifecycle (move-to-cold, delete-after). It also covers vault lock for WORM compliance and monitoring backup jobs via CloudWatch and EventBridge.
TL;DR
Create one backup vault per environment (dev/prod). Create a backup plan with two rules: a daily rule (retention 35 days, move-to-cold after 7) and a weekly rule (retention 365 days). Assign resources by tag Backup=true so any new DynamoDB table, EFS file system, or RDS cluster with that tag is automatically protected. Enable continuous backup (PITR) on DynamoDB and RDS separately — AWS Backup's scheduled rules do not replace point-in-time recovery. Enable vault lock in governance mode during setup and transition to compliance mode (WORM) before your first audit.
Core concepts
Backup vault
A backup vault is an encrypted container for recovery points. Each vault has a KMS key, an access policy, and optional vault lock settings. Recovery points (backups) can only be deleted by deleting from the vault — not by deleting the source resource. Create separate vaults for dev and prod so a runaway delete in dev can't touch prod backups. The default vault created by AWS uses the AWS-managed key aws/backup; prefer a customer-managed key for auditable key rotation.
import * as backup from 'aws-cdk-lib/aws-backup';
import * as kms from 'aws-cdk-lib/aws-kms';
const backupKey = new kms.Key(this, 'BackupKey', {
enableKeyRotation: true,
description: 'AliveMCP production backup vault key',
});
const prodVault = new backup.BackupVault(this, 'ProdVault', {
backupVaultName: 'alivemcp-prod',
encryptionKey: backupKey,
// After enableDate passes, recovery points cannot be deleted
// before minRetentionDays even by the root account user
lockConfiguration: {
minRetentionDays: cdk.Duration.days(7),
// Leave maxRetentionDays undefined to allow longer-lived rules
},
notificationTopic: backupAlertTopic, // SNS topic for job state changes
});
Backup plan and rules
A backup plan is a named set of rules. Each rule defines: a schedule (cron or rate expression), the target vault, a completion window (how long AWS Backup waits before marking the job failed), a lifecycle (move-to-cold-storage after N days, delete after M days), and optionally a copy action (send the recovery point to a second vault, possibly in a different region or account).
const plan = new backup.BackupPlan(this, 'McpBackupPlan', {
backupPlanName: 'alivemcp-prod-plan',
backupVault: prodVault,
backupPlanRules: [
// Daily backup — keep 35 days hot, delete at 35 days
new backup.BackupPlanRule({
ruleName: 'daily-35d',
scheduleExpression: events.Schedule.cron({ hour: '03', minute: '00' }),
startWindow: cdk.Duration.hours(1),
completionWindow: cdk.Duration.hours(4),
deleteAfter: cdk.Duration.days(35),
moveToColdStorageAfter: cdk.Duration.days(7),
}),
// Weekly backup — keep 1 year, move cold at 30 days
new backup.BackupPlanRule({
ruleName: 'weekly-365d',
scheduleExpression: events.Schedule.cron({ weekDay: 'SUN', hour: '04', minute: '00' }),
startWindow: cdk.Duration.hours(2),
completionWindow: cdk.Duration.hours(8),
deleteAfter: cdk.Duration.days(365),
moveToColdStorageAfter: cdk.Duration.days(30),
}),
],
});
// Add a cross-region copy action to the daily rule via CloudFormation escape hatch
// (CDK L2 construct for copy actions requires the BackupPlanRule to be created via L1)
const cfnPlan = plan.node.defaultChild as backup.CfnBackupPlan;
cfnPlan.addPropertyOverride('BackupPlan.BackupPlanRule.0.CopyActions', [{
DestinationBackupVaultArn: `arn:aws:backup:us-west-2:${this.account}:backup-vault:alivemcp-prod-dr`,
Lifecycle: { DeleteAfterDays: 35 },
}]);
Resource assignment
AWS Backup supports two assignment methods: explicit ARN list (fragile, requires updating on every new resource) and tag-based selection (preferred). Tag every resource you want protected at creation time and assign the plan to that tag selector. The IAM role used by AWS Backup must have permissions to call the backup APIs for each service type — the managed policy AWSBackupServiceRolePolicyForBackup covers all supported resource types.
const assignment = plan.addSelection('TaggedResources', {
backupSelectionName: 'backup-true-tag',
role: iam.Role.fromRoleArn(this, 'BackupRole',
`arn:aws:iam::${this.account}:role/AWSBackupDefaultServiceRole`
),
resources: [
// Tag-based: everything with Backup=true
backup.BackupResource.fromTag('Backup', 'true'),
],
});
// Tag your DynamoDB table at creation
new dynamodb.Table(this, 'SessionTable', {
tableName: 'alivemcp-sessions',
// ... table config ...
}).applyRemovalPolicy(cdk.RemovalPolicy.RETAIN);
// Add via Tags construct at stack level
cdk.Tags.of(this).add('Backup', 'true');
Supported resource types
AWS Backup supports backup for the following resource types relevant to MCP servers:
| Resource | Recovery point type | Continuous (PITR) | Cold storage |
|---|---|---|---|
| DynamoDB table | On-demand snapshot | Yes (enable separately via pointInTimeRecovery: true) | Yes |
| EFS file system | Incremental file-level backup | No | Yes |
| RDS / Aurora cluster | DB snapshot / cluster snapshot | Yes (automated backups + AWS Backup) | No |
| EBS volume | EBS snapshot | No | No |
| EC2 instance | AMI + EBS snapshots | No | No |
| S3 bucket | Continuous or periodic | Yes (requires opt-in per bucket) | No |
| DocumentDB cluster | Cluster snapshot | Yes | No |
| Neptune cluster | Cluster snapshot | Yes | No |
DynamoDB PITR and EFS are the most critical for most MCP servers. PITR lets you restore a DynamoDB table to any second in the last 35 days — far more granular than a daily snapshot for accidental write operations. EFS incremental backup means only changed files are backed up after the initial full backup, so the backup window is much shorter than a nightly rsync.
Vault lock for WORM compliance
AWS Backup Vault Lock prevents recovery points from being deleted or their retention changed, even by the root account user. It has two modes:
- Governance mode: IAM users with the
backup:DeleteBackupVaultLockConfigurationpermission can remove the lock. Use this during initial setup to validate your retention settings. - Compliance mode: Once the lock cool-off period (1–3 days you specify) expires, the lock cannot be removed by any entity including AWS Support. Use this before your first SOC 2 or HIPAA audit.
# Enable vault lock via CLI (CDK L2 supports this via lockConfiguration)
aws backup put-backup-vault-lock-configuration \
--backup-vault-name alivemcp-prod \
--min-retention-days 7 \
--max-retention-days 365 \
--changeable-for-days 3 # cool-off: 3 days in governance mode before compliance mode locks
The changeable-for-days parameter controls the governance-to-compliance transition window. After this many days, the lock becomes permanent. Never set changeable-for-days=0 in production until you have validated that the min/max retention values are correct — a misconfigured permanent lock can prevent you from cleaning up accidental large backups.
DynamoDB-specific backup considerations
AWS Backup's scheduled backup of a DynamoDB table creates an on-demand snapshot — not a PITR restore. For fine-grained recovery, enable PITR independently on every table:
new dynamodb.Table(this, 'SessionTable', {
tableName: 'alivemcp-sessions',
partitionKey: { name: 'sessionId', type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
pointInTimeRecovery: true, // enables PITR (continuous backup)
encryption: dynamodb.TableEncryption.CUSTOMER_MANAGED,
encryptionKey: backupKey,
removalPolicy: cdk.RemovalPolicy.RETAIN,
});
PITR retains data for exactly 35 days. You cannot extend PITR retention — if you need backups beyond 35 days, use AWS Backup's scheduled snapshots in addition to PITR. AWS Backup scheduled snapshots of a PITR-enabled table capture a consistent on-demand snapshot at that moment; they do not interact with PITR's rolling window.
To restore a DynamoDB table to a specific point in time:
aws dynamodb restore-table-to-point-in-time \
--source-table-name alivemcp-sessions \
--target-table-name alivemcp-sessions-restored-2026-11-08 \
--restore-date-time 2026-11-08T02:00:00Z \
--billing-mode-override PAY_PER_REQUEST
The restore creates a new table — it never overwrites the source. After validating the restored table, update your MCP server configuration or use UpdateTable to swap names (requires renaming both tables in sequence since DynamoDB table names are unique per account/region).
Monitoring backup jobs
AWS Backup emits job state change events to EventBridge from the aws.backup source. Subscribe to these events to alert when backup jobs fail or are skipped.
import * as events from 'aws-cdk-lib/aws-events';
import * as targets from 'aws-cdk-lib/aws-events-targets';
// Alert on any backup job failure or partial success
new events.Rule(this, 'BackupJobFailureRule', {
ruleName: 'alivemcp-backup-job-failure',
eventPattern: {
source: ['aws.backup'],
detailType: ['Backup Job State Change'],
detail: {
state: ['FAILED', 'PARTIAL', 'EXPIRED', 'ABORTED'],
},
},
targets: [
new targets.SnsTopic(backupAlertTopic, {
message: events.RuleTargetInput.fromObject({
message: 'AWS Backup job failed',
jobId: events.EventField.fromPath('$.detail.backupJobId'),
resourceArn: events.EventField.fromPath('$.detail.resourceArn'),
state: events.EventField.fromPath('$.detail.state'),
statusMessage: events.EventField.fromPath('$.detail.statusMessage'),
}),
}),
],
});
// CloudWatch metric filter for backup job failures
// (AWS Backup also publishes metrics to aws/backup namespace)
const backupJobFailureAlarm = new cloudwatch.Alarm(this, 'BackupJobFailureAlarm', {
alarmName: 'alivemcp-backup-job-failure',
metric: new cloudwatch.Metric({
namespace: 'AWS/Backup',
metricName: 'NumberOfBackupJobsFailed',
dimensionsMap: { BackupVaultName: 'alivemcp-prod' },
period: cdk.Duration.hours(24),
statistic: 'Sum',
}),
threshold: 0,
evaluationPeriods: 1,
comparisonOperator: cloudwatch.ComparisonOperator.GREATER_THAN_THRESHOLD,
treatMissingData: cloudwatch.TreatMissingData.NOT_BREACHING,
});
Restoring resources
To restore from a recovery point in the AWS console: Backup → Protected resources → select resource → Recovery points → select point → Restore. For CLI-driven restores in incident response:
# List recovery points for a DynamoDB table
aws backup list-recovery-points-by-resource \
--resource-arn arn:aws:dynamodb:us-east-1:123456789012:table/alivemcp-sessions \
--query 'RecoveryPoints[?Status==`COMPLETED`]|sort_by(@,&CreationDate)[-1]'
# Start a restore job
aws backup start-restore-job \
--recovery-point-arn arn:aws:backup:us-east-1:123456789012:recovery-point:abc123 \
--iam-role-arn arn:aws:iam::123456789012:role/AWSBackupDefaultServiceRole \
--resource-type DynamoDB \
--metadata '{
"targetTableName": "alivemcp-sessions-restored",
"restoreTime": "latest"
}'
# Poll restore job status
aws backup describe-restore-job --restore-job-id JOB_ID
Restore jobs are asynchronous. DynamoDB table restores typically take 10–30 minutes depending on table size. EFS restores can take several hours for large file systems. Monitor restore job status via EventBridge (Restore Job State Change events) rather than polling.
Common failures
| Symptom | Cause | Fix |
|---|---|---|
Backup job fails with IAMROLEREQUIRESPUTOBJECTPERMISSION | The backup IAM role lacks S3 permissions for backup metadata export | Attach AWSBackupServiceRolePolicyForBackup and AWSBackupServiceRolePolicyForRestores to the backup role; for S3 backups, also attach AWSBackupServiceRolePolicyForS3Backup |
DynamoDB table shows as NOT_APPLICABLE for cold storage lifecycle | DynamoDB recovery points cannot be moved to cold storage in all regions | Remove the moveToColdStorageAfter lifecycle from the DynamoDB resource rule; cold storage is supported for EFS, EBS, and EC2, not all resource types |
Backup job starts but never completes — stuck in RUNNING | EFS backup started but the completion window is too short for a large file system | Increase completionWindow to 24h for large EFS volumes; AWS Backup aborts the job after the completion window, leaving an incomplete recovery point |
Recovery point exists but restore fails with RECOVERY_POINT_EXPIRED | Recovery point's retention period elapsed between listing and restore start | Use the most recent recovery point and restore immediately; check vault retention rules are not shorter than expected |
| Tag-based assignment not picking up new tables | The tag was added after the backup plan evaluated its resource selection | AWS Backup re-evaluates tag-based selections at each backup job run; the first backup after adding the tag will include the new resource |