RDS Snapshot → S3: Diagnose and Fix a Disabled Export
Before diving into the why and how, here’s what we deliver:
⏱️ Clear diagnosis in under a minute (no console guesswork)
🫥 Non-intrusive path: temporary instance only (we don’t touch your current RDS)
🔐 Export that works, with the correct IAM/KMS shape
✅ Copy-paste commands for your runbook or CI
🔒 Why the export is disabled
The most frequent blocker is simple: the snapshot was taken from an instance on magnetic storage. Snapshots with that lineage aren’t eligible for export. The console is vague; the CLI tells you exactly what’s going on.⚖️ Console vs CLI: what actually helps
| ❌ Console only | ✅ CLI runbook | |
|---|---|---|
| Feedback | Greyed-out button, no reason why | Explicit status & fields (StorageType, errors) |
| Actions | Trial and error | Deterministic flow (restore → snapshot → export) |
| Repeatability | Manual steps | Scriptable & auditable |
🕵️♂️ Fast diagnosis (CLI)
Check the snapshot storage type:aws rds describe-db-snapshots \
--db-snapshot-identifier <SNAPSHOT_ID> \
--query 'DBSnapshots[0].[DBSnapshotIdentifier,Engine,EngineVersion,StorageType,Encrypted,Status]' \
--output table
🧭 Architecture at a glance
The flow is straightforward: restore snapshot → temporary gp3 instance → new snapshot → export to S3 with an export role trusted by export.rds.amazonaws.com.

This shows:
🔁 Where the storage lineage changes (magnetic → gp3)
🔗 How the export service assumes an IAM role to write to S3
🧰 The minimal moving parts (RDS, S3, IAM, optional KMS)
📘 The playbook (no changes to your existing RDS)
🧱 Restore the snapshot on gp3 (temporary instance)
# Vars
export REGION=<REGION>
export TEMP_DB_ID=<TEMP_DB_ID>
export SNAPSHOT_ID=<SNAPSHOT_ID>
export SUBNET_GROUP=<DB_SUBNET_GROUP>
export SG_ID=<SECURITY_GROUP_ID>
aws rds restore-db-instance-from-db-snapshot \
--region $REGION \
--db-instance-identifier $TEMP_DB_ID \
--db-snapshot-identifier $SNAPSHOT_ID \
--storage-type gp3 \
--no-publicly-accessible \
--db-subnet-group-name $SUBNET_GROUP \
--vpc-security-group-ids $SG_ID
📸 Create a new snapshot (now SSD-backed)
export NEW_SNAPSHOT_ID=${TEMP_DB_ID}-exportable-$(date +%Y%m%d)
aws rds create-db-snapshot \
--db-snapshot-identifier $NEW_SNAPSHOT_ID \
--db-instance-identifier $TEMP_DB_ID
# Sanity check
aws rds describe-db-snapshots \
--db-snapshot-identifier $NEW_SNAPSHOT_ID \
--query 'DBSnapshots[0].[DBSnapshotIdentifier,StorageType,Status]' \
--output table
🧷 Export to S3 (the wiring that matters)
✅ Requirements
🌍 S3 bucket in the same region as the snapshot
🤝 Export IAM role trusted by
export.rds.amazonaws.com📝 Role permissions to write to S3 (PutObject, ListBucket, GetBucketLocation, etc.)
🔑 If using KMS, key Enabled and allows the export service
🛑 No Organization SCP blocking
rds:StartExportTask
👇 Trust policy (assumed by the export service)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "export.rds.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
📜 Minimal permissions policy (S3 + KMS)
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject*",
"s3:AbortMultipartUpload",
"s3:ListBucket",
"s3:GetBucketLocation",
"s3:DeleteObject*"
],
"Resource": [
"arn:aws:s3:::<BUCKET>",
"arn:aws:s3:::<BUCKET>/*"
]
},
{
"Effect": "Allow",
"Action": [
"kms:CreateGrant",
"kms:DescribeKey",
"kms:Encrypt",
"kms:GenerateDataKey*"
],
"Resource": "<KMS_KEY_ARN>"
}
]
}
🧾 Bucket policy (explicit allow for the role)
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowRDSExportWrite",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<ACCOUNT_ID>:role/<RDS_EXPORT_ROLE>"
},
"Action": [
"s3:PutObject",
"s3:AbortMultipartUpload",
"s3:ListBucket",
"s3:GetBucketLocation"
],
"Resource": [
"arn:aws:s3:::<BUCKET>",
"arn:aws:s3:::<BUCKET>/*"
]
}
]
}
▶️ Start the export
aws rds start-export-task \
--export-task-identifier export-$NEW_SNAPSHOT_ID \
--source-arn arn:aws:rds:$REGION:<ACCOUNT_ID>:snapshot:$NEW_SNAPSHOT_ID \
--s3-bucket-name <BUCKET> \
--s3-prefix rds-exports/$NEW_SNAPSHOT_ID/ \
--iam-role-arn arn:aws:iam::<ACCOUNT_ID>:role/<RDS_EXPORT_ROLE> \
--kms-key-id <KMS_KEY_ARN>
👀 Observe the run
aws rds describe-export-tasks \
--filters Name=export-task-identifier,Values=export-$NEW_SNAPSHOT_ID \
--query 'ExportTasks[0].[Status,ProgressPercentage,S3Bucket,S3Prefix,FailureCause]' \
--output table
🧯 Troubleshooting (quick triage)
- 🔎 AccessDenied → check Organization SCPs, caller permissions, and the export role policy
- 🧱 KMSKeyNotAccessible → key disabled or a deny affecting
export.rds.amazonaws.com - 🗺️ BucketRegionError → snapshot and bucket must be in the same region
- 🤝 Shared snapshot → align KMS access/grants cross-account
💡 Lessons Learned
- Don’t rely on the console to explain the “why” — the CLI is your source of truth
- Prevent this upfront: standardize on
gp3in modules and blockstandardstorage in CI - Keep a pre-approved export role per account/region to avoid one-off IAM changes
🙋♂️ Need Help With This?
Need help maintaining your RDS setup - keeping backups restorable, snapshot governance tight, etc. ?
We’ve implemented these patterns across multiple AWS accounts and regions — repeatable, auditable, and low‑touch.
💬 Let’s chat on LinkedIn — happy to help or exchange ideas.
