A
Arun's Blog
← Back to all posts

S3 Cross-Account Replication: A Complete Step-by-Step Guide

AWSS3StorageDisaster RecoveryMigration
TL;DR

Replicate S3 data between AWS accounts by: (1) enabling versioning on both buckets, (2) creating an IAM role in the source account with read/write permissions, (3) adding a bucket policy on the destination to trust that role, and (4) creating a replication rule. New objects replicate automatically; use S3 Batch Replication for existing objects.

Introduction

Need to replicate S3 data between AWS accounts? Whether you're setting up a disaster recovery solution, centralizing logs from multiple accounts, or meeting compliance requirements for data redundancy, S3 Cross-Account Replication is the answer.

Cross-account replication can seem complex at first - you're dealing with IAM roles, bucket policies, and trust relationships spanning two separate AWS accounts. But once you understand the flow, it's straightforward to implement and incredibly reliable.

In this guide, I'll walk you through the complete setup process: enabling versioning, creating the IAM role and policies in the source account, configuring the destination bucket policy, setting up the replication rule, and testing everything works.

Architecture Overview

S3 Cross-Account Replication Architecture Diagram

The key components are:

  • Source Bucket (Account A) - Where your original objects live, must have versioning enabled
  • Destination Bucket (Account B) - Where replicated objects will be stored, must have versioning enabled
  • IAM Role (Account A) - Allows S3 to perform replication on your behalf
  • Bucket Policy (Account B) - Grants the source account's role permission to write to the destination

Prerequisites

Before you begin, ensure you have:

  • AWS CLI installed and configured with appropriate credentials
  • Access to both AWS accounts (source and destination)
  • Permissions to create IAM roles and modify S3 bucket policies
  • Both buckets already created (we'll enable versioning as part of setup)

Step 1: Enable Versioning on Both Buckets

Important

S3 Replication requires versioning to be enabled on both source and destination buckets. This is a hard requirement - replication simply won't work without it, and you'll get a cryptic error when trying to create the replication rule.

Enable Versioning on Source Bucket (Account A)

aws s3api put-bucket-versioning \
  --bucket YOUR-SOURCE-BUCKET-NAME \
  --versioning-configuration Status=Enabled \
  --profile source-account-profile

Enable Versioning on Destination Bucket (Account B)

aws s3api put-bucket-versioning \
  --bucket YOUR-DESTINATION-BUCKET-NAME \
  --versioning-configuration Status=Enabled \
  --profile destination-account-profile

Verify Versioning Status

# Check source bucket
aws s3api get-bucket-versioning \
  --bucket YOUR-SOURCE-BUCKET-NAME \
  --profile source-account-profile

# Check destination bucket
aws s3api get-bucket-versioning \
  --bucket YOUR-DESTINATION-BUCKET-NAME \
  --profile destination-account-profile

Expected output:

{
    "Status": "Enabled"
}

Step 2: Create IAM Role for Replication (Account A)

This IAM role allows S3 to perform replication on your behalf. It needs a trust policy that allows the S3 service to assume it.

Create the Trust Policy

Create a file named trust-policy.json:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "s3.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Create the IAM Role

aws iam create-role \
  --role-name S3CrossAccountReplicationRole \
  --assume-role-policy-document file://trust-policy.json \
  --description "Role for S3 cross-account replication" \
  --profile source-account-profile
Pro Tip

Save the Role ARN from the output immediately - you'll need it for both the replication configuration and the destination bucket policy. It looks like: arn:aws:iam::111122223333:role/S3CrossAccountReplicationRole

Step 3: Create and Attach IAM Permissions Policy (Account A)

The replication role needs permissions to read from the source bucket and write to the destination bucket.

Create the Permissions Policy

Create a file named replication-permissions-policy.json:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "SourceBucketPermissions",
      "Effect": "Allow",
      "Action": [
        "s3:GetReplicationConfiguration",
        "s3:ListBucket"
      ],
      "Resource": "arn:aws:s3:::YOUR-SOURCE-BUCKET-NAME"
    },
    {
      "Sid": "SourceObjectPermissions",
      "Effect": "Allow",
      "Action": [
        "s3:GetObjectVersionForReplication",
        "s3:GetObjectVersionAcl",
        "s3:GetObjectVersionTagging"
      ],
      "Resource": "arn:aws:s3:::YOUR-SOURCE-BUCKET-NAME/*"
    },
    {
      "Sid": "DestinationBucketPermissions",
      "Effect": "Allow",
      "Action": [
        "s3:ReplicateObject",
        "s3:ReplicateDelete",
        "s3:ReplicateTags"
      ],
      "Resource": "arn:aws:s3:::YOUR-DESTINATION-BUCKET-NAME/*"
    }
  ]
}

