S3 Delete Bucket Access Denied: Hidden Versions and Locks
If Amazon S3 returns AccessDenied while you are deleting a bucket, first identify which delete failed: an object, an object version, or the bucket itself. A versioned bucket can look completely empty in the normal Objects view and still contain old versions and delete markers that prevent deletion. The counterintuitive part is that pressing Delete on a versioned object can add another item—the delete marker—instead of removing the stored version. Object Lock, legal holds, event holds, MFA Delete, access points, and policy denies can then add separate blockers.
A bucket deletion problem is easier once you split it into layers. S3 can refuse an object-version deletion because Object Lock protects that version, while the final DeleteBucket request can fail for an entirely different reason such as an explicit policy deny. Treating both errors as “S3 permissions are broken” sends you in circles.
The commands use Jake's bucket, biscuit-old-invoices, and one stuck object, invoices/2024/march.pdf; swap in your own names, and treat VERSION-ID as a placeholder. Copy real version IDs from S3 rather than typing them from memory, because Object Lock and permanent deletion apply to specific versions.
First identify which S3 delete operation is failing
Do not start by editing IAM. Start with the error line. The words immediately before AccessDenied tell you which API operation failed.
If the AWS CLI says the error occurred when calling DeleteObject, the failure is at object or object-version level. If it says DeleteBucket, the data cleanup might already be complete and the final bucket-level request is what S3 is denying.
There is also a different failure worth separating: BucketNotEmpty. That means the final delete reached S3, but the bucket still contains something that must be removed first. In a versioned bucket, those remaining things can be invisible in the ordinary Objects view.
| Error or symptom | What it narrows down | Check next |
|---|---|---|
AccessDenied on DeleteObject | Object or object-version deletion is blocked. | Version permission, Object Lock, legal hold, event hold, MFA Delete. |
AccessDenied on DeleteBucket | The final bucket deletion request is not authorized. | s3:DeleteBucket, bucket policy, SCP, RCP, caller identity. |
BucketNotEmpty | Objects, versions, or delete markers still remain. | list-object-versions. |
BucketHasAccessPointsAttached | An S3 Access Point or Multi-Region Access Point dependency remains. | Delete same-account attached access points first. |
🙋♂️ Jake's Reality Check
"I can open the bucket, download a file, and even delete the visible object. Why can't I delete the bucket?"
Because those are different permissions and different operations. Reading an object does not imply permission to delete its historical versions, bypass retention, or delete the bucket itself.
Run this five-minute diagnosis before changing IAM
This sequence separates the most common causes with the least destructive work. Run it with the same AWS profile or assumed role that you intend to use for deletion.
- Confirm your caller identity. Make sure the CLI is using the account and role you think it is.
- Check bucket versioning. Versioning can be enabled or suspended, and old versions remain after suspension.
- Check Object Lock configuration. If Object Lock is enabled, individual versions can carry fixed or variable retention and legal holds.
- List object versions and delete markers. This reveals data that the normal Objects view hides.
- Retry only the failing operation. If one version fails, inspect that version. If only
DeleteBucketfails, investigate bucket-level authorization.
aws sts get-caller-identity
aws s3api get-bucket-versioning \
--bucket biscuit-old-invoices
aws s3api get-object-lock-configuration \
--bucket biscuit-old-invoices
aws s3api list-object-versions \
--bucket biscuit-old-invoices
The version listing is the key command. Look for two collections: Versions and DeleteMarkers. If either collection still contains entries, the bucket still has version history that can prevent the bucket itself from being deleted.
If list-object-versions returns AccessDenied, solve that visibility problem before attempting bulk deletion. A cleanup process that cannot see version IDs cannot safely reason about which stored versions still exist.
If get-object-lock-configuration shows that Object Lock is enabled, do not assume the bucket-level default tells the full story. Retention is associated with object versions. Older versions can have settings that differ from the bucket's current default.
Why a versioned S3 bucket looks empty but is not empty
Versioning changes what “delete” means. In an unversioned bucket, deleting an object normally removes that object. In a versioning-enabled bucket, a simple delete request that does not name a specific version normally creates a delete marker instead of permanently erasing a historical version.
A delete marker is a special version that becomes current and makes the key behave as though the object is gone. The previous versions can still exist underneath it.
Imagine orders.csv has version A, version B, and version C. You delete orders.csv without specifying a version ID. S3 adds delete marker D. The normal Objects view stops showing orders.csv, but A, B, C, and D still matter when you are trying to empty the bucket completely.
Now scale that to a backup bucket where the same keys were overwritten every night for three years. A screen showing zero current objects can hide a large version history.
Suspending versioning does not erase that history. It changes how later writes and deletes are handled, but existing version IDs remain until they are explicitly removed or a lifecycle configuration removes eligible versions.
Ethan explains it to Jake this way: “Turning versioning off is like deciding not to save new drafts. It doesn't shred the drafts already in the drawer.”
| Versioning state | Simple delete behavior | What must be gone before bucket deletion |
|---|---|---|
| Never enabled | The object is permanently deleted. | All objects. |
| Enabled | A delete without version ID creates a delete marker for a versioned object. | Every object version and every delete marker. |
| Suspended | Null-version behavior applies while old version IDs can still remain. | Null/current objects, historical versions, and delete markers. |
This is also why aws s3 rb s3://biscuit-old-invoices --force is not the right hammer for a versioned bucket. The high-level command can remove current objects in an unversioned bucket, but it does not clear historical versions from a versioning-enabled bucket. The final bucket deletion then fails because the bucket is still not empty.
Console route: expose the versions before you empty the bucket
Open the Amazon S3 console and choose General purpose buckets. Choose the bucket you want to remove.
First open Properties. Find Bucket Versioning. If the bucket is versioning-enabled or versioning-suspended, expect historical versions to be part of the cleanup.
On the same Properties page, find Object Lock. If it is enabled, note the default retention configuration. The default matters for new versions, but it is not a substitute for inspecting a specific version that refuses deletion.
Return to the Objects tab and enable Show versions. The list changes from “what is current?” to “what versions and markers actually exist?”
If you find a version that cannot be deleted, choose that version and inspect its Object Lock information. Check the retention mode, retain-until date, legal hold, and—on buckets using variable retention—the event-hold state and duration.
Do not select hundreds of protected versions and repeatedly press Delete hoping one attempt will “take.” Pick one failing version and understand why it is protected. Once that cause is clear, you can determine whether it applies to the rest of the bucket.
⚠️ What this actually breaks
Permanently deleting a specific version destroys that version. A delete marker can be removed later; a permanently deleted version is not sitting underneath waiting to be restored.
If your goal is only to stop using the bucket rather than destroy the data, do not treat “empty and delete” as the automatic final step. A general purpose bucket name lives in a shared global namespace. Deleting the bucket can allow another AWS account to create that name later.
CLI route: permanently delete versions and delete markers
Use the lower-level s3api commands when a bucket has version history. Start with a listing:
aws s3api list-object-versions \
--bucket biscuit-old-invoices
A single permanent version delete looks like this:
aws s3api delete-object \
--bucket biscuit-old-invoices \
--key "invoices/2024/march.pdf" \
--version-id "VERSION-ID"
The same command shape deletes a delete marker when you provide the marker's version ID.
Removing a delete marker can make an older version current again. That surprises people because a key that had disappeared can suddenly show up in the normal Objects view after the marker is removed. Nothing new was uploaded; an older stored version simply became visible again.
For larger cleanups, delete-objects can delete multiple version IDs in one request. A practical pattern is to create a JSON document containing the exact keys and version IDs you want to remove, inspect it, and then pass it with file://.
{
"Objects": [
{
"Key": "orders/2026-09.csv",
"VersionId": "EXAMPLE-VERSION-ID"
},
{
"Key": "logs/app.log",
"VersionId": "EXAMPLE-DELETE-MARKER-ID"
}
],
"Quiet": false
}
aws s3api delete-objects \
--bucket biscuit-old-invoices \
--delete file://delete.json
Using a file has another advantage on Windows: it avoids turning an S3 problem into a PowerShell or Command Prompt quoting problem. If your shell cannot parse nested JSON, S3 never receives the request, so changing IAM cannot fix it.
After every batch, list the versions again. Large listings can be paginated. “One delete request succeeded” is not the same as “the bucket contains no more versions.” Continue until the version listing shows no object versions and no delete markers you intend to remove.
✅ Why this is the one to use
During destruction of a versioned bucket, treat list-object-versions as the truth. A normal object listing tells you what is current; a version listing tells you what still exists.
Object Lock: why a permanent version delete returns 403
S3 Object Lock protects object versions from deletion using write-once-read-many protection. Object Lock works with versioning, so the protected unit is a specific object version.
When an object version is protected by an Object Lock retention period or legal hold, a permanent delete that names the protected version ID can return 403 Access Denied.
A simple delete without a version ID behaves differently. S3 can return success and add a delete marker as the newest version. The protected historical version still exists. That difference explains one of the strangest-looking Object Lock cases: “Delete works, but permanent delete fails.” They are not the same operation.
Check the bucket-level configuration:
aws s3api get-object-lock-configuration \
--bucket biscuit-old-invoices
Then inspect the particular version that failed:
aws s3api get-object-retention \
--bucket biscuit-old-invoices \
--key "invoices/2024/march.pdf" \
--version-id "VERSION-ID"
aws s3api get-object-legal-hold \
--bucket biscuit-old-invoices \
--key "invoices/2024/march.pdf" \
--version-id "VERSION-ID"
There are three separate protection questions to answer:
- Is the version under Governance or Compliance retention?
- Is a legal hold set to
ON? - Does the version use variable retention with an event hold?
Any one of those can change what deletion is allowed to do.
Governance retention: permission plus an explicit bypass
Governance mode is designed to stop ordinary users from removing protected versions while allowing specifically authorized principals to override the retention when your organization's policy permits it.
The principal needs s3:BypassGovernanceRetention, and the request must explicitly ask to bypass Governance retention.
aws s3api delete-object \
--bucket biscuit-old-invoices \
--key "invoices/2024/march.pdf" \
--version-id "VERSION-ID" \
--bypass-governance-retention
Giving a role the permission does not mean every delete silently bypasses retention. The delete request must carry the bypass intent.
This also explains a console-versus-CLI difference that can look mysterious. The S3 console includes the Governance bypass request header when the signed-in principal has the bypass permission. A CLI command without --bypass-governance-retention is not making the same request.
Governance bypass does not switch off an independent legal hold. If a version has legal hold ON, deal with that protection separately.
🙋♂️ Jake's Reality Check
"Wouldn't it be faster to add s3:*, delete everything, then remove the policy?"
That can give you more power without solving the real blocker. An explicit deny, legal hold, Compliance retention, MFA Delete, or an Organizations policy can still stop the operation.
Compliance retention: the case IAM cannot override
Compliance mode is deliberately stricter than Governance mode. While Compliance retention is active, the protected version cannot be permanently deleted. The retention period cannot simply be shortened by an administrator to make the cleanup convenient.
This protection also applies to the AWS account root user. Root access is not an emergency Object Lock bypass.
Inspect the exact version:
aws s3api get-object-retention \
--bucket biscuit-old-invoices \
--key "invoices/2024/march.pdf" \
--version-id "VERSION-ID"
If it shows COMPLIANCE and the effective retain-until date is still in the future, stop adding IAM permissions. IAM is not the missing piece.
When that date passes, check legal hold as well. A version can have a retention period and a legal hold at the same time. Expiry of one protection does not automatically clear the other.
⚠️ What this actually breaks
A teardown plan that assumes “the root user can always delete it” can be blocked until Compliance retention finishes. Build the infrastructure-removal date around the protected versions, not around the day somebody wants the CloudFormation or Terraform cleanup to finish.
Deleting the application stack also does not erase retention metadata from an object version. If the application that created the locked files no longer exists, the storage protection can still remain until its conditions are satisfied.
Legal holds: why an expired retention date may still not help
A legal hold protects an individual object version without a built-in expiration date. It remains until somebody with the required authorization explicitly removes it.
Check it with:
aws s3api get-object-legal-hold \
--bucket biscuit-old-invoices \
--key "invoices/2024/march.pdf" \
--version-id "VERSION-ID"
An active hold appears with Status set to ON.
If your records policy allows the hold to be removed and the caller has s3:PutObjectLegalHold, the CLI shape is:
aws s3api put-object-legal-hold \
--bucket biscuit-old-invoices \
--key "invoices/2024/march.pdf" \
--version-id "VERSION-ID" \
--legal-hold Status=OFF
Then inspect the retention state again. Turning a legal hold off does not cancel an independent Governance or Compliance retention period.
The reverse is equally important: a fixed retention date can expire while a legal hold remains on. If somebody tells you, “The date passed last week and I still get Access Denied,” check the legal hold before you rewrite the policy.
There is a process lesson here too. Technical permission to remove a legal hold is not the same as organizational approval to remove it. A legal hold might exist because of an audit or records requirement that sits outside the S3 team.
Event holds: the Object Lock change from September 8, 2026
🕐 What changed between versions
- Before September 8, 2026: Object Lock cleanup guidance mainly revolved around fixed retain-until dates and legal holds.
- On September 8, 2026: S3 added variable retention with event holds.
- Now: an object's retention can be tied to a future event, and releasing the event hold can start the configured retention duration.
Variable retention solves a different records problem from fixed retention. Sometimes you know that a file must remain immutable for a certain length of time after an event, but you do not know when that event will happen.
An event hold lets the object remain protected while the triggering event has not happened. When the hold is released, S3 establishes the final retention timing based on the configured duration.
Check a version through the same retention API:
aws s3api get-object-retention \
--bucket biscuit-old-invoices \
--key "invoices/2024/march.pdf" \
--version-id "VERSION-ID"
Variable retention can show an event hold state and an event-hold duration. The duration can be specified from 1 to 36,500 days or from 1 to 100 years.
If an authorized workflow releases the event hold, the object does not necessarily become deletable immediately. S3 fixes the retain-until date using the release time plus the configured duration, subject to any later minimum retain-until date that also applies.
For example, suppose a version has a 30-day event-hold duration. If the event hold is released on October 10, the object remains protected for that duration after release rather than becoming deletable on October 10.
A CLI pattern for releasing an event hold is:
aws s3api put-object-retention \
--bucket biscuit-old-invoices \
--key "invoices/2024/march.pdf" \
--version-id "VERSION-ID" \
--retention '{"Mode":"COMPLIANCE","EventHold":"OFF"}'
That command is not a general-purpose “unlock this object” shortcut. Use the mode and hold operation that match the actual version and your retention policy.
The practical troubleshooting lesson is simple: an older runbook that checks only legal hold and a fixed retain-until date can now miss variable retention. If the bucket uses Object Lock and was updated in late 2026, check event holds too.
MFA Delete: the version-delete requirement people forget
MFA Delete adds another protection around permanent deletion of versions. It is part of S3 Versioning behavior, not the same feature as Object Lock.
Check the bucket configuration:
aws s3api get-bucket-versioning \
--bucket biscuit-old-invoices
If MFA Delete is enabled, a versioned delete request that includes a version ID requires valid MFA information.
The CLI pattern is:
aws s3api delete-object \
--bucket biscuit-old-invoices \
--key "invoices/2024/march.pdf" \
--version-id "VERSION-ID" \
--mfa "MFA-DEVICE-SERIAL MFA-CODE"
There is a nasty bulk-delete edge case here. If a Multi-Object Delete request against an MFA Delete-enabled bucket contains versioned deletes and the request does not provide valid MFA, the entire request can fail.
That means one missing MFA requirement can make a large batch look like every object has a permission problem.
Do not confuse a bad or missing MFA value with a missing s3:DeleteObjectVersion permission. The symptom may still be a failed delete, but the remedy is different.
The permissions that matter—and the denies that beat them
Once you know that Object Lock and MFA are not the blocker, look at authorization. Do this by action rather than by asking whether the role has “S3 access.”
| Task | Permission to inspect | Why it matters |
|---|---|---|
| Delete ordinary object | s3:DeleteObject | Used for an ordinary object delete. |
| Delete a particular version | s3:DeleteObjectVersion | Required when a specific version ID is being permanently deleted. |
| Bypass Governance retention | s3:BypassGovernanceRetention | Allows an authorized bypass request against Governance-mode retention. |
| Read retention | s3:GetObjectRetention | Lets the troubleshooter inspect the protection on a version. |
| Read legal hold | s3:GetObjectLegalHold | Lets the troubleshooter see whether a hold is on. |
| Change legal hold | s3:PutObjectLegalHold | Needed when an authorized process removes a legal hold. |
| Delete bucket | s3:DeleteBucket | Controls the final bucket-level deletion. |
A common infrastructure-as-code failure is a role that has s3:DeleteObject but not s3:DeleteObjectVersion. The role can delete an ordinary current object yet fail when Terraform, CloudFormation cleanup code, or a script tries to permanently remove version IDs.
Adding the missing allow is only half the policy story. An explicit deny can still win.
For the final bucket deletion, inspect these policy layers:
- The IAM identity policy on the user or role.
- The bucket policy.
- A permissions boundary.
- A session policy on an assumed role.
- A VPC endpoint policy if the request reaches S3 through an endpoint.
- An AWS Organizations service control policy.
- An AWS Organizations resource control policy.
If the Access Denied message identifies an explicit deny and names a policy type, use that information. Do not compensate for a deny by attaching a larger Allow policy somewhere else.
A bucket policy can explicitly deny s3:DeleteBucket. Some service-created buckets intentionally use protections like this to make accidental teardown harder.
Console works, CLI fails: check the identity before the policy
When the S3 console works but the CLI fails, many people compare commands before comparing identities. Reverse that order.
Run:
aws sts get-caller-identity
Check the returned account ID and ARN. Your browser session might be using an IAM Identity Center permission set while your terminal still has credentials for a different IAM user or account.
If you use named profiles, make the profile explicit while troubleshooting:
aws s3api list-object-versions \
--bucket biscuit-old-invoices \
--profile production-admin
Also watch for --expected-bucket-owner. That option is useful because it stops a request when the bucket owner is not the account you expected. If the supplied account ID does not match the actual bucket owner, S3 can return 403 rather than letting your script continue against the wrong target.
That is a safety feature, not a strange permissions bug.
Cross-account cleanup deserves extra caution. If another account owns the bucket or controls an essential bucket policy, your local administrator role cannot manufacture authority that the owning account has not granted.
Access points can block the final bucket deletion
A general purpose bucket must not have same-account S3 Access Points or Multi-Region Access Points attached when you delete the bucket.
This is an easy dependency to miss because the object cleanup can be completely correct. You can remove every version and delete marker, then discover the bucket still cannot be deleted because an access point remains.
In the S3 console, open Access Points from the left navigation. Identify access points associated with the bucket and make sure applications no longer depend on them.
For an ordinary S3 Access Point, the CLI deletion uses S3 Control:
aws s3control delete-access-point \
--name ACCESS-POINT-NAME \
--account-id 123456789012
Remove the dependency first, then return to DeleteBucket.
Do not delete an access point merely because it exists. Check what uses it. An application may have been configured with the access point alias rather than the bucket name.
Stop writers before emptying the bucket
Sometimes the bucket really is empty—and then another service immediately writes a new object.
Before destructive cleanup, identify anything that can still deliver data to the bucket. That can include an application, backup process, log-delivery configuration, replication workflow, or another AWS service.
Elastic Load Balancing logs deserve special attention. If a load balancer still sends logs to the bucket, stop or redirect that delivery before deleting the bucket.
This is not merely about winning a race with a new log file. Bucket names for general purpose buckets exist in a shared global namespace. If you delete a bucket and another account later creates the same name, stale systems that continue sending requests to that old name can create an ugly ownership problem.
Static website buckets have a similar cleanup concern. If the bucket was part of a Route 53 static-website configuration, clean up the related DNS records and hosted-zone settings rather than treating S3 deletion as the whole teardown.
Ethan's advice to Jake is plain: “Turn off the tap before you remove the sink.”
It sounds obvious until the “tap” is a log-delivery setting configured two years ago by a different team.
Should you delete the bucket at all?
If your only goal is to stop storage charges or retire the workload, deletion is not always the safest final state.
A general purpose bucket name is globally unique in the shared namespace. Once you delete the bucket, another AWS account can potentially create that name. Old software, customer integrations, logging configurations, bookmarks, or DNS records can still point at it.
If preserving control of the name matters, emptying the bucket and retaining it can be safer than deleting it. You can then block unwanted bucket requests while keeping ownership of the name.
This is especially useful when the bucket name was embedded in:
- A public URL.
- A static website configuration.
- An old mobile or desktop application.
- A partner integration.
- A logging destination.
- A backup configuration.
- A script that nobody wants to admit still runs from a server under somebody's desk.
If none of those apply and you truly want to surrender the name, deletion is reasonable after the data and dependency checks are complete.
Worked example: 560 hidden items and one locked version
Jake's phone shop had an old bucket called jake-shop-backups. The current Objects view showed zero objects, so he expected to click Delete and be finished.
The version listing told a different story:
- 420 historical object versions.
- 139 ordinary delete markers.
- 1 more delete marker above a protected file.
- 1 version of
orders/2026-09.jsonunder Compliance retention.
That gives Jake 560 version-history entries to reason about before the bucket can become empty.
The protected version has an effective retain-until time of October 15, 2026 at 14:00 UTC. Jake starts cleanup on October 1.
His runbook is:
- Stop the backup job so no new versions arrive.
- Confirm his CLI identity with
get-caller-identity. - Delete the unprotected versions and delete markers.
- Re-run
list-object-versions. - Inspect the one remaining version's Object Lock state.
- Confirm it is under active Compliance retention rather than a missing IAM permission.
- Wait until that retention permits deletion.
- Delete the remaining version.
- Run
list-object-versionsagain. - Remove any same-account access point.
- Run
delete-bucket.
If the final command now returns AccessDenied, Jake no longer needs to investigate the old Compliance retention. That version is gone. The remaining failure is bucket-level authorization.
This separation saves time because one cleanup can contain several different kinds of refusal. Solving yesterday's blocker does not prove today's blocker has the same cause.
What this cleanup costs
As of October 1, 2026, S3 DELETE and CANCEL requests are free. The direct S3 request price for 1,000,000 DELETE requests is therefore $0.
That does not mean every teardown has a $0 storage impact.
Protected versions continue to occupy storage until they can actually be deleted. Some storage classes also have minimum storage durations. Deleting objects before those minimums are satisfied can produce prorated charges for the remaining minimum period.
| Item | Current rule | Teardown consequence |
|---|---|---|
| DELETE / CANCEL requests | Free | The delete requests themselves do not create a request charge. |
| S3 Standard-IA / One Zone-IA | 30-day minimum storage duration | Early deletion can leave a prorated charge for the remaining minimum. |
| S3 Glacier Instant Retrieval / Flexible Retrieval | 90-day minimum storage duration | Deleting early can still incur the remaining minimum-duration storage charge. |
| S3 Glacier Deep Archive | 180-day minimum storage duration | A deleted object can still leave a prorated minimum-duration charge. |
Use US East (N. Virginia) as the reference Region when you later calculate any Region-specific S3 prices for this cleanup. The important distinction here is simpler: free DELETE requests do not cancel minimum-storage-duration charges.
Also do not restore Glacier objects merely because you want to delete them. Restore and delete are different operations. A version that is otherwise eligible for deletion does not need to be restored first merely to remove it.
When nothing works, reduce the failure to one request
If the bucket still refuses deletion, stop making broad policy changes. Build a small failure report around one operation.
Collect:
- The exact CLI command that fails, with credentials and current MFA code removed.
- The complete error text and request ID.
aws sts get-caller-identityoutput.get-bucket-versioningoutput.get-object-lock-configurationoutput.- A small version-listing sample.
- For one failing version,
get-object-retentionandget-object-legal-hold. - The relevant bucket-policy statements.
- Whether the account sits under an SCP or RCP.
- Whether the request goes through an S3 VPC endpoint.
- Whether same-account access points remain.
“The bucket won't delete” is a wide problem. “This specific version ID returns AccessDenied under Governance retention even though the role has DeleteObjectVersion” is much narrower. “All versions are gone and DeleteBucket alone is explicitly denied by an SCP” is narrower still.
If the remaining version is under active Compliance retention, support is not a secret bypass route around the retention model. The useful question becomes whether the state and dates are behaving as configured, not how to force the version away early.
If the error identifies a VPC endpoint policy, inspect that endpoint policy. A role can have the correct IAM allow while the endpoint through which the request travels still restricts the S3 action.
Production-safe S3 bucket deletion runbook
If this is a production or customer-data bucket, use a fixed order. It prevents cleanup from turning into “keep granting permissions until the error goes away.”
- Confirm the bucket really should be deleted. Decide whether preserving the globally unique name matters.
- Back up or replicate anything that must survive. Bucket deletion is not reversible.
- Stop every writer. Include applications and service log delivery.
- Clean up related DNS or static-website configuration.
- Confirm the account and caller ARN.
- Check versioning and MFA Delete.
- Check Object Lock configuration.
- List versions and delete markers.
- Inspect one protected version before changing policy.
- Handle Governance, Compliance, legal hold, and event hold according to their actual state.
- Delete every eligible object version and delete marker.
- Repeat the version listing until the intended history is gone.
- Remove same-account Access Points and Multi-Region Access Points.
- Check the bucket policy, SCPs, RCPs, and other relevant policy layers for
s3:DeleteBucketdenies. - Run the final bucket deletion.
The final CLI operation is simple:
aws s3api delete-bucket \
--bucket biscuit-old-invoices
The hard part is making the bucket truly eligible for that final request.
16 S3 bucket deletion questions that come after the first error
Why does AWS S3 say Access Denied when I delete a bucket?
If the failure is on DeleteBucket, check s3:DeleteBucket authorization and explicit denies in the bucket policy, SCPs, RCPs, and other applicable policy layers. If Access Denied occurs while emptying the bucket, investigate object-version permission, Object Lock, legal holds, event holds, and MFA Delete instead.
Why is my S3 bucket not empty after I deleted all objects?
If versioning was enabled or suspended, historical versions and delete markers can remain after current objects disappear. Run list-object-versions and clear the remaining versions and markers that are eligible for deletion.
Does aws s3 rb --force delete all versions?
No. For a versioning-enabled bucket, aws s3 rb --force does not clear historical versioned objects. The bucket deletion can therefore fail because versions remain.
How do I delete all object versions from an S3 bucket?
Use list-object-versions to obtain keys and version IDs, then delete those versions explicitly. Delete markers also have version IDs and must be removed when you are completely emptying the bucket.
Why does DeleteObjectVersion return Access Denied?
The caller can be missing s3:DeleteObjectVersion, an explicit deny can apply, MFA Delete can require MFA, or Object Lock can protect that specific version. Inspect the exact version before widening IAM permissions.
Can I delete an S3 object before its Object Lock retention date?
Governance-mode retention can be bypassed by an authorized principal making an explicit Governance-bypass request. Active Compliance-mode retention is not bypassed the same way and cannot simply be shortened to permit early deletion.
Can the AWS root user delete a Compliance-mode locked object?
Not while active Compliance retention protects that object version. Root access does not provide an override for the active Compliance-mode retention period.
How do I bypass S3 Governance retention?
The caller needs s3:BypassGovernanceRetention, and the request must explicitly use the Governance bypass. With the AWS CLI, delete-object supports --bypass-governance-retention.
Why is Access Denied still happening after the retention date passed?
Check legal hold, event-hold state, the exact version ID, and authorization. A retention date can stop being the blocker while an independent legal hold or policy deny continues to prevent permanent deletion.
How do I check whether an S3 version has a legal hold?
Run get-object-legal-hold with the bucket, key, and version ID. A status of ON means the version remains under legal hold until an authorized operation turns that hold off.
What is an S3 Object Lock event hold?
An event hold is part of variable Object Lock retention introduced on September 8, 2026. It protects an object while the hold is active, and releasing the hold establishes retention based on the configured event-hold duration.
Why does Multi-Object Delete fail when MFA Delete is enabled?
If the request includes versioned deletes that require MFA and valid MFA information is missing, the multi-object request can fail as a whole. Supply the required MFA information for that versioned delete operation.
Why can I delete S3 objects in the console but not in the CLI?
First compare caller identities. The console and CLI can use different principals. For Governance-protected versions, the console can also include the bypass header when the principal has permission, while the CLI requires the explicit bypass option.
Can an SCP block S3 deletion even when my role has full S3 access?
Yes. An applicable Organizations service control policy can restrict the effective permissions available in the account. An explicit deny is not defeated by attaching a broad Allow policy to the role.
Why does S3 say BucketHasAccessPointsAttached?
The bucket still has a same-account S3 Access Point or Multi-Region Access Point dependency. Remove the access point after confirming that applications no longer use it, then retry bucket deletion.
Do S3 DELETE requests cost money?
As of October 1, 2026, S3 DELETE and CANCEL requests are free. Storage can still generate charges while locked versions remain, and deleting objects before a storage class's minimum duration can leave prorated minimum-duration charges.
The final check before DeleteBucket
At the end of the cleanup, you should be able to answer every one of these questions without guessing: Which account and role are making the request? Is versioning enabled or suspended? Are any versions or delete markers left? Does Object Lock protect any remaining version? Is a legal or event hold active? Does MFA Delete apply? Are access points still attached? Is another service still writing to the bucket? Does any policy explicitly deny s3:DeleteBucket?
Only then run the final command:
aws s3api delete-bucket \
--bucket biscuit-old-invoices
If it now fails with AccessDenied, you have done something valuable: you have removed version history and Object Lock ambiguity from the problem. The remaining failure is the authorization path for the bucket deletion itself.
If it succeeds, remember that S3 bucket deletion can take time to propagate through the distributed service. Do not build an automation that assumes the old bucket name can be safely recreated or repurposed the instant one delete response returns.
If your bucket has been fighting you for an afternoon, the shortest route out is usually not a bigger IAM policy. Find the exact version or exact API operation that is refusing to move, then solve that layer and only that layer.
📌 If you keep one line from this page
An S3 bucket is ready to delete only when its versions are gone, its protections permit deletion, its dependencies are removed, and the final caller is allowed to delete the bucket.
Treat those as four separate checks and a generic 403 becomes a much smaller problem.
Revision note. Written October 1, 2026, with Object Lock event holds included. A bucket that refuses to go is usually guarding one old version, and that version can be found.