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:

ResourceRecovery point typeContinuous (PITR)Cold storage
DynamoDB tableOn-demand snapshotYes (enable separately via pointInTimeRecovery: true)Yes
EFS file systemIncremental file-level backupNoYes
RDS / Aurora clusterDB snapshot / cluster snapshotYes (automated backups + AWS Backup)No
EBS volumeEBS snapshotNoNo
EC2 instanceAMI + EBS snapshotsNoNo
S3 bucketContinuous or periodicYes (requires opt-in per bucket)No
DocumentDB clusterCluster snapshotYesNo
Neptune clusterCluster snapshotYesNo

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:

# 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

SymptomCauseFix
Backup job fails with IAMROLEREQUIRESPUTOBJECTPERMISSIONThe backup IAM role lacks S3 permissions for backup metadata exportAttach AWSBackupServiceRolePolicyForBackup and AWSBackupServiceRolePolicyForRestores to the backup role; for S3 backups, also attach AWSBackupServiceRolePolicyForS3Backup
DynamoDB table shows as NOT_APPLICABLE for cold storage lifecycleDynamoDB recovery points cannot be moved to cold storage in all regionsRemove 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 RUNNINGEFS backup started but the completion window is too short for a large file systemIncrease 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_EXPIREDRecovery point's retention period elapsed between listing and restore startUse the most recent recovery point and restore immediately; check vault retention rules are not shorter than expected
Tag-based assignment not picking up new tablesThe tag was added after the backup plan evaluated its resource selectionAWS Backup re-evaluates tag-based selections at each backup job run; the first backup after adding the tag will include the new resource