Attach the Policy to the Role

aws iam put-role-policy \
  --role-name S3CrossAccountReplicationRole \
  --policy-name S3ReplicationPermissionsPolicy \
  --policy-document file://replication-permissions-policy.json \
  --profile source-account-profile

Step 4: Configure Destination Bucket Policy (Account B)

Critical Step

This is where most cross-account replication setups fail. The destination bucket must explicitly allow the source account's replication role to write objects. If you skip this or get the ARN wrong, replication will silently fail with AccessDenied.

Create the Bucket Policy

Create a file named destination-bucket-policy.json:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowReplicationFromSourceAccount",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::SOURCE-ACCOUNT-ID:role/S3CrossAccountReplicationRole"
      },
      "Action": [
        "s3:ReplicateObject",
        "s3:ReplicateDelete",
        "s3:ReplicateTags",
        "s3:ObjectOwnerOverrideToBucketOwner"
      ],
      "Resource": "arn:aws:s3:::YOUR-DESTINATION-BUCKET-NAME/*"
    },
    {
      "Sid": "AllowReplicationVersionCheck",
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::SOURCE-ACCOUNT-ID:role/S3CrossAccountReplicationRole"
      },
      "Action": [
        "s3:GetBucketVersioning",
        "s3:PutBucketVersioning"
      ],
      "Resource": "arn:aws:s3:::YOUR-DESTINATION-BUCKET-NAME"
    }
  ]
}

Apply the Bucket Policy

aws s3api put-bucket-policy \
  --bucket YOUR-DESTINATION-BUCKET-NAME \
  --policy file://destination-bucket-policy.json \
  --profile destination-account-profile

Step 5: Create Replication Configuration (Account A)

Now we create the actual replication rule on the source bucket.

Create the Replication Configuration

Create a file named replication-configuration.json:

{
  "Role": "arn:aws:iam::SOURCE-ACCOUNT-ID:role/S3CrossAccountReplicationRole",
  "Rules": [
    {
      "ID": "CrossAccountReplicationRule",
      "Status": "Enabled",
      "Priority": 1,
      "Filter": {},
      "Destination": {
        "Bucket": "arn:aws:s3:::YOUR-DESTINATION-BUCKET-NAME",
        "Account": "DESTINATION-ACCOUNT-ID",
        "AccessControlTranslation": {
          "Owner": "Destination"
        }
      },
      "DeleteMarkerReplication": {
        "Status": "Enabled"
      }
    }
  ]
}
Note

The AccessControlTranslation setting is crucial for cross-account replication. Without it, replicated objects would be owned by the source account, making them inaccessible to the destination account. Setting Owner: Destination ensures the destination account owns the replicated objects.

Apply the Replication Configuration

aws s3api put-bucket-replication \
  --bucket YOUR-SOURCE-BUCKET-NAME \
  --replication-configuration file://replication-configuration.json \
  --profile source-account-profile

Step 6: Test the Replication

Let's verify everything works by uploading a test file.

Upload a Test File

echo "This is a test file for replication" > test-replication.txt

aws s3 cp test-replication.txt s3://YOUR-SOURCE-BUCKET-NAME/ \
  --profile source-account-profile

Check Replication Status

Wait 1-2 minutes, then verify the file appears in the destination:

aws s3 ls s3://YOUR-DESTINATION-BUCKET-NAME/ \
  --profile destination-account-profile

Check Object Replication Status

aws s3api head-object \
  --bucket YOUR-SOURCE-BUCKET-NAME \
  --key test-replication.txt \
  --profile source-account-profile

Look for the ReplicationStatus field:

Status Meaning
PENDING Replication is in progress
COMPLETED Object has been replicated successfully
FAILED Replication failed - check permissions
REPLICA Object is a replica (seen on destination)

Step 7: Replicate Existing Objects

Important

S3 Replication only applies to new objects uploaded after the rule is enabled. Existing objects in your bucket are NOT automatically replicated. You must use one of the methods below to replicate them.

Option 1: S3 Batch Replication (Recommended)

  1. Go to Amazon S3 Console
  2. Navigate to your source bucket
  3. Go to Management tabReplication rules
  4. Select your replication rule
  5. Click ActionsReplicate existing objects
  6. Follow the wizard to create a Batch Operations job

Option 2: AWS CLI Sync (For Smaller Buckets)

