Copy an S3 Bucket to Another Bucket, Account or Region: aws s3 sync, Cross-Account Policies, KMS and Tag Errors, and Moving a Bucket
To copy an S3 bucket to another bucket, run aws s3 sync s3://source-bucket s3://destination-bucket. The copy happens inside S3 (server-side), so nothing downloads to your PC. Between Regions, add --source-region for the source and --region for the destination; you pay inter-Region data transfer per GB. Between AWS accounts, one identity needs to read the source and write the destination: usually a role in the destination account plus a bucket policy on the source granting it s3:ListBucket, s3:GetObject and s3:GetObjectTagging. Two gotchas cause most "Access Denied" errors: SSE-KMS objects need kms:Decrypt on the source key (objects encrypted with the AWS managed aws/s3 key cannot be read from another account at all), and object tags need tagging permissions on both sides. You cannot rename a bucket or transfer one to another account; "moving" a bucket means create, copy, verify, delete. For millions of objects or all versions, use S3 Batch Replication, S3 Batch Operations or DataSync. Below: commands for every case, the exact policies, KMS and ownership rules, large-bucket options, costs, and every error.
The shop's product images lived in a bucket in the founders' original AWS account, in US East. The company had since moved everything else to an organization with a separate production account in Europe (London). Jake had to get 1.2 million objects across the account boundary and the Atlantic without breaking the website. His first aws s3 sync failed on object one with "Access Denied", and the second failed on object 40,000 with the same message for a completely different reason.
New to S3? The cards below cover S3 itself, replication, sync behavior and the 403 errors you may meet.
Copy one S3 bucket to another (same account)
The two commands you need, and the difference between them:
# copy everything that is new or changed; safe to re-run after an interruption
aws s3 sync s3://shop-old-assets s3://shop-assets-new
# copy one prefix ("folder") or one file
aws s3 cp s3://shop-old-assets/images/ s3://shop-assets-new/images/ --recursive
aws s3 cp s3://shop-old-assets/logo.png s3://shop-assets-new/logo.png
synccompares the two sides and copies only what is missing or different, so you can run it again until it copies nothing. It does not delete files on the destination unless you add--delete; our sync guide covers that behavior.cp --recursivecopies everything every time, which is fine for a one-off.- Filters:
--exclude "*" --include "*.jpg"copies only JPEGs;--dryrunshows what would happen. - Storage class on arrival:
--storage-class STANDARD_IAif the copy is an archive.
Both are server-side copies (S3's CopyObject): data moves inside AWS, not through your laptop, so your home internet speed does not matter, only the number of parallel requests. Raise that for big jobs with aws configure set default.s3.max_concurrent_requests 50.
Sync options worth knowing for migrations:
--only-show-errorskeeps the output to failures, so a million-object run leaves a readable log; send it to a file and you have a list of exactly what to fix.--size-onlycompares by size alone. Use it on a re-run when timestamps differ between the buckets but the content is the same, so sync does not copy everything again.--exact-timestampsdoes the opposite and treats any time difference as a change; rarely needed for S3-to-S3.--deleteremoves destination objects that are not in the source. Leave it off for a migration into a new bucket; it matters only when you keep two buckets in step.--metadata-directive REPLACEwithcplets you set new metadata (such as Cache-Control) on the copies, instead of carrying the old values over.
Copy an S3 bucket to another Region
Buckets live in one Region, so a cross-Region copy needs the CLI to know both. Tell it with two flags:
aws s3 sync s3://shop-old-assets s3://shop-assets-london \ --source-region us-east-1 --region eu-west-2
What changes compared with a same-Region copy:
- Cost: data transfer between Regions is charged per GB (typically around $0.02 per GB between most Regions), on top of the request charges. Same-Region copies have no transfer charge; our data transfer pricing map lists the cases.
- Encryption keys: KMS keys are Regional, so SSE-KMS objects are decrypted with the source Region's key and re-encrypted with a key in the destination Region (the bucket's default, or one you name with
--sse aws:kms --sse-kms-key-id). - Speed: longer distance, slightly slower per request; more parallel requests make up for it.
- Ongoing changes: if the source keeps changing, set up Cross-Region Replication for new objects and use the one-off copy (or Batch Replication) for the existing ones. Our replication guide covers the setup.
Jake: "Does the data come down to my PC and go back up?"
Ethan: "No. S3 copies it straight across inside AWS. Your laptop just sends the instructions."
Copy an S3 bucket to another AWS account
Across accounts, one identity must be allowed to read the source and write the destination. The cleanest setup runs the copy from the destination account, so the new objects belong to it from the start:
- In the destination account, create a role (or use your admin user) with permission to read the source and write the destination bucket.
- In the source account, add a bucket policy on the source bucket that lets that role list and read.
- If the source uses SSE-KMS with a customer managed key, add the role to that key's policy for
kms:Decrypt. - Run the sync with credentials for the destination-account role.
- Verify counts and sizes before anything is deleted (see verify).
The source bucket policy (in the source account):
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "AllowProductionAccountToCopy",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::222233334444:role/s3-migration"},
"Action": ["s3:ListBucket", "s3:GetObject", "s3:GetObjectVersion", "s3:GetObjectTagging"],
"Resource": ["arn:aws:s3:::shop-old-assets", "arn:aws:s3:::shop-old-assets/*"]
}]
}
The role's own policy (in the destination account) needs the same read actions on the source, plus s3:ListBucket, s3:PutObject and s3:PutObjectTagging on the destination bucket. Note the two resource lines: ListBucket applies to the bucket ARN, the object actions to bucket/*; missing either half is a classic 403.
Written out in full, the migration role's own policy in the destination account looks like this:
{
"Version": "2012-10-17",
"Statement": [
{"Sid": "ReadSource", "Effect": "Allow",
"Action": ["s3:ListBucket", "s3:GetObject", "s3:GetObjectVersion", "s3:GetObjectTagging"],
"Resource": ["arn:aws:s3:::shop-old-assets", "arn:aws:s3:::shop-old-assets/*"]},
{"Sid": "WriteDestination", "Effect": "Allow",
"Action": ["s3:ListBucket", "s3:PutObject", "s3:PutObjectTagging"],
"Resource": ["arn:aws:s3:::shop-assets-london", "arn:aws:s3:::shop-assets-london/*"]},
{"Sid": "UseKeys", "Effect": "Allow",
"Action": ["kms:Decrypt", "kms:GenerateDataKey"],
"Resource": ["arn:aws:kms:us-east-1:111122223333:key/SOURCE-KEY-ID",
"arn:aws:kms:eu-west-2:222233334444:key/DEST-KEY-ID"]}
]
}
Cross-account access always needs both sides to agree: this policy says what the role may try, and the source bucket policy (plus the source key policy) says the other account accepts it. If either side is missing, the request is denied, which is why testing with one object from each encryption type before the full run saves hours. Delete the role, or at least its access to the source, once the migration is finished; a migration role left in place is a standing path into the old account.
Inside an AWS Organization, you can grant the whole organization instead of naming each role, with the aws:PrincipalOrgID condition. This statement lets any principal in your organization read the bucket, while their own IAM policies still decide who actually can:
{
"Sid": "AllowOrgReadForMigration",
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:ListBucket", "s3:GetObject", "s3:GetObjectTagging"],
"Resource": ["arn:aws:s3:::shop-old-assets", "arn:aws:s3:::shop-old-assets/*"],
"Condition": {"StringEquals": {"aws:PrincipalOrgID": "o-a1b2c3d4e5"}}
}
It is convenient for a migration with many target accounts, but it is broad: remove it when the copy is done, and remember it does nothing for KMS, whose key policy still needs its own grant.
Who owns the copied objects? New buckets use the "bucket owner enforced" setting by default (ACLs disabled), so every object in the destination bucket belongs to the destination account no matter who wrote it. On older buckets with ACLs still enabled, an object written by another account belongs to the writer unless you add --acl bucket-owner-full-control; check the destination bucket's Object Ownership setting before you start.
Encryption and tags: the two mid-copy "Access Denied" causes
Jake's second failure, at object 40,000, was one of these. Both only affect some objects, which is why a copy can run for an hour and then stop:
| Objects that fail | Why | Fix |
|---|---|---|
| SSE-KMS with a customer managed key | The copying role lacks kms:Decrypt on the source key (and kms:GenerateDataKey on the destination key) | Add the role to both key policies, or grant it in IAM where the key policy allows |
SSE-KMS with the AWS managed aws/s3 key | That key's policy cannot be changed, so no other account can ever decrypt | Run the copy from the source account into the destination bucket, or re-encrypt in place first with SSE-S3 or a shareable customer managed key |
| Objects with tags | Copying carries tags across, which needs s3:GetObjectTagging on the source and s3:PutObjectTagging on the destination | Add both permissions |
| Objects in Glacier storage classes | Archived objects must be restored before they can be read or copied | Restore first, or use --force-glacier-transfer on restored objects |
| Objects uploaded by a third account (ACL-era buckets) | The bucket owner does not own those objects, so its policy cannot grant access to them | Switch the source bucket to bucket owner enforced first, or copy with the owner's credentials |
The cross-account KMS key policy. For objects under a customer managed key, add a statement like this to the source key's policy (in the source account), naming the migration role from the destination account:
{
"Sid": "AllowMigrationRoleToDecrypt",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::222233334444:role/s3-migration"},
"Action": ["kms:Decrypt", "kms:DescribeKey"],
"Resource": "*"
}
In a key policy, "Resource": "*" means "this key". The role also needs kms:Decrypt on the key ARN in its own IAM policy (shown above), because cross-account KMS access, like S3, needs both sides to agree. On the destination side, the role needs kms:GenerateDataKey (and kms:Decrypt for multipart copies) on the destination bucket's key, which lives in the same account and can be granted in IAM alone if that key's policy allows IAM policies to grant access.
Run the copy from the source account when the source uses aws/s3: that account can decrypt its own objects, and a bucket policy on the destination can allow its role to s3:PutObject. With bucket owner enforced on the destination, the objects still end up owned by the destination account. Our S3 403 guide has more ways to read these errors, and our KMS costs guide explains why a million-object copy also shows up as KMS requests.
Jake: "The first 40,000 objects were SSE-S3. Then the old uploads with the aws/s3 key started."
Ethan: "And no policy can fix that key. Run that part from the old account instead."
What a copy carries across, and what it does not
A copied object is a new object, so some properties travel and some are set fresh. Knowing which saves a surprise after the switch:
| Property | After an S3-to-S3 copy | What to do |
|---|---|---|
| Content-Type, Cache-Control, user metadata | Copied | Nothing; spot-check with head-object |
| Tags | Copied, if you have tagging permissions on both sides | Grant GetObjectTagging and PutObjectTagging |
| Storage class | Standard, unless you ask for another | Add --storage-class, or let a lifecycle rule move objects |
| Encryption | The destination bucket's default encryption, unless you name a key | Set the destination default before you start |
| Last-modified date | The time of the copy | Keep the original dates in metadata if they matter to your app |
| Older versions and delete markers | Not copied by sync or cp, which copy current versions only | Batch Replication if you need the history |
| ACLs | Not carried over | Use bucket policies; ACLs are off on new buckets anyway |
| Object Lock retention and legal holds | Not carried over by a plain copy | Enable Object Lock on the destination and set retention again (Batch Operations can do it at scale) |
The last-modified change trips up two kinds of systems: lifecycle rules that expire objects by age (every object looks brand new, so expiry restarts) and applications that sort or sync by date. If either matters, plan for it before you switch.
Copy a bucket with boto3 (Python)
When you need logic the CLI does not have (rename keys on the way, skip objects by metadata, write a report), copy with boto3. Use the managed copy() method rather than copy_object(): it switches to a multipart copy for large objects automatically, while copy_object() fails on anything over 5 GB.
import boto3
from botocore.config import Config
from concurrent.futures import ThreadPoolExecutor
src_s3 = boto3.client("s3", region_name="us-east-1", config=Config(max_pool_connections=32))
dst_s3 = boto3.client("s3", region_name="eu-west-2", config=Config(max_pool_connections=32))
SRC, DST = "shop-old-assets", "shop-assets-london"
def copy_one(key):
dst_s3.copy({"Bucket": SRC, "Key": key}, DST, key, SourceClient=src_s3)
return key
keys = (o["Key"] for page in src_s3.get_paginator("list_objects_v2").paginate(Bucket=SRC)
for o in page.get("Contents", []))
with ThreadPoolExecutor(max_workers=32) as pool:
for n, key in enumerate(pool.map(copy_one, keys), 1):
if n % 10000 == 0:
print(n, "copied")
Two details in that code matter. The destination client lives in the destination Region, and SourceClient tells boto3 to read the source through a client in the source Region, which avoids redirect errors across Regions. And the connection pool matches the thread count, so the threads do not queue for connections. For a cross-account copy, create the clients from a session using the destination-account role, exactly as with the CLI. Unlike sync, this loop copies everything every time; to make it resumable, skip keys whose destination object already exists with the same size, or keep a list of finished keys.
Objects over 5 GB. A single CopyObject request handles objects up to 5 GB. Larger objects need a multipart copy (UploadPartCopy), which aws s3 cp, aws s3 sync and boto3's copy() all do for you. If you script the low-level API yourself, large backups and video files are where the copy fails with an invalid request error.
Running the copy from Windows PowerShell
Plenty of these copies are run from a Windows laptop, and the AWS CLI works the same there, with three differences that trip people up:
- Line continuation: the backslash at the end of a line in the examples above is for Linux shells. In PowerShell, use a backtick (
`), or put the whole command on one line. - JSON policies: quoting JSON inline in PowerShell is fragile. Save the policy to a file and pass it with
file://, for exampleaws s3api put-bucket-policy --bucket shop-old-assets --policy file://policy.json. - Profiles: keep one named profile per account (
aws configure --profile old-account,--profile prod) and pass--profileon every command, so a copy never runs with the wrong account's credentials.
aws s3 sync s3://shop-old-assets s3://shop-assets-london ` --source-region us-east-1 --region eu-west-2 --profile prod
A laptop that goes to sleep stops the copy, though the copy itself runs inside AWS. For a long job, run it on a small EC2 instance or in CloudShell in the destination account, or use one of the managed tools below, so a closed lid does not leave the migration half done. If you stop and restart, sync picks up where it left off.
Jake: "I ran it overnight on my laptop and it stopped when Windows restarted for updates."
Ethan: "Then run it again; sync skips what's done. Next time, CloudShell, so updates can't stop it."
Copying in the S3 console
For a handful of files or folders, the console is fine: select the objects, choose Actions, then Copy, and pick the destination bucket and prefix. It is the quickest way to move a few test files, and it shows exactly what is being copied.
Its limits appear quickly for real migrations. A browser tab has to stay open for the whole job, there is no resume after an interruption, and you only see buckets your signed-in identity can reach, which for a cross-account copy means switching roles into an identity with access to both sides. For anything beyond a few thousand objects, use the CLI from CloudShell (it runs in the browser too, with your console credentials, and keeps going if a page reloads) or start an S3 Batch Operations job from the console, which runs entirely inside S3 and reports when it is done.
Which method for which job
- Development data into a production account, once:
aws s3 syncfrom the production side, with a source bucket policy for the migration role. Remove the policy afterwards. - A protected backup in another account: AWS Backup can back up S3 buckets and copy the backups to a vault in another account, with retention you control there. That is a backup, with restore points, rather than a live second bucket.
- A live mirror in another account or Region: replication for new writes, plus Batch Replication for what is already there.
- Merging several old buckets into one: one sync per source into its own prefix in the new bucket (
s3://new-bucket/old-bucket-name/), so keys from different sources cannot collide. - Splitting one bucket into several: sync per prefix, with
--excludeand--includefilters, and verify each prefix's count separately. - Tens of millions of objects, once: S3 Inventory plus a Batch Operations Copy job, with its completion report as the audit record.
- Regular transfers on a schedule: DataSync tasks, which verify each run and keep a history.
Jake: "Our accountant wants a copy of the invoices bucket in a separate account that nobody in IT can delete."
Ethan: "That's a backup job, not a copy: AWS Backup to a vault in the audit account, with a vault lock. A synced bucket can be emptied as easily as the original."
How long will it take, and how to make it faster
Copy time depends on two things: how many objects, and how many bytes. Millions of small objects are limited by requests per second; a few huge files are limited by throughput. Rather than guess, measure:
- Copy a sample prefix of about 10,000 objects that looks like the rest of the bucket, and time it.
- Scale by object count for small-object buckets, or by total size for large-object buckets, to estimate the full run.
- Raise parallelism and time the sample again; stop when the speed stops improving or you see 503 Slow Down responses.
The CLI settings that matter for big copies live in your AWS config file and apply to every aws s3 command:
aws configure set default.s3.max_concurrent_requests 64 aws configure set default.s3.max_queue_size 10000 aws configure set default.s3.multipart_chunksize 64MB aws configure set default.s3.preferred_transfer_client crt
More concurrent requests help small objects most; larger multipart chunks help big files; and the CRT transfer client in AWS CLI v2 manages parallelism itself and is often faster on large transfers. S3 supports at least 3,500 write and copy requests per second per prefix, so a bucket whose keys spread across many prefixes can absorb far more parallel copies than one with everything under a single folder. Running the copy from an EC2 instance or CloudShell in the destination Region also helps, since every request travels a shorter distance than from a home connection.
When the source bucket policy itself blocks you
Sometimes every Allow is in place and the copy still fails with Access Denied. The usual reason is an explicit Deny, which always wins over any Allow:
- A VPC endpoint condition: policies with a Deny unless
aws:SourceVpcematches a specific endpoint block any request from outside that VPC, including your copy from a laptop or another account. Run the copy from inside the allowed VPC, or add a narrow exception for the migration role. - An IP restriction (
aws:SourceIp) that only allows office addresses; server-side copies from AWS services do not come from those addresses. - A Deny on unencrypted uploads in the destination bucket's policy, which rejects copies that do not ask for the required encryption header. Add
--sse aws:kms(and the key ID) or--sse AES256to match the rule. - Service control policies in an AWS Organization that deny S3 actions outside certain Regions or for certain principals; these sit above both accounts' own policies.
- Requester Pays buckets, which reject requests that do not accept the charges; add
--request-payer requester.
The fastest way to find the blocker is the error context: CloudTrail records the denied call with the principal, and IAM Access Analyzer's policy checks can show which statement applies. Our S3 403 guide walks through reading these in order.
"Move" or migrate an S3 bucket to another account
There is no button to transfer a bucket's ownership, and bucket names cannot be renamed. Migrating a bucket is a sequence:
- Create the new bucket in the destination account (and Region), with the encryption, versioning, lifecycle rules and Object Ownership you want.
- Set up permissions as above, and start ongoing replication if the source keeps changing during the migration.
- Copy existing objects with sync, Batch Replication or Batch Operations.
- Verify object counts and total size on both sides.
- Switch applications (and CloudFront origins) to the new bucket; keep the old one read-only for a while.
- Delete the old bucket once nothing reads it. If you need the same name, you can only reuse it after the old bucket is deleted, and names are global, so someone else could take it in between.
For moving within one account (a "rename"), aws s3 mv s3://old s3://new --recursive copies then deletes each object, but a sync followed by a verified delete is safer for anything important.
Jake: "Can't AWS Support just move the bucket to the new account for us?"
Ethan: "No such thing. Buckets stay where they're born. We copy, verify, switch, and only then delete."
Switching CloudFront and applications to the new bucket
The copy is only half a migration; the switch is where websites break. For a bucket behind CloudFront, as the shop's images were:
- Give CloudFront access to the new bucket. With origin access control, the new bucket's policy must allow the CloudFront service to
s3:GetObject, with a condition naming your distribution's ARN inAWS:SourceArn. The old bucket's policy does not carry over. - Test on a second path first. Add the new bucket as a second origin with a cache behavior on a test path, or use a staging distribution, and load a sample of pages and images through it.
- Switch the origin of the main behavior to the new bucket. CloudFront serves cached copies until they expire, so users see no gap.
- Invalidate only if needed. If the files are identical, there is no need; if metadata changed (Content-Type, Cache-Control), invalidate the paths that matter.
- Make the old bucket read-only with a bucket policy that denies writes, and watch its request metrics. Requests that keep arriving name the application you forgot.
For applications that read the bucket directly, change the bucket name in configuration (Parameter Store or environment variables are easier than code), deploy, and check the old bucket's metrics again. Mobile apps and partner integrations are the usual stragglers, since they update slowly; a month of read-only overlap usually catches them.
Keep the new bucket in step: cross-account replication in brief
If the old bucket keeps receiving writes during the migration, a one-off copy goes stale the moment it ends. S3 Replication solves that for new and changed objects, and works across accounts with three pieces:
- A replication rule on the source bucket, with versioning turned on in both buckets (replication requires it).
- A replication IAM role in the source account that can read from the source and replicate to the destination, including
kms:Decryptandkms:Encrypton the keys involved if objects use SSE-KMS. - A bucket policy on the destination that lets that role call
s3:ReplicateObject,s3:ReplicateDeleteands3:ReplicateTags, pluss3:ObjectOwnerOverrideToBucketOwnerif you turn on owner override.
Replication only covers objects written after the rule exists. For everything already in the bucket, run S3 Batch Replication once, or the sync above, so old and new objects both arrive. The usual migration order is: create the destination, turn on replication, copy the existing objects, verify, switch, then stop replication and retire the source. Our replication troubleshooting guide covers the errors this setup can hit.
Millions of objects, all versions, or terabytes
aws s3 sync handles a few million objects fine if you give it time and parallel requests. Beyond that, or when you need more than the current versions, use a managed option:
| Tool | Copies | Tracking | Best for |
|---|---|---|---|
aws s3 sync | Current versions | Your terminal output; re-run to resume | Up to a few million objects, one-off jobs |
| S3 Batch Replication | Existing objects, including older versions where configured | A job with a completion report | Migrations that also need ongoing replication |
| S3 Batch Operations (Copy) | Every object in a manifest (Inventory report or CSV) | Retries plus a failure report | Huge one-off copies, or changing storage class or encryption while copying |
| AWS DataSync | Files and objects, with filters | Task history, built-in verification | Scheduled or repeated transfers between accounts and Regions |
DataSync between accounts, in short. DataSync works well for repeated or scheduled copies, and it verifies the data it moves. Across accounts, run it in the destination account and create the source location (the other account's bucket) with the CLI, since the console only lists buckets in your own account. The source bucket's policy must allow the IAM role DataSync uses to list and read, and the destination bucket needs the usual write access for that role. DataSync is priced per GB transferred, on top of the S3 request charges, so for a single one-off copy of a bucket, sync or Batch Operations usually costs less.
Batch Operations from an inventory. For tens of millions of objects, turn on S3 Inventory for the source bucket, wait for the first report, and point a Batch Operations Copy job at it. The job retries failed objects, writes a completion report listing every failure with its reason, and can set storage class, encryption or Object Lock settings on the copies as it goes. It is the tool to reach for when "re-run the sync until it copies nothing" would take days.
Very fast copies can hit S3's per-prefix request limits and return "503 Slow Down"; the tools retry, and our Slow Down guide explains spreading load across prefixes.
What a copy costs
- Requests: each copy is a request on the destination (billed like a PUT) plus a read and list requests on the source; for 1.2 million objects, a few dollars.
- Inter-Region transfer: per GB moved between Regions; the shop's 800 GB cost about $16. Same-Region copies have no transfer charge.
- KMS: decrypt and encrypt requests for SSE-KMS objects; S3 Bucket Keys reduce them.
- Storage: you pay for both copies until the old bucket is deleted.
- Batch Operations, Batch Replication and DataSync add their own small per-job and per-object or per-GB charges.
A worked example for the shop's migration, 1.2 million objects and 800 GB from US East to London, using typical list prices (check your Regions in the pricing calculator):
- Copy requests on the destination: 1.2 million × about $0.005 per 1,000 = about $6.
- Listing and reads on the source: a few thousand list requests plus the reads, about $1.
- Transfer between Regions: 800 GB × about $0.02 = about $16.
- Double storage for a month of overlap: 800 GB × about $0.024 in London = about $19.
Roughly $40 for the whole migration, most of it the month of overlap. Two things make copies expensive that people do not expect: millions of tiny objects (request charges scale with the object count, not the size), and copying into an infrequent-access or archive class, where each object carries a minimum billable size or storage duration. Copy into Standard and let a lifecycle rule move objects later if you are unsure.
Verify the copy before deleting anything
aws s3 ls s3://shop-old-assets --recursive --summarize --source-region us-east-1 | tail -2 aws s3 ls s3://shop-assets-london --recursive --summarize --region eu-west-2 | tail -2 # a second sync that copies nothing is a good sign aws s3 sync s3://shop-old-assets s3://shop-assets-london --source-region us-east-1 --region eu-west-2 --dryrun
Compare "Total Objects" and "Total Size". For very large buckets, S3 Inventory reports on both sides are faster than listing; our guide to listing more than 1,000 objects covers scripts that page through a bucket. Check a few objects' metadata (Content-Type, Cache-Control) too, since a website serving images depends on them:
aws s3api head-object --bucket shop-assets-london --key images/jacket-blue.jpg --region eu-west-2 \ --query "[ContentType,CacheControl,ServerSideEncryption,ContentLength]"
For files where a silent change would really hurt (contracts, backups, exports for an auditor), compare checksums too. Objects with an additional checksum return it from head-object when you add --checksum-mode ENABLED, so you can check the same value on both sides for a sample of keys. Do not compare ETags across a copy: for objects uploaded in parts, the ETag depends on how the upload was split, so two identical files can carry different ETags. Size, count and checksum are the comparisons that hold.
Then point a test copy of your application (or a CloudFront test distribution) at the new bucket before switching production. Keep the old bucket for a few weeks with access logging or a request metric on it: zero requests is the signal that nothing still depends on it.
Jake: "Object count matches, size matches, and the dry run copies nothing."
Ethan: "Good. Now leave the old one alone for a month. Deleting is the only step we can't undo."
A migration checklist you can copy
- Inventory the source: object count, total size, storage classes, encryption types (SSE-S3, aws/s3, customer managed keys), versioning and Object Lock.
- Create the destination with the settings you want to end up with: Region, default encryption, versioning, Object Ownership (bucket owner enforced), lifecycle rules, Block Public Access.
- Set permissions: source bucket policy, destination role policy, KMS key policies, tagging permissions.
- Turn on replication if the source keeps changing during the move.
- Copy a test prefix first and check metadata, tags and encryption on a few objects.
- Copy everything with sync, Batch Operations or DataSync; copy aws/s3-encrypted objects from the source account.
- Verify counts and sizes, and run a dry-run sync that copies nothing.
- Switch CloudFront and applications, with the old bucket read-only.
- Watch the old bucket's requests for a few weeks, then stop replication and delete it.
The shop printed this list and checked it off during the move; the only step that took longer than planned was step 3, which is normal: the permissions are the job, and the copy itself is just waiting.
Errors and fixes
AccessDeniedcalling ListObjectsV2: nos3:ListBucketon the source bucket ARN (not/*).AccessDeniedcalling CopyObject on some objects: KMS key access, the aws/s3 key, tagging permissions, or objects owned by another account (see the table above).PermanentRedirector "must be addressed using the specified endpoint": the Region is wrong; add--source-regionand--region.AccessDeniedon AssumeRole: the role's trust policy does not allow you; see our AssumeRole guide.InvalidObjectState: the object is archived in a Glacier class; restore it first.- 503 Slow Down: too many requests to one prefix; the CLI retries, or lower concurrency.
- Copy "succeeds" but the website breaks: Content-Type or other metadata differs; copy with metadata preserved (the default for S3-to-S3) and check CloudFront points to the new bucket with the right permissions.
Frequently asked questions
How do I copy an S3 bucket to another bucket?
Run aws s3 sync s3://source-bucket s3://destination-bucket; it copies server-side and can be re-run safely.
How do I copy an S3 bucket to another AWS account?
Give a role in the destination account read access through the source bucket policy (and KMS key policy), then run aws s3 sync with that role.
How do I copy S3 objects between Regions?
Use aws s3 sync with --source-region for the source and --region for the destination; inter-Region transfer is charged per GB.
Can I transfer an S3 bucket to another account?
No. Create a bucket in the other account, copy the objects, verify, then delete the old bucket.
Can I rename an S3 bucket?
No. Create a new bucket and copy or move the objects into it.
Why do I get Access Denied copying S3 objects across accounts?
Usually missing ListBucket or GetObject on the source, KMS decrypt permission, tagging permissions, or objects encrypted with the aws/s3 managed key.
Can another account read objects encrypted with the aws/s3 key?
No. Its key policy cannot be changed; copy from the source account or re-encrypt the objects first.
Does aws s3 sync download files to my computer?
Not for S3-to-S3 copies; the data is copied inside S3.
What is the difference between aws s3 cp and aws s3 sync?
cp copies everything you name; sync copies only new or changed objects and can be re-run to resume.
How do I move objects from one S3 bucket to another?
aws s3 mv s3://source s3://destination --recursive copies then deletes; a sync plus a verified delete is safer.
Does copying S3 objects keep their metadata and tags?
S3-to-S3 copies keep metadata and tags by default, which is why tagging permissions are needed.
How do I copy all versions of S3 objects?
Use S3 Batch Replication with a replication rule; sync copies only current versions.
What is the fastest way to copy a huge S3 bucket?
S3 Batch Operations, Batch Replication or DataSync; or sync with more concurrent requests.
How much does it cost to copy an S3 bucket?
Request charges, inter-Region transfer per GB if Regions differ, KMS requests for encrypted objects, and storage for both copies until you delete one.
Who owns objects copied into another account's bucket?
With bucket owner enforced (the default for new buckets), the destination bucket's account owns them.
Do I need bucket-owner-full-control when copying?
Only for destination buckets that still have ACLs enabled.
How do I check that an S3 copy is complete?
Compare Total Objects and Total Size from aws s3 ls --recursive --summarize, and run a dry-run sync that should copy nothing.
What IAM permissions does a cross-account S3 copy need?
ListBucket, GetObject and GetObjectTagging on the source, ListBucket, PutObject and PutObjectTagging on the destination, KMS Decrypt and GenerateDataKey on any keys, and a matching source bucket policy.
What KMS key policy does a cross-account S3 copy need?
The source key's policy must allow the destination account's role kms:Decrypt, and that role's IAM policy must allow it too; the destination key needs kms:GenerateDataKey for the role.
How do I share an S3 bucket with another account?
Add a bucket policy naming the other account's role (or its account) with the read or write actions it needs, and grant KMS access if the bucket uses a customer managed key.
Can I compare ETags to check an S3 copy?
Not reliably; ETags of multipart uploads depend on the part sizes. Compare sizes, counts and, for important files, checksums from head-object with checksum mode enabled.
Should I delete the migration role afterwards?
Yes, or remove its access to the source; a leftover migration role is a standing path into the old account.
Can I copy an S3 bucket in the console?
For a few files or folders, yes, with Actions then Copy; for large or cross-account copies, use CloudShell with the CLI or a Batch Operations job.
How do I back up an S3 bucket to another account?
Use AWS Backup with a backup vault in the other account for protected restore points; a synced copy can be deleted as easily as the original.
How do I merge several S3 buckets into one?
Sync each source into its own prefix in the new bucket, so keys cannot collide, and verify each prefix's object count.
How long does it take to copy an S3 bucket?
It depends on object count and total size; time a sample prefix of about 10,000 objects and scale it, then raise concurrency until speed stops improving.
How do I make aws s3 sync faster?
Raise max_concurrent_requests, use larger multipart chunks for big files, try the CRT transfer client, and run the copy from EC2 or CloudShell in the destination Region.
Why is my S3 copy denied when all permissions allow it?
An explicit Deny wins: VPC endpoint or IP conditions, required encryption headers, service control policies, or a Requester Pays bucket.
Does copying S3 objects keep the last-modified date?
No. Copies get the time of the copy as their last-modified date, which restarts age-based lifecycle rules.
Does aws s3 sync copy older versions?
No, only current versions; use S3 Batch Replication to copy version history.
How do I copy an S3 bucket with Python?
Use boto3's managed copy() method in a loop over list_objects_v2 pages, with SourceClient set for cross-Region copies; it handles objects over 5 GB.
Why does copy_object fail on large files?
A single CopyObject request is limited to 5 GB; larger objects need a multipart copy, which the CLI and boto3's copy() do automatically.
How do I run aws s3 sync in PowerShell?
The same command works; use a backtick instead of a backslash for line continuation, and pass JSON policies with file://.
How do I allow my whole AWS Organization to read a bucket?
Add a bucket policy statement with the aws:PrincipalOrgID condition set to your organization ID; KMS keys still need their own grant.
How do I switch CloudFront to a new S3 bucket?
Allow the distribution in the new bucket's policy with origin access control, test on a second path, then change the origin; cached files keep serving during the switch.
Does S3 replication copy existing objects?
No. Replication covers new writes after the rule exists; use Batch Replication or a sync for objects already in the bucket.
Should I use replication or a one-time copy?
Replication for ongoing changes, a one-time copy or Batch Replication for existing objects; migrations usually use both.
For readers preparing for an AWS certification
We have been covering AWS certifications alongside our fixes, so here is a short extra for readers studying for one. If you only came for the fix, you already have it; feel free to skip this part.
Exams that test this topic: Solutions Architect – Associate (SAA-C03) • CloudOps Engineer – Associate (SOA-C03) • Security – Specialty (SCS-C03).
Here are the exam-style questions this topic tends to raise, with the reasoning you need to answer them:
1. A role in account B must copy objects from a bucket in account A. What must account A configure?
A bucket policy on the source bucket granting the role read access (ListBucket and GetObject).
2. Objects encrypted with the AWS managed key aws/s3 must be read from another account. Is that possible?
No; re-encrypt them with SSE-S3 or a customer managed key, or copy from the source account.
3. Which S3 feature copies existing objects, including older versions, to another bucket under a replication rule?
S3 Batch Replication.
4. Which S3 Object Ownership setting makes the bucket owner own every object and disables ACLs?
Bucket owner enforced.
5. Is data transfer charged when copying S3 objects between Regions?
Yes, inter-Region data transfer is charged per GB.
6. Can an S3 bucket be transferred to another AWS account?
No; create a new bucket and copy the objects.
Exam codes and dates change often. Our AWS certification changes page keeps track of what is current.
The 1.2 million product images now live in the London bucket of the production account. The old-account objects encrypted with aws/s3 were copied from the old account into the new bucket, the rest from the new account, and a final dry-run sync copied nothing. CloudFront switched origins on a Tuesday morning, and the old bucket was deleted a month later, after a month of zero requests.
Looking back, Jake's notes for the next migration fit on one card: list the encryption types before writing any policy, because the aws/s3 objects decide which account runs which part of the copy; test one prefix end to end, including a page load through CloudFront; and keep the old bucket read-only rather than deleting it on switch day. The copy itself took a night. The planning took a week, and that was the part that made the night uneventful.
📌 If you keep one line from this page
Copy buckets with aws s3 sync (adding --source-region and --region across Regions); across accounts, grant a destination-account role read through the source bucket policy and KMS key policy, include the tagging permissions, and copy aws/s3-encrypted objects from the source account.
No bucket transfers or renames · verify counts before deleting · Batch Replication for versions and huge buckets.
Revision note. Written October 20, 2026. Buckets cannot be moved, only copied; the permissions are the whole job.