AWS S3 sync deletes existing uploads because --delete runs against the wrong prefix

2026-11-13 · 3 min read

A deploy script ran this:


aws s3 sync ./dist s3://acme-prod/uploads/ --delete

The intent was to publish build output. The result was that every object under uploads/ that was not in ./dist was deleted—including customer-uploaded files. The site still loaded, but profile images and attachments returned 404s until the bucket’s versioned objects were restored. The cost was not the sync; it was the rollback, support load, and the next deploy being delayed while everyone checked prefixes.

How an aws s3 sync --delete wrong prefix erases unrelated objects

aws s3 sync makes the destination match the source. With --delete, files present in the destination but absent from the source are deleted. [link needed]

The dangerous part is scope. S3 has no real folders; a “prefix” is just a key prefix. If the destination prefix is broader than the source’s intended target, --delete treats every object under that broader prefix as fair game. An aws s3 sync --delete wrong prefix can therefore remove unrelated assets, another team’s files, or user uploads that were never part of the build.

Two common forms:


# Source is narrower than destination: build output vs. all uploads
aws s3 sync ./dist s3://acme-prod/uploads/ --delete

# Destination prefix is one level too high: releases/ vs. releases/app/
aws s3 sync ./dist s3://acme-prod/releases/ --delete

In both cases the command is valid. The deletion is expected behavior. The path is the bug.

A safe preflight procedure

1. Write the source and destination as explicit variables and inspect them before running anything:


SRC="./dist/"
DST="s3://acme-prod/app/static/"
printf 'SRC=%s\nDST=%s\n' "$SRC" "$DST"

2. Confirm that the destination prefix is dedicated to this source. If uploads/ contains both build assets and user files, sync to app/static/ or omit --delete.

3. Run the exact command with --dryrun and --delete:


aws s3 sync "$SRC" "$DST" --delete --dryrun

4. Read the (delete) lines. If you see filenames that do not belong to the build, stop and fix the prefix.

5. Check the destination contents separately:


aws s3 ls --recursive "$DST" | wc -l
find "$SRC" -type f | wc -l

The counts do not need to match exactly—excludes and nested paths can differ—but a large gap is a warning. [link needed]

6. Make sure bucket versioning is enabled, or take a backup of the destination prefix. Versioning makes recovery from an aws s3 sync --delete wrong prefix much cheaper. [link needed]

7. Run the command without --dryrun, then spot-check a known old object and a newly uploaded object.

When NOT to use --delete

Do not use --delete when the destination prefix is shared by multiple producers: user uploads plus deploy artifacts, two services writing under one prefix, or manual files that are not in source control. Also skip it during a first migration or when syncing a partial build that intentionally excludes files.

In those cases, either sync without --delete or create a dedicated prefix per source. A dedicated prefix is usually simpler than maintaining complex --exclude/--include rules. This is where an aws s3 sync --delete wrong prefix hurts most: the command is correct, but the destination contract was never defined.

Preflight checklist