aws s3 sync s3://YOUR-SOURCE-BUCKET-NAME s3://YOUR-DESTINATION-BUCKET-NAME \
  --acl bucket-owner-full-control \
  --profile source-account-profile
Pro Tip

For buckets with millions of objects, use S3 Batch Replication - it's designed for scale and provides progress tracking. The CLI sync works well for buckets under 100,000 objects.

Optional Configurations

Filter Replication by Prefix

To replicate only objects with a specific prefix:

"Filter": {
  "Prefix": "logs/"
}

Filter by Tags

To replicate only objects with specific tags:

"Filter": {
  "Tag": {
    "Key": "replicate",
    "Value": "true"
  }
}

KMS Encryption Support

If your objects are encrypted with KMS, add these permissions to the IAM policy:

{
  "Sid": "KMSPermissions",
  "Effect": "Allow",
  "Action": [
    "kms:Decrypt",
    "kms:GenerateDataKey"
  ],
  "Resource": [
    "arn:aws:kms:REGION:SOURCE-ACCOUNT-ID:key/SOURCE-KMS-KEY-ID",
    "arn:aws:kms:REGION:DESTINATION-ACCOUNT-ID:key/DESTINATION-KMS-KEY-ID"
  ]
}

And add EncryptionConfiguration to the replication destination:

"EncryptionConfiguration": {
  "ReplicaKmsKeyID": "arn:aws:kms:REGION:DESTINATION-ACCOUNT-ID:key/DESTINATION-KMS-KEY-ID"
}

Troubleshooting

Common issues and their solutions:

  • Objects not replicating - Verify versioning is enabled on both buckets. Check IAM role permissions include all required actions. Confirm the destination bucket policy trusts the exact role ARN.
  • AccessDenied errors - The role ARN in the destination bucket policy must match exactly. Check for typos in account IDs. Ensure s3:ObjectOwnerOverrideToBucketOwner is included if using AccessControlTranslation.
  • Replication status shows FAILED - Check CloudTrail logs for detailed error messages. Common causes: KMS key permissions missing, destination bucket doesn't exist, or bucket policy is malformed.
  • Existing objects not replicated - This is expected behavior. Use S3 Batch Replication or aws s3 sync to replicate existing objects.
  • "Versioning must be enabled" error - Run get-bucket-versioning on both buckets. If status is null or Suspended, versioning isn't enabled.
  • Replicated objects inaccessible in destination account - You're missing AccessControlTranslation in the replication config. Without it, replicated objects are owned by the source account.

Check CloudTrail for Errors

aws cloudtrail lookup-events \
  --lookup-attributes AttributeKey=EventName,AttributeValue=ReplicateObject \
  --start-time "2026-01-01T00:00:00Z" \
  --profile source-account-profile

Quick Reference: Placeholders

Placeholder Description Example
YOUR-SOURCE-BUCKET-NAME Name of source S3 bucket my-app-data-prod
YOUR-DESTINATION-BUCKET-NAME Name of destination S3 bucket my-app-data-backup
SOURCE-ACCOUNT-ID 12-digit AWS account ID (source) 111122223333
DESTINATION-ACCOUNT-ID 12-digit AWS account ID (destination) 444455556666
source-account-profile AWS CLI profile for source account company-prod
destination-account-profile AWS CLI profile for destination account company-backup

Cleanup

To remove the replication configuration:

aws s3api delete-bucket-replication \
  --bucket YOUR-SOURCE-BUCKET-NAME \
  --profile source-account-profile

To delete the IAM role and policy:

# Delete the inline policy first
aws iam delete-role-policy \
  --role-name S3CrossAccountReplicationRole \
  --policy-name S3ReplicationPermissionsPolicy \
  --profile source-account-profile

# Then delete the role
aws iam delete-role \
  --role-name S3CrossAccountReplicationRole \
  --profile source-account-profile

Conclusion

S3 Cross-Account Replication is a powerful feature for building resilient, multi-account architectures. Once configured, it runs automatically in the background, keeping your destination bucket in sync with minimal latency (typically under 15 minutes for most objects).

The key things to remember:

  • Versioning must be enabled on both buckets - no exceptions
  • The IAM role needs read access to source and write access to destination
  • The destination bucket policy is the critical cross-account trust piece
  • New objects replicate automatically; existing objects need Batch Replication
  • Always use AccessControlTranslation so the destination account owns replicated objects

Whether you're replicating for disaster recovery, compliance, or data aggregation, this setup gives you a reliable, hands-off solution that scales with your data.