Reduce Amazon Aurora Snapshot Backup Costs for Long-Term Retention Using AWS Backup and Amazon S3.
Amazon Aurora automatically backs up cluster volumes and retains restore data for up to 35 days at no additional cost. Snapshots taken within this window whether automated or through AWS Backup are free. However, many organizations must retain backups well beyond 35 days to meet compliance, regulatory, or business continuity requirements. The purpose of this article is to present a practical, cost-effective strategy for long-term Aurora backup retention by combining AWS Backup with Amazon S3.
Reducing Amazon Aurora Backup Costs for Long Retention Periods
Amazon Aurora backs up cluster volumes automatically and retains restore data for the length of the backup retention period. Aurora automated backups are continuous and incremental, so you can restore to any point within the backup retention period from 1–35 days. Aurora also provides a free amount of backup usage equal to the latest cluster volume size. Snapshots taken within the automated backup retention period — whether manual or through AWS Backup — are free.
However, if you need to retain backups for more than 35 days for compliance or business continuity, costs can escalate quickly. AWS Backup snapshots of Aurora are always full backups, and each snapshot retained beyond the 35-day window incurs charges at the full GB-month rate. For a 500 GiB database with daily snapshots retained for 3 years, this can exceed $10,000 per month.
In this post, we discuss how to reduce Aurora backup costs for long retention periods by combining AWS Backup with Amazon S3 snapshot exports. This approach keeps short-term backups free within Aurora's retention window and offloads long-term retention to Amazon S3, where lifecycle policies can transition data to cost-effective storage classes like Amazon S3 Glacier.
Solution Overview
This solution uses a two-tier backup strategy: AWS Backup manages short-term Aurora snapshots within the free 35-day retention window, while an automated pipeline exports snapshots to Amazon S3 in compressed Apache Parquet format for long-term archival. Amazon EventBridge detects completed backup jobs and triggers an AWS Lambda function to initiate the S3 export automatically.
The following diagram illustrates the architecture for this solution:
Note: AWS KMS is a critical component in this architecture. Both the Lambda execution role and the IAM export role require KMS permissions. The KMS key encrypts the snapshot data exported to S3. Without proper KMS grants, the export will fail with
KMSKeyNotAccessibleFaultorIamRoleMissingPermissionserrors.
The workflow includes the following steps:
- AWS Backup runs a daily backup plan and creates a full Aurora snapshot.
- The snapshot is retained for up to 35 days at no additional cost (within Aurora's free backup allowance).
- Amazon EventBridge detects the completed backup job and triggers a Lambda function.
- The Lambda function calls
start-export-taskto export the snapshot to Amazon S3 in Apache Parquet format. AWS KMS encrypts the exported data — both the Lambda role and the export role must have KMS permissions. - An S3 Lifecycle policy transitions the exported data through storage tiers (Standard → Standard-IA → Glacier → Deep Archive) and expires it after the desired retention period.
Prerequisites
To implement this solution, you need the following:
- An Amazon Aurora DB cluster
- An Amazon S3 bucket in the same AWS Region as the Aurora cluster
- An AWS KMS symmetric encryption key for the S3 export
- An IAM role granting the export task access to the S3 bucket
- AWS Backup configured with access to your Aurora cluster
Step 1: Configure Aurora Backup Retention to 35 Days
Maximize the free backup tier by setting your Aurora cluster's backup retention period to the maximum of 35 days:
aws rds modify-db-cluster \ --db-cluster-identifier my-aurora-cluster \ --backup-retention-period 35 \ --apply-immediately
Warning: The
--apply-immediatelyflag can cause an unexpected reboot of the cluster if there are other pending modifications that require a reboot. If you're running this against a production cluster, consider omitting the flag to apply the change during the next maintenance window instead, or verify there are no pending changes first withaws rds describe-db-clusters --db-cluster-identifier my-aurora-cluster --query 'DBClusters[0].PendingModifiedValues'.
This ensures that all snapshots — whether taken by Aurora automatically or by AWS Backup — are free for up to 35 days.
Step 2: Create an AWS Backup Plan with 35-Day Retention
Create a backup plan that takes daily snapshots and deletes them after 35 days. Because this aligns with Aurora's automated backup retention period, these snapshots incur zero additional backup storage charges.
Save the following as backup-plan.json:
{ "BackupPlanName": "aurora-short-term-backup", "Rules": [ { "RuleName": "daily-aurora-snapshot", "TargetBackupVaultName": "Default", "ScheduleExpression": "cron(0 3 * * ? *)", "StartWindowMinutes": 60, "CompletionWindowMinutes": 180, "Lifecycle": { "DeleteAfterDays": 35 } } ] }
Create the backup plan:
aws backup create-backup-plan --backup-plan file://backup-plan.json
Then assign your Aurora cluster to the backup plan using a resource assignment.
Step 3: Set Up the IAM Role for Snapshot Export
Create an IAM role that grants the RDS export task permission to write to your S3 bucket and use the KMS key.
Trust policy (trust-policy.json):
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "export.rds.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
Permission policy (export-permissions.json):
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:DeleteObject", "s3:GetBucketLocation", "s3:ListBucket", "s3:AbortMultipartUpload", "s3:ListMultipartUploaParts" ], "Resource": [ "arn:aws:s3:::my-aurora-exports-bucket", "arn:aws:s3:::my-aurora-exports-bucket/*" ] }, { "Effect": "Allow", "Action": [ "kms:Encrypt", "kms:Decrypt", "kms:GenerateDataKey", "kms:GenerateDataKeyWithoutPlaintext", "kms:ReEncryptFrom", "kms:ReEncryptTo", "kms:DescribeKey", "kms:CreateGrant", "kms:RetireGrant" ], "Resource": "arn:aws:kms:<region>:<account-id>:key/<your-kms-key-id>" } ] }
Important: The export task requires
s3:GetObjectands3:DeleteObjectin addition tos3:PutObject. Without these, theStartExportTaskAPI call will fail with anIamRoleMissingPermissionserror.
Create the role:
aws iam create-role \ --role-name AuroraSnapshotExportRole \ --assume-role-policy-document file://trust-policy.json aws iam put-role-policy \ --role-name AuroraSnapshotExportRole \ --policy-name AuroraExportToS3 \ --policy-document file://export-permissions.json
Additionally, create a KMS grant for the export role to ensure the RDS export service can use the key:
aws kms create-grant \ --key-id <your-kms-key-id> \ --grantee-principal arn:aws:iam::<account-id>:role/AuroraSnapshotExportRole \ --operations Encrypt Decrypt GenerateDataKey \ GenerateDataKeyWithoutPlaintext \ ReEncryptFrom ReEncryptTo CreateGrant DescribeKey RetireGrant
Step 4: Automate Snapshot Exports with EventBridge and Lambda
Create an EventBridge rule that detects completed AWS Backup jobs for Aurora and triggers a Lambda function to start the S3 export.
EventBridge rule pattern:
{ "source": ["aws.backup"], "detail-type": ["Backup Job State Change"], "detail": { "state": ["COMPLETED"], "resourceType": ["Aurora"] } }
The Lambda function extracts the cluster identifier from the event, finds the latest available snapshot, and calls start-export-task. Environment variables are used for configuration so the function can be reused across clusters:
import boto3 import datetime import os rds = boto3.client("rds") def handler(event, context): detail = event.get("detail", {}) resource_arn = detail.get("resourceArn", "") state = detail.get("state", "") resource_type = detail.get("resourceType", "") print(f"Event received: type={resource_type}, state={state}, arn={resource_arn}") if state != "COMPLETED": return # Get the recovery point ARN which is the snapshot ARN backup_job_id = detail.get("backupJobId", "") # Find the snapshot created by this backup job cluster_id = resource_arn.split(":")[-1] snapshots = rds.describe_db_cluster_snapshots( DBClusterIdentifier=cluster_id, SnapshotType="awsbackup", )["DBClusterSnapshots"] if not snapshots: print(f"No snapshots found for {cluster_id}") return # Get the latest available snapshot available = [s for s in snapshots if s["Status"] == "available"] if not available: print("No available snapshots") return snapshot = sorted(available, key=lambda s: s["SnapshotCreateTime"])[-1] snapshot_arn = snapshot["DBClusterSnapshotArn"] timestamp = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") print(f"Exporting snapshot: {snapshot_arn}") try: rds.start_export_task( ExportTaskIdentifier=f"aurora-export-{timestamp}", SourceArn=snapshot_arn, S3BucketName=os.environ["S3_BUCKET"], S3Prefix="aurora-exports/", IamRoleArn=os.environ["EXPORT_ROLE_ARN"], KmsKeyId=os.environ["KMS_KEY_ARN"], ) print(f"Export started: aurora-export-{timestamp}") except rds.exceptions.ClientError as e: if "already being exported" in str(e) or "ExportTaskAlreadyExists" in str(e): print(f"Export already exists for this snapshot, skipping") else: raise
Important: The Lambda execution role must also have KMS permissions (
kms:Encrypt,kms:Decrypt,kms:GenerateDataKey,kms:DescribeKey,kms:CreateGrant) on the KMS key. Without these, theStartExportTaskcall will fail with aKMSKeyNotAccessibleFaulterror, even though the export IAM role has the correct permissions. This is because the calling principal (Lambda) must also be authorized to use the KMS key.
Step 5: Apply S3 Lifecycle Policies for Tiered Storage
Configure an S3 Lifecycle policy to automatically transition exported data to cheaper storage classes and expire it after your desired retention period:
aws s3api put-bucket-lifecycle-configuration \ --bucket my-aurora-exports-bucket \ --lifecycle-configuration '{ "Rules": [ { "ID": "aurora-export-lifecycle", "Status": "Enabled", "Filter": { "Prefix": "aurora-exports/" }, "Transitions": [ { "Days": 30, "StorageClass": "STANDARD_IA" }, { "Days": 90, "StorageClass": "GLACIER" }, { "Days": 365, "StorageClass": "DEEP_ARCHIVE" } ], "Expiration": { "Days": 1825 } } ] }'
Step 6: Restore Data from S3 When Needed
To access exported data archived in S3 Glacier storage classes, use the restore-object command. The following example restores an object for 25 days:
aws s3api restore-object \ --bucket my-aurora-exports-bucket \ --key aurora-exports/example-file.parquet \ --restore-request '{"Days":25,"GlacierJobParameters":{"Tier":"Standard"}}'
To monitor the restore status:
aws s3api head-object \ --bucket my-aurora-exports-bucket \ --key aurora-exports/example-file.parquet
Because the exported data is in Parquet format, you can also query it directly using Amazon Athena or Amazon Redshift Spectrum without restoring a full Aurora cluster.
Conclusion
In this post, we explored how to reduce the cost of long-term Aurora backup retention by combining AWS Backup with Amazon S3 snapshot exports. This approach provides the best of both worlds: the operational convenience of AWS Backup for recent recovery points (free within the 35-day retention window), and the cost efficiency of Amazon S3 tiered storage for compliance and archival needs.
Key benefits of this approach:
- Zero cost for short-term backups — Aurora snapshots within the 35-day retention period are free
- Up to 99% cost reduction for long-term retention — S3 Glacier Deep Archive costs a fraction of Aurora snapshot storage
- Queryable archives — exported Parquet data can be queried directly with Amazon Athena or Redshift Spectrum without restoring a full cluster
- Fully automated — EventBridge and Lambda handle the export pipeline with no manual intervention
Test the procedure outlined in this post and share your feedback in the comments section.
References
Relevant content
- asked 10 months ago
- Accepted Answerasked 3 months ago
AWS OFFICIALUpdated 4 months ago