Restore Deleted Files in Amazon S3: Undelete With Delete Markers, Restore Thousands at Once, and Roll Back an Old Version (Versioning)
If versioning was turned on when your files were deleted from Amazon S3, they are not gone. S3 hid each one behind a small placeholder called a delete marker, and every byte is still there. Remove the delete marker and the file comes back, exactly as it was, under the same name. One file takes one command; ten thousand take a short script, because S3 accepts at most 1,000 keys per delete request. If versioning was off, S3 has no undelete at all, and only a backup or a copy elsewhere can help. Two surprises catch people mid-rescue. Running the delete command again does not undo anything; it stacks another delete marker on top. And the most-copied bulk-restore answer online sits right next to a command that permanently deletes every version in the bucket, the opposite of what you want. Check one setting first, because a lifecycle rule may be counting down the days until your hidden files really are deleted.
Jake's repair shop keeps a before-and-after photo of every phone it fixes, in an S3 bucket Ethan set up, because warranty claims sometimes arrive months later. On Thursday night Jake tried to tidy the shop laptop's photo folder with the sync command Ethan had shown him, ran it from the wrong, empty folder with --delete on the end, and watched 4,218 lines scroll past, each starting with "delete:". The bucket was empty. He called Ethan in a panic, at 11 at night. Ethan asked one question first: "Did I turn on versioning when I made that bucket?" He had. "Then go to bed," he said. "They're all still there. We'll bring them back in the morning." This page shows how: the 30-second check that tells you whether your files can come back, how to undelete one file or thousands, how to restore a folder or roll back a file that was overwritten, the clock that can make a recovery impossible, what to try when versioning was off, and how to make the next accident a non-event.
New to S3? Our plain-English guide to what Amazon S3 is explains buckets and objects from zero. You can also start right here.
First: can these files come back? The 30-second check
Everything depends on one bucket setting, so look at it before you try anything else:
aws s3api get-bucket-versioning --bucket shop-photos
There are three possible answers:
"Status": "Enabled": good news. Every delete since versioning was switched on left the data in place. Keep reading."Status": "Suspended": versioning was on and has been paused. Files uploaded while versioning was on can come back, even if they were deleted later. Files uploaded while it was suspended get the version IDnull, and in a suspended bucket a delete permanently removes anullversion, so those cannot.- Nothing at all (an empty response): versioning has never been enabled on this bucket. S3 cannot undelete anything. Skip to what to try when versioning was off.
A bucket that has had versioning switched on can never return to the unversioned state; it can only be suspended. So "Enabled" today does not prove it was enabled on the day of the delete. The second half of the check proves it, by looking for the deleted files' hidden versions:
aws s3api list-object-versions --bucket shop-photos --prefix photos/ \
--query 'DeleteMarkers[?IsLatest==`true`].[Key,VersionId,LastModified]' --output text
Each line is one deleted file: its name, the ID of the delete marker hiding it, and when it was deleted. If your files are listed, they can come back. If this prints nothing for a folder you know was full, the files were deleted before versioning existed, or something has already removed their old versions.
If the AWS CLI is not set up on your machine yet, our guide to the "Unable to locate credentials" error gets it working in a few minutes. Those backticks around true are part of the query language, not a typo; keep the single quotes around the whole query so your shell leaves them alone.
What a delete marker is, in plain English
In a bucket with versioning on, S3 never throws a file away when you delete it the ordinary way. Instead it adds a delete marker: a placeholder with the same name as the file, a version ID of its own, and no data. The marker becomes the "current version" of that name, and the real file becomes a "noncurrent version" underneath it.
From then on, S3 behaves as if the file is gone. A normal download returns a 404 Not Found error with the response header x-amz-delete-marker: true, listings skip the name, and the console hides it. But every earlier version is still stored, untouched, and still billed: normal S3 rates apply to every version you keep.
Undeleting is therefore simple in principle: delete the delete marker, by naming its version ID. The newest real version becomes current again and the file reappears, same name, same contents, same metadata. Nothing is copied or rebuilt.
Jake: "So deleting a file just puts a 'sold' sticker on the box, and the box stays on the shelf?"
Ethan: "Exactly. And to un-sell it, you peel off the sticker. You don't build a new box. The one thing you mustn't do is throw the box away while you're looking for the sticker."
Three behaviors that surprise people
- Deleting again adds another marker. If the current version of a name is already a delete marker, a plain delete, such as
aws s3 rmrun a second time, does not remove anything. S3 adds a second delete marker on top. Only a delete that names the marker's version ID removes it, and a file deleted twice needs both of its markers removed before it reappears. - The S3 console's "Restore" is a different thing. In S3, "restore" means bringing an archived object back from the S3 Glacier Flexible Retrieval or S3 Glacier Deep Archive storage classes, or from Intelligent-Tiering's archive tiers. It has nothing to do with deleted files, which is why it is grayed out for them.
- To undelete in the console, you type "permanently delete." It sounds terrifying. What you are permanently deleting is the marker, not your file.
Which deletes can be undone, and which cannot
Whether a delete is reversible depends on how it was made and on the bucket's versioning state. This table is the whole rule:
| What deleted the file | Versioning enabled | Versioning suspended | Never enabled |
|---|---|---|---|
aws s3 rm, console Delete with versions hidden, an SDK delete with no version ID | Undoable: adds a delete marker | Undoable only for versions with a real ID; a null version is removed | Permanent |
aws s3 sync --delete | Undoable: adds delete markers | Same as the row above | Permanent |
Lifecycle Expiration on current versions | Undoable: adds delete markers | Adds a null delete marker, replacing any null version | Permanent |
| A delete that names a version ID, or console Delete with versions shown | Permanent for that version | Permanent for that version | Not applicable |
Lifecycle NoncurrentVersionExpiration | Permanent, after the days you set | Permanent, after the days you set | Not applicable |
| Overwriting with a new upload | Undoable: the old content becomes a noncurrent version | The old version is kept only if its ID was not null | Permanent |
The pattern is simple. Anything that deletes "by name" is reversible in a versioned bucket; anything that deletes "by version ID" is not, and that includes deleting from the console while Show versions is switched on. During a recovery, only ever delete rows whose type is Delete marker.
The safe recovery order
Panic makes people run commands in the wrong order. This sequence keeps every option open until the files are back:
- Stop whatever is deleting. Disable the sync job, pause the script, or fix the lifecycle rule first, or it will add fresh delete markers while you are removing the old ones.
- Check versioning with
get-bucket-versioning, and list the delete markers for the affected prefix to prove the data is there. - Check the clock with
get-bucket-lifecycle-configuration, so you know how many days the hidden versions have left. - Write down the time of the accident from the markers'
LastModifiedvalues, so you restore only what this accident deleted. - Dry-run the restore, compare the count with what you expect, then run it for real.
- Verify: no current delete markers left under the prefix, the object count back where it was, and a few files opened and checked.
Notice what is missing from that list: anything that deletes versions. A recovery never needs to permanently delete data, so if a command you are about to run says Versions where it should say DeleteMarkers, stop and read it again.
Undelete one file
In the S3 console
- Open the bucket in the S3 console.
- Turn on the Show versions switch (labeled List versions in some views) above the object list. Deleted files now appear, each with a row whose type is Delete marker.
- Tick the check box next to the delete marker row only, not the rows of the real versions below it. If a file shows two or more delete markers above its newest version, tick all of them.
- Choose Delete, type
permanently deletein the confirmation box, and choose Delete objects. - Turn Show versions off again. The file is back in the normal list.
The console cannot undelete a whole folder in one go. For folders and anything more than a handful of files, use the command line below.
With the AWS CLI
First get the ID of the marker that is hiding the file, then delete exactly that version:
aws s3api list-object-versions --bucket shop-photos --prefix photos/a.jpg \
--query 'DeleteMarkers[?IsLatest==`true`].VersionId' --output text
aws s3api delete-object --bucket shop-photos --key photos/a.jpg --version-id PASTE_THE_ID_HERE
The reply includes "DeleteMarker": true and the version ID you named, which confirms that the marker, not the data, was removed. Download the file to check: aws s3 cp s3://shop-photos/photos/a.jpg . brings back the last version that existed before the delete.
If the file is still missing after that, it was deleted more than once: removing the top marker exposed an older one underneath. Run the same two commands again, and repeat until the download works. The script below handles that case automatically.
Watch the --prefix: it matches the start of the name, so photos/a.jpg also matches photos/a.jpg.bak. The IsLatest==`true` filter keeps only markers that are currently hiding a file. A key can carry several markers if it was deleted more than once; only the newest one matters.
Undelete thousands of files safely
When a whole folder or bucket was emptied, removing markers one by one is far too slow. S3 has a DeleteObjects call that removes up to 1,000 keys or versions per request, which makes a bulk undelete fast. The script below wraps it with the safety checks a real recovery needs:
- It removes only delete markers, never a real version of a file, so it cannot destroy data.
- It clears the whole stack of markers above a file's newest real version, so a file deleted twice comes back too.
- It can be limited to a folder with
--prefix, and to markers created after a moment with--since, so files someone deleted on purpose last month stay deleted. - It has a dry run that counts and shows what it would restore.
- It sends batches of at most 1,000, so it works for 50 files or 5 million.
- It reads keys from JSON, so names with spaces or odd characters are handled correctly.
import argparse
from collections import defaultdict
from datetime import datetime, timezone
import boto3
p = argparse.ArgumentParser(description="Undelete S3 objects by removing the delete markers on top of them.")
p.add_argument("--bucket", required=True)
p.add_argument("--prefix", default="")
p.add_argument("--since", help="only markers created at or after this UTC time, e.g. 2026-10-12T09:00")
p.add_argument("--dry-run", action="store_true")
a = p.parse_args()
s3 = boto3.client("s3")
since = datetime.fromisoformat(a.since).replace(tzinfo=timezone.utc) if a.since else None
# every version and delete marker of every key
history = defaultdict(list)
for page in s3.get_paginator("list_object_versions").paginate(Bucket=a.bucket, Prefix=a.prefix):
for v in page.get("Versions", []):
history[v["Key"]].append((v["IsLatest"], v["LastModified"], False, v["VersionId"]))
for m in page.get("DeleteMarkers", []):
history[m["Key"]].append((m["IsLatest"], m["LastModified"], True, m["VersionId"]))
markers, files = [], []
for key, items in history.items():
items.sort(reverse=True) # current version first, then newest to oldest
top = []
for latest, when, is_marker, vid in items:
if not is_marker:
break # reached real data: the markers above it are what hides the file
if since and when < since:
top = [] # deleted before the accident: leave it deleted
break
top.append({"Key": key, "VersionId": vid})
else:
top = [] # only markers, no data underneath: nothing to restore
if top:
files.append(key)
markers += top
print(f"{len(files)} files to restore ({len(markers)} delete markers) under '{a.prefix}'")
if a.dry_run:
for k in files[:5]:
print(" would restore", k)
raise SystemExit
failed = 0
for i in range(0, len(markers), 1000): # DeleteObjects takes at most 1,000 keys
r = s3.delete_objects(Bucket=a.bucket, Delete={"Objects": markers[i:i + 1000], "Quiet": True})
for e in r.get("Errors", []):
print(" failed:", e["Key"], e["Code"])
failed += 1
print(f"removed {len(markers) - failed} delete markers; {len(files)} files should be back")
Save it as undelete.py, install boto3 (pip install boto3, inside a virtual environment on Kali and other recent Linux systems), and run it in two steps:
python3 undelete.py --bucket shop-photos --prefix photos/ --since 2026-10-08T22:00 --dry-run
python3 undelete.py --bucket shop-photos --prefix photos/ --since 2026-10-08T22:00
The dry run prints how many files it found, how many delete markers hide them, and the first five names. If the count matches what was lost, run it again without --dry-run. The --since time is in UTC; take it from the LastModified column of the check in the first section, a few minutes before the first delete. The script uses your normal AWS credentials and Region settings; add AWS_PROFILE=... in front of the command for another profile.
That is what Ethan ran on Friday morning. The dry run found 4,218 files to restore, all marked within the same minute on Thursday night. The real run took five batches and about a minute, and the folder was whole again before the shop opened.
Check that everything came back
Three quick checks turn "it seemed to work" into proof. First, the check query from the top of this page should now list only files that were deleted on purpose before the accident, if any. A line that just says None is an empty page of results, not a file:
aws s3api list-object-versions --bucket shop-photos --prefix photos/ --query 'DeleteMarkers[?IsLatest==`true`].Key' --output text
Second, count the visible objects and compare the total with the dry run's number, or with the count you remember:
aws s3 ls s3://shop-photos/photos/ --recursive --summarize | tail -2
The last two lines read Total Objects: and Total Size:. Third, download two or three files from different parts of the folder and open them. A photo that opens is the only test a customer cares about.
How fast can it go?
S3 handles at least 3,500 write requests (PUT, COPY, POST and DELETE) per second for each prefix, and each DeleteObjects call counts its keys against that. The script sends one batch at a time, which stays well inside that limit for most recoveries. For tens of millions of objects, split the work by prefix and run several copies side by side, one per prefix.
Why the popular one-line restore can hurt you
Search for a bulk undelete and you will meet this answer, copied into countless notes and scripts. It feeds the list of delete markers straight into delete-objects:
aws s3api delete-objects --bucket shop-photos --delete "$(aws s3api list-object-versions \
--bucket shop-photos --prefix photos/ --output json \
--query '{Objects: DeleteMarkers[?IsLatest==`true`].{Key:Key,VersionId:VersionId}}')"
That version, with the IsLatest filter and a --prefix, does work for a small folder. The versions people copy are often riskier, in four ways:
- The next command in the same well-known answer deletes every real version. The answer was written for emptying a bucket, and right below "delete DeleteMarkers at once" it shows the same pattern with
Versions[]instead ofDeleteMarkers[]. Run that one during a rescue and every hidden copy of every file is permanently destroyed. Copy only the line you need. - A big list may not even reach S3. The whole list is pasted into one command-line argument, and Linux allows at most 128 KiB in a single argument. With 1,200 deleted files with short names, and again with 2,500, bash refused to run the command at all, with
Argument list too long, before any request was sent. In practice the shell gives up at around a thousand files. - More than 1,000 markers breaks it, even when they fit.
DeleteObjectsaccepts at most 1,000 keys per request. Any code that sends more, from the CLI or an SDK, gets the whole call rejected withMalformedXML: The XML you provided was not well-formed or did not validate against our published schema, and nothing is restored. - Zero markers breaks it too. Run it a second time, after everything is already restored, and the list is empty. The CLI then stops before sending anything, with
Parameter validation failed: Invalid type for parameter Delete.Objects, value: None; SDK code that sends an empty list getsMalformedXMLfrom S3 instead. Both look alarming and both mean nothing is left to do. - Files deleted twice stay hidden. It removes only the top marker of each file, so anything deleted more than once is still missing afterward, and nothing tells you so.
- Without filters, it brings back everything ever deleted. Versions of the command with no
--prefixand no date check resurrect every file anyone ever deleted from the bucket, including the ones that were meant to go.
Older answers also parse --output text with awk, which splits on spaces, so a file called repair 2.jpg becomes two broken names. The Python script above reads JSON and has none of these problems.
Restore a deleted folder
S3 has no real folders. A "folder" in the console is just a shared beginning of names, such as photos/2026/, and it appears whenever at least one object has that beginning. So you never restore the folder itself; you restore the files inside it, and the folder reappears on its own.
With the script, that is simply a prefix that ends in a slash:
python3 undelete.py --bucket shop-photos --prefix photos/2026/ --dry-run
Include the trailing slash. Without it, photos/2026 also matches photos/2026-old/ and photos/2026.zip. And remember that the S3 console cannot undelete folders, so this is a job for the command line or an SDK.
Roll back a file that was overwritten, not deleted
Sometimes the file is still there but wrong: a new upload replaced a good photo or a working configuration file. With versioning on, the old content is a noncurrent version of the same name. There are two ways to bring it back.
The safe way: copy the old version over itself. This makes a new current version with the old content and keeps every version, including the bad one, in case you need it later:
aws s3api list-object-versions --bucket shop-photos --prefix photos/a.jpg \
--query 'Versions[].[VersionId,LastModified,Size,IsLatest]' --output text
aws s3api copy-object --bucket shop-photos --key photos/a.jpg \
--copy-source "shop-photos/photos/a.jpg?versionId=PASTE_THE_OLD_ID"
The first command lists every version with its date and size, newest first, so you can pick the right one. The second creates a fresh current version from it.
The quick way: delete the bad current version. aws s3api delete-object with the bad version's ID removes it permanently, and the previous version becomes current again. It is faster, but there is no way back if you picked the wrong ID, so the copy method is better whenever you are unsure. For many files at once, S3 Batch Operations can run copies from a list; copy older versions before newer ones if the order matters to you.
The clock: lifecycle rules that permanently delete old versions
Before you plan a slow, careful recovery, find out whether you have time. A lifecycle rule on the bucket can include a NoncurrentVersionExpiration action, which permanently deletes noncurrent versions a set number of days after they became noncurrent. Your deleted files are exactly that: noncurrent versions under a delete marker. When the days run out, they are gone for good, and no support ticket brings them back.
aws s3api get-bucket-lifecycle-configuration --bucket shop-photos
If the reply says NoSuchLifecycleConfiguration, there is no rule and no clock. If it shows a rule with NoncurrentVersionExpiration and NoncurrentDays, count the days since the delete. If you are close, restore first and investigate later, or change the rule so it no longer applies to the affected files while you work. A NewerNoncurrentVersions setting in the same rule keeps a number of newer noncurrent versions regardless of age; both conditions must be met before S3 deletes a version.
Lifecycle rules can also cause the problem. In a versioned bucket, an Expiration action on current versions does not delete the data; it adds a delete marker to every matching object, exactly like a manual delete. A rule meant for a logs/ folder that was accidentally applied to the whole bucket can therefore "delete" everything overnight. The recovery is the same as above. Fix or remove the rule first, or it will add new markers again on its next run, which can take a day or more to come around.
One harmless rule worth knowing: "Delete expired object delete markers" removes a marker only when no versions remain behind it, so it never hides data and never blocks a recovery.
Jake: "So the bucket can have a shredder on a timer, and the timer started when I deleted everything?"
Ethan: "Right. Ours is set to 30 days for old versions, to keep the bill down. That's why I asked about versioning first and the timer second. You had 29 days to spare. Some people find out on day 31."
What a recovery costs
Almost nothing, which surprises people who expect a "data recovery" bill. In US East (N. Virginia), the S3 price list published September 28, 2026 gives these rates:
| What the recovery does | Price | For Jake's 4,218 photos |
|---|---|---|
| Remove delete markers (DELETE requests) | Free | $0 |
| List versions (LIST requests, up to 1,000 entries per page) | $0.005 per 1,000 requests | About 5 to 10 requests: a fraction of a cent |
| Roll back by copying an old version (COPY requests) | $0.005 per 1,000 requests | Not needed for an undelete |
| Keep the versions while they exist (S3 Standard storage) | $0.023 per GB-month, first 50 TB | About 8.4 GB at roughly 2 MB a photo: about $0.19 a month |
The real cost of versioning is the storage line, and it is the same whether you restore or not: every hidden version is billed at its storage class's normal rate until a lifecycle rule or a person removes it. That is why the prevention section pairs versioning with a 30-day noncurrent-version rule. Old versions kept forever are where versioning bills grow; old versions kept for a month are cheap insurance. The delete markers themselves cost almost nothing: each is billed only for the length of its key name, in S3 Standard.
Objects in other storage classes keep their class when they become noncurrent, so a deleted file in S3 Standard-IA or Glacier Instant Retrieval is billed at that class's rate. Our guide to S3 storage classes has those rates and when each one makes sense.
If versioning was off: what you can still try
This is the hard answer: S3 cannot undelete an object from a bucket that did not have versioning enabled at the time of the delete. There is no recycle bin and no hidden copy that AWS support can restore. Turning versioning on now protects future deletes only. Here is what is still worth checking:
- AWS Backup. If anyone set up AWS Backup for S3 in your account, recovery points may hold the files. Look in the AWS Backup console under protected resources. Our guides to what AWS Backup is and what a restore costs explain the options.
- Replication. If the bucket replicated to another bucket, the copies may still be there. Replication requires versioning, so this applies to buckets that once had it on.
- The source of the upload. The laptop, server, build job or app that uploaded the files may still have them. For a photo folder synced from one machine, that machine is often the best backup you have.
- Other copies. A CloudFront distribution in front of the bucket may still be serving cached copies for a while; download what you can from it now. Data exports, data lakes or another Region may hold copies too.
- Who did it. If CloudTrail data events for S3 were turned on, they record each
DeleteObjectcall with the identity and time, which tells you what to restore from elsewhere and which script or person to fix.
A deleted bucket, not just its files
A deleted bucket cannot be restored by AWS. And because S3 only deletes a bucket once it is completely empty, every version and delete marker inside it was gone before the bucket itself went. The steps above, backups, replicas and the original source, are the only places the data can still be. Two practical notes. If you need the same name back, create a new bucket with that name as soon as possible: bucket names are shared across all AWS accounts, and once yours is deleted, someone else can take it and start receiving requests meant for you. And if the bucket was emptied but not yet deleted, stop: an empty bucket with versioning still holds its noncurrent versions and markers until they are removed too, so check with list-object-versions before anyone runs delete-bucket.
Then turn versioning on, today:
aws s3api put-bucket-versioning --bucket shop-photos --versioning-configuration Status=Enabled
Make the next accident survivable
Jake's photos came back because of one setting Ethan chose on the first day. Here is the full set, from cheapest to strongest:
| Protection | What it does | Cost and catch |
|---|---|---|
| Versioning | Every delete and overwrite keeps the old data, so any file can be undeleted or rolled back | You pay for every version you keep; pair it with a lifecycle rule |
| Lifecycle for noncurrent versions | Permanently deletes old versions after a number of days you choose, such as 30 | Sets the length of your undo window; shorter is cheaper but riskier |
| IAM deny on version deletes | A policy that denies s3:DeleteObjectVersion to everyday users and scripts, so they can hide files but never destroy them | Free; someone with more rights still can |
| MFA Delete | Permanently deleting a version, or changing the versioning state, needs a code from the bucket owner's MFA device | Free, but awkward for automation |
| S3 Object Lock | Versions cannot be deleted or overwritten until a retention date, even by administrators in compliance mode | The strongest; plan retention periods carefully |
| AWS Backup or replication | A separate copy in another place, with its own retention | Extra storage and request costs |
For most small businesses, versioning plus a 30-day noncurrent-version rule plus an IAM deny on s3:DeleteObjectVersion covers the realistic accidents, a bad sync, a wrong script, a fat-fingered delete, for very little money. In the console, versioning is under the bucket's Properties tab, Bucket Versioning, Edit. Here is the lifecycle rule:
aws s3api put-bucket-lifecycle-configuration --bucket shop-photos --lifecycle-configuration '{
"Rules": [{
"ID": "keep-old-versions-30-days",
"Status": "Enabled",
"Filter": {},
"NoncurrentVersionExpiration": {"NoncurrentDays": 30},
"Expiration": {"ExpiredObjectDeleteMarker": true}
}]
}'
That rule keeps every deleted or overwritten file recoverable for 30 days, then deletes the old copy and tidies up markers with nothing left behind them. And as our guide to aws s3 sync --delete explains, a --dryrun before any sync with --delete shows exactly what would be removed, which would have saved Jake his Thursday night.
Troubleshooting by symptom
The files are not in the bucket, but list-object-versions shows them
They are hidden behind delete markers. Remove the current markers with the script or the one-file command and they reappear.
"Restore" is grayed out in the S3 console
That button is for archived objects in the Glacier storage classes, not for deleted files. Use Show versions and delete the delete marker instead.
I deleted the file again and it is still gone
A second plain delete adds a second delete marker. Remove the current marker by its version ID; repeat if the name was deleted several times and you need an earlier version to surface.
MalformedXML, "Argument list too long" or "Parameter validation failed" from delete-objects
More than 1,000 keys were sent in one request, or SDK code sent an empty list because nothing is left to restore. From the CLI, an empty list fails earlier with "Parameter validation failed", and a long list fails with "Argument list too long." Use the batching script, which handles all three.
I removed the delete marker and the file is still missing
The file was deleted more than once, so an older delete marker sits underneath. Remove it too, or run the script, which clears every marker above the newest real version.
AccessDenied when removing a delete marker
Removing a marker is a delete of a specific version, which needs s3:DeleteObjectVersion, not just s3:DeleteObject. MFA Delete or Object Lock settings can also block it. Our guide to S3 delete Access Denied walks through every layer.
list-object-versions prints nothing
Check the bucket name, the prefix (names are case-sensitive and the prefix matches from the start), and your credentials and Region. If all are right, the files were deleted before versioning was on, or a lifecycle rule has already removed their versions.
The file came back but with old content
A delete marker hides the newest version; removing it restores that newest version. If the file was overwritten before it was deleted, list its versions and copy the one you want over itself.
Files keep disappearing again overnight
A lifecycle Expiration rule is adding delete markers on a schedule, or a sync job with --delete runs from an empty source. Fix the rule or the job first, then restore.
My app still gets NoSuchKey after the restore
Check the exact name the app requests against the restored name, including case and any leading slash. Our NoSuchKey guide lists every cause in order.
Restore deleted S3 files: frequently asked questions
Can I recover a deleted file from Amazon S3?
Yes, if the bucket had versioning enabled when the file was deleted. The delete only added a delete marker; remove that marker and the file reappears unchanged. If versioning was off, S3 cannot undelete it, and only a backup, a replica or the original source can help.
How do I restore a deleted object in a versioned S3 bucket?
Find the object's current delete marker with aws s3api list-object-versions, then delete that marker with aws s3api delete-object and its version ID. In the console, turn on Show versions, select the delete marker, choose Delete and type permanently delete.
What is an S3 delete marker?
A placeholder S3 adds when you delete an object in a versioned bucket without naming a version. It has the object's key and its own version ID but no data, and it makes S3 answer 404 for that key while every earlier version stays stored.
How do I remove delete markers from multiple S3 objects at once?
List the current delete markers with list-object-versions and pass them to delete-objects in batches of at most 1,000, or use a short boto3 script that paginates, filters by prefix and date, and supports a dry run. A single delete-objects call cannot take more than 1,000 keys.
How do I restore a deleted folder in S3?
Restore the objects under the folder's prefix by removing their current delete markers, for example with a script and --prefix photos/2026/. The folder reappears once objects with that prefix exist again. The S3 console cannot undelete folders.
Can I recover deleted S3 files without versioning?
Not from S3 itself. Check AWS Backup recovery points, a replication destination, the machine or job that uploaded the files, and any other copies. Then enable versioning so future deletes can be undone.
Can I restore a deleted S3 bucket?
No. AWS cannot restore a deleted bucket, and a bucket can only be deleted once it is completely empty, so its data was already gone. Recover the files from a backup, a replica or the original source, and recreate the bucket name quickly, because another account can claim it.
How do I restore a previous version of an S3 object?
List versions with aws s3api list-object-versions, then copy the version you want over the same key with aws s3api copy-object and --copy-source bucket/key?versionId=ID. That makes it the current version while keeping every other version.
Why is Restore grayed out for my deleted S3 file?
In S3, Restore means retrieving an archived object from the Glacier storage classes or Intelligent-Tiering archive tiers. It does not undelete. For a deleted file, show versions and delete its delete marker instead.
Why is my S3 file still deleted after removing the delete marker?
It was deleted twice, so two delete markers were stacked on it. Removing the newest one makes the older marker current, and the file stays hidden. Remove every marker above the newest real version, or use a script that clears the whole stack.
Does deleting a file again in S3 restore it?
No. If the current version is already a delete marker, a plain delete adds another delete marker. Only a delete that names the marker's version ID removes it and brings the file back.
How long can I recover deleted S3 objects?
For as long as their noncurrent versions exist. With no lifecycle rule, that is forever. A NoncurrentVersionExpiration rule permanently deletes them after the number of days it sets, so check get-bucket-lifecycle-configuration first.
Do deleted S3 objects still cost money?
Yes, with versioning on. The noncurrent versions behind a delete marker are stored and billed at normal S3 rates, and the marker itself adds a tiny charge based on its key name length. A lifecycle rule for noncurrent versions keeps that cost in check.
Why does delete-objects return MalformedXML?
The request had more than 1,000 keys, or an empty list sent from SDK code. DeleteObjects accepts 1 to 1,000 keys per call. Split large restores into batches and skip the call when nothing is left; the CLI reports an empty list as a parameter validation error instead.
What permission is needed to remove a delete marker?
s3:DeleteObjectVersion on the object, because removing a marker deletes a specific version. If MFA Delete is enabled on the bucket, permanently deleting a version also needs the bucket owner's MFA code.
Can I turn off versioning on an S3 bucket?
No. Once enabled, a bucket can only be suspended, never returned to unversioned. Suspending stops new versions from being kept but leaves existing versions in place, still recoverable and still billed.
How do I stop S3 files from being deleted by accident?
Enable versioning, add a lifecycle rule that keeps noncurrent versions for 30 days or more, deny s3:DeleteObjectVersion to everyday users and scripts, and consider MFA Delete, Object Lock or AWS Backup for critical data.
Does aws s3 rm delete files permanently in a versioned bucket?
No. aws s3 rm, aws s3 sync --delete and a console delete all add delete markers in a versioned bucket, so the data can be restored. Only deletes that name a version ID, or lifecycle rules for noncurrent versions, remove data permanently.
By the time the first customer walked in on Friday, all 4,218 photos were back, and Jake had learned three new words: versioning, delete marker and dry run. Ethan added two things to the shop's setup that afternoon: a policy that lets the laptop hide files but never destroy them, and a --dryrun in the sync command, so the screen shows what would happen before anything does. Jake printed the undelete script and pinned it inside the stockroom door, next to the fire extinguisher. "Same idea," he said. "You hope you never need it."
📌 If you keep one line from this page
With versioning on, a deleted S3 file is only hidden: remove the delete marker, and it is back.
Check the lifecycle clock first, restore in batches of 1,000 with a dry run, and never run the Versions[] command.
Revision note. Written October 12, 2026, for everyone who has watched a folder empty itself late at night. As the saying goes, an ounce of prevention is worth a pound of cure; one versioning switch is that ounce.