Amazon S3 Security Best Practices: Bucket Security Checklist

S3 is private by default, but most S3 security incidents happen because of misconfiguration, not a compromised credential. A forgotten ACL, an overly broad bucket policy, or Block Public Access disabled “temporarily” can expose sensitive data without any alarm firing. This page covers the controls that matter most, what order to apply them, and where most teams go wrong.

S3 security in plain English

Think of an S3 bucket like a filing cabinet in a locked room. The room is locked by default and only people with a key can get in. The problem is not the lock itself. It is when someone hands out copies of the key, props the door open, or writes “public: yes” on the outside without realising it.

When you create an S3 bucket, it is private. No one outside your AWS account can read or write to it. Exposure happens when someone in your account changes that default, usually through a policy mistake rather than a security breach.

S3 security comes down to four things:

  1. Access control: who can read, write, or delete objects? Controlled via IAM policies, bucket policies, ACLs, and Block Public Access settings.
  2. Encryption: are objects protected at rest and in transit? S3 handles encryption at rest automatically. In-transit protection requires HTTPS.
  3. Logging and visibility: do you know who accessed what, and when?
  4. Recovery and retention: can you restore deleted or overwritten objects?

Understanding how these layers interact is what prevents the mistakes that cause breaches.

Security checklist: start here on every new bucket

These are the highest-value controls, roughly in priority order:

  • Enable Block Public Access at the account level. Prevents any bucket from being made public without a deliberate override.
  • Set Object Ownership to “Bucket owner enforced”. Disables ACLs entirely. Use bucket policies and IAM instead.
  • Apply a least-privilege IAM or bucket policy. Grant only the permissions actually needed. See S3 IAM Policies vs Bucket Policies.
  • Leave default encryption (SSE-S3) in place. It is on automatically and costs nothing.
  • Switch to SSE-KMS only if you need key audit trails or compliance key separation.
  • Enable versioning on any bucket where rollback matters. See S3 Versioning.
  • Enable CloudTrail data events for sensitive buckets. Structured, reliable access audit trail.
  • Enable server access logging for high-volume or regulated buckets.
  • Enable Object Lock only if you have a genuine retention or compliance requirement. It cannot be undone.
  • Review public access and cross-account grants regularly using the S3 Access Analyser in the console.

How S3 access control actually works

S3 has multiple access control layers, and they interact in ways that confuse even experienced engineers. Here is what each one is and what it does.

IAM policies are attached to users, roles, or groups. They define what S3 actions an identity can perform across all buckets they interact with.

Bucket policies are attached to the bucket itself. They can grant or deny access to any principal, including principals from other AWS accounts. A bucket policy can make a bucket public or grant another account write access.

ACLs are a legacy per-object and per-bucket access mechanism. They predate IAM and bucket policies and are harder to audit. AWS recommends disabling them entirely on modern workloads.

Block Public Access is a set of four account-level or bucket-level overrides that prevent public access regardless of what bucket policies or ACLs say. Think of it as a master override switch that says “no matter what anyone configured below, this cannot be public.”

How access evaluation works, step by step:

  1. If Block Public Access is enabled and the request would result in public access, it is denied immediately.
  2. If there is an explicit Deny anywhere in an IAM policy, bucket policy, or SCP, access is denied.
  3. Otherwise, access is allowed if there is at least one explicit Allow in either the IAM policy or the bucket policy.
  4. If there is no Allow, access is denied by default.
Tip

The most confusing scenario for beginners: a bucket has a public-read bucket policy AND Block Public Access enabled. Result? Still private. Block Public Access overrides the bucket policy completely. Always check Block Public Access first when debugging unexpected access behaviour.

Key rules to remember:

  • Block Public Access at the account level applies to all buckets in the account. A bucket-level override is needed to allow any public bucket.
  • An explicit Deny in an IAM policy cannot be overridden by a bucket policy Allow, and vice versa.
  • For cross-account access, both the IAM policy in the caller’s account and the bucket policy on the bucket must allow the action.

For a deeper look at how IAM and bucket policies interact, see S3 IAM Policies vs Bucket Policies and IAM Policy Structure.

Core S3 security best practices

1. Enable Block Public Access by default

Block Public Access is four settings that override bucket policies and ACLs to prevent public access. Enable all four at the account level first. This protects all buckets without needing bucket-by-bucket configuration.

  • BlockPublicAcls: prevents new ACLs that grant public access; ignores existing public ACLs
  • IgnorePublicAcls: ignores all existing public ACLs
  • BlockPublicPolicy: prevents bucket policies that grant public access from being saved
  • RestrictPublicBuckets: restricts access on buckets with public policies to AWS services and authorised users only

Enable Block Public Access at account level in the S3 console under Block Public Access settings for this account. For the rare case where you need a public bucket (such as static website hosting), override it at the bucket level only, not the account level.

# Enable all Block Public Access settings on a specific bucket
aws s3api put-public-access-block \
  --bucket my-company-assets \
  --public-access-block-configuration \
    "BlockPublicAcls=true,IgnorePublicAcls=true,BlockPublicPolicy=true,RestrictPublicBuckets=true"

# Verify the current settings
aws s3api get-public-access-block --bucket my-company-assets

2. Prefer IAM and bucket policies over ACLs

IAM policies give you centralised, auditable access control that works consistently across all AWS services. Bucket policies let you control access at the bucket level without requiring IAM changes in every account that needs access.

Use IAM policies when:

Use bucket policies when:

  • Granting access to another AWS account (cross-account access)
  • Enforcing conditions like requiring HTTPS (aws:SecureTransport) or restricting access to a VPC endpoint
  • Making an entire bucket or specific path publicly readable

3. Disable ACLs with Bucket owner enforced where possible

ACLs are a legacy access control mechanism. The modern replacement is to set Object Ownership to Bucket owner enforced, which disables ACLs entirely. All objects become owned by the bucket owner, and access is controlled through policies only.

This is simpler, more auditable, and less prone to the kind of silent misconfiguration that ACLs are known for.

# Disable ACLs by setting Object Ownership to Bucket owner enforced
aws s3api put-bucket-ownership-controls \
  --bucket my-company-assets \
  --ownership-controls 'Rules=[{ObjectOwnership=BucketOwnerEnforced}]'
Warning

If external accounts are uploading objects using ACLs to transfer ownership (a common legacy cross-account pattern), disabling ACLs will break those uploads. Audit cross-account upload patterns before making this change in production.

4. Encrypt data at rest by default

Since January 2023, S3 automatically encrypts all new objects with SSE-S3 (S3-managed keys). You do not need to configure anything. Encryption at rest is on by default at no extra cost.

Data in transit is protected by HTTPS. You can enforce HTTPS-only access with a bucket policy condition using aws:SecureTransport: "false" as a Deny condition. S3 does not enforce HTTPS automatically. A bucket policy deny is required.

5. Choose SSE-S3 vs SSE-KMS deliberately

SSE-S3 (default): AWS manages the encryption keys. No additional cost, no configuration required. Suitable for the vast majority of workloads.

SSE-KMS: Uses an AWS KMS key you control. Every encryption and decryption is logged in CloudTrail, giving you a full audit trail of data access. Use SSE-KMS when:

  • A compliance framework requires key audit trails
  • You need to revoke access to data by disabling the KMS key
  • A regulation mandates key separation from the storage service

Always enable BucketKeyEnabled: true with SSE-KMS. Without it, every S3 object read or write triggers a separate KMS API call. On a busy bucket this creates significant cost. The bucket key generates a per-bucket data key locally, reducing KMS API calls by up to 99%.

# Enable SSE-KMS as the default encryption for a bucket
aws s3api put-bucket-encryption \
  --bucket my-company-assets \
  --server-side-encryption-configuration '{
    "Rules": [{
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "aws:kms",
        "KMSMasterKeyID": "arn:aws:kms:eu-west-2:123456789012:key/my-key-id"
      },
      "BucketKeyEnabled": true
    }]
  }'
Tip

Not sure whether you need SSE-KMS? Ask whether your security or compliance team needs to see a log of every time a specific file was read. If yes, use SSE-KMS. If the question does not apply, SSE-S3 is the right default.

For more on key types and rotation, see AWS KMS Overview and Customer-Managed Keys.

6. Enable versioning for recovery

S3 Versioning keeps every version of every object. If someone deletes or overwrites a file accidentally or maliciously, you can restore the previous version. This is the primary defence against accidental deletion and ransomware attacks on S3.

Versioning is not on by default. Enable it on any bucket where data loss would be a problem.

# Enable versioning on a bucket
aws s3api put-bucket-versioning \
  --bucket my-company-assets \
  --versioning-configuration Status=Enabled

Note: versioning increases storage costs because old versions are retained. Use S3 Lifecycle Policies to automatically expire or archive old versions after a defined period.

7. Use Object Lock only when retention is required

Object Lock prevents objects from being deleted or overwritten for a set period. It is designed for compliance scenarios (financial records, audit logs, medical data) where data must not be altered after writing (WORM: Write Once Read Many).

Two modes:

Governance mode: prevents deletion and overwrite, but users with s3:BypassGovernanceRetention IAM permission can override it. Useful for internal protection against accidental deletion.

Compliance mode: nobody, including the root account, can delete or shorten the retention period before it expires. Use only when a regulation requires it.

# Enable Object Lock when creating a bucket (cannot be added after creation)
aws s3api create-bucket \
  --bucket my-compliance-archive \
  --region eu-west-2 \
  --create-bucket-configuration LocationConstraint=eu-west-2 \
  --object-lock-enabled-for-bucket

# Apply a 7-year compliance retention to a specific object
aws s3api put-object-retention \
  --bucket my-compliance-archive \
  --key records/2025/audit-log.csv \
  --retention '{"Mode":"COMPLIANCE","RetainUntilDate":"2032-01-01T00:00:00Z"}'
Danger

Compliance mode Object Lock is permanent for the duration set. Nobody, including AWS Support, can remove a Compliance lock before the retention date. If you set a 7-year Compliance retention and later need to delete the data for legal reasons, you cannot. Test in a non-production bucket before applying this to real data.

Note

Object Lock must be enabled at bucket creation time. It cannot be added to an existing bucket. Plan for this before you start writing data.

8. Turn on logging and monitoring

S3 does not log access by default. For any bucket containing sensitive or regulated data, enable at least one of:

AWS CloudTrail data events record every S3 API call (GetObject, PutObject, DeleteObject, etc.) in a structured, reliable log. They integrate with CloudWatch for alerting and are the better choice for security investigations and compliance auditing. See CloudTrail Overview and CloudTrail Log Types.

S3 Server Access Logging records raw per-request logs to a destination S3 bucket. Delivery is best-effort and not guaranteed. It gives detailed per-request data including requester IP and response code, which is useful for access pattern analysis.

For compliance buckets, enable both. For most workloads, CloudTrail data events alone is sufficient.

9. Review access regularly and remove legacy permissions

Permissions accumulate over time. Quarterly audits should check:

  • Any bucket with public access enabled (use the S3 console access analyser)
  • Cross-account bucket policies and who they grant access to
  • IAM roles with broad S3 permissions (look for s3:* on *)
  • Old signed URLs that may still be in use, see S3 Signed URLs

The S3 Access Analyser in the console shows you all buckets with public or cross-account access in one view.

Choosing controls by scenario

Not every bucket needs every control. Here is what to apply based on the use case:

Private application bucket (API uploads, application data, internal logs)

  • Block Public Access: on
  • ACLs: disabled
  • Encryption: SSE-S3 default (no changes needed)
  • Versioning: enabled if data is hard to recreate
  • Logging: CloudTrail data events if the data is sensitive
  • Object Lock: not needed

Public static content (website assets, public downloads)

  • Block Public Access: disabled at the bucket level only
  • Bucket policy: allow GetObject for the public, restricted to specific key prefixes
  • ACLs: disabled (use bucket policy instead)
  • Versioning: optional, but useful for rollback during deployments
  • Logging: server access logging if traffic analysis matters

Regulated compliance archive (financial records, audit logs, medical data)

  • Block Public Access: on
  • ACLs: disabled
  • Encryption: SSE-KMS with a customer-managed key and audit trail
  • Versioning: enabled
  • Object Lock: Compliance mode with retention period matching the regulation
  • Logging: both CloudTrail data events and server access logging
  • Regular access reviews: required

Cross-account upload bucket (receiving uploads from another account or partner)

  • Block Public Access: on
  • ACLs: disabled. Use a bucket policy with an aws:PrincipalAccount condition to restrict which account can upload.
  • Versioning: enabled to protect against overwrite
  • Logging: CloudTrail data events to audit cross-account activity

High-throughput bucket (logs, telemetry, data pipeline)

  • SSE-S3 default encryption. Avoid SSE-KMS unless required, as KMS costs scale with request volume even with BucketKeyEnabled.
  • Lifecycle policies to expire or transition old objects. See S3 Lifecycle Policies and S3 Storage Classes.
  • Versioning: usually off (cost impact on high-churn data) unless recovery matters

Comparisons: choosing the right access control

IAM policies vs bucket policies vs ACLs

IAM PoliciesBucket PoliciesACLs
Attached toUsers, roles, groupsThe bucketObjects or buckets
Cross-account accessNo (requires trust)Yes, directlyYes (legacy)
Object-level controlNoYes (via key prefix)Yes (legacy)
AuditabilityHighHighLow
RecommendedYesYesNo, disable if possible
Use whenControlling your own AWS identitiesCross-account grants or HTTPS enforcementNever for new buckets

SSE-S3 vs SSE-KMS

SSE-S3SSE-KMS
Key managementAWS-managedYou control the key
CostFreeKMS API call charges apply
Audit trailNoneFull CloudTrail log per call
Key revocationNot possibleDisable key to cut off access
Recommended defaultYesOnly when audit or compliance requires it

Versioning vs Object Lock

VersioningObject Lock
Protects againstAccidental deletion, overwriteDeliberate deletion, tampering
Can be undoneYes, delete the versionNo, retention period is fixed in Compliance mode
CostStores all previous versionsSame as versioning, plus Object Lock overhead
Use whenYou need rollback or recoveryRegulation requires WORM retention

Common mistakes

  1. Assuming S3 stays private because it was private at creation. S3 is private by default, but permissions can be changed at any time by anyone with sufficient IAM access. Enable Block Public Access at the account level so that any accidental policy change cannot make a bucket public.
  2. Relying on ACLs without realising it. If you created buckets before ACLs were disabled by default, they may still have ACLs in place. Audit existing buckets and migrate to bucket policies before disabling ACLs, as the change is not always reversible without restoring object ownership.
  3. Confusing bucket policy evaluation with Block Public Access. Block Public Access overrides bucket policies that would allow public access. A bucket can have a s3:GetObject Allow for * in its bucket policy AND have Block Public Access enabled. The result is still private. This trips up engineers who test policies without checking whether Block Public Access is on. See S3 Access Denied Errors for more on this.
  4. Turning on SSE-KMS without enabling BucketKeyEnabled. Every S3 object read and write triggers a KMS API call without the bucket key enabled. On a high-throughput bucket, this can multiply your AWS bill significantly. Always set BucketKeyEnabled: true alongside SSE-KMS.
  5. Confusing CloudTrail with server access logging. Server access logs are best-effort and give raw text data. CloudTrail data events are reliable and structured. For compliance or security investigations, use CloudTrail, not server access logs. See CloudTrail Log Types.
  6. Enabling Object Lock without understanding the consequences. Compliance mode Object Lock cannot be undone by anyone, including AWS Support. If you set a 7-year Compliance retention and then need to delete the data for a legal reason, you cannot. Always verify the retention consequences before enabling Compliance mode.
  7. Forgetting versioning until after a deletion incident. Versioning must be enabled before the deletion happens. It cannot recover objects deleted before versioning was turned on. Enable versioning early on any bucket where data loss would be a problem.
  8. Writing bucket policies that grant s3:* to *. Broad permissions are easy to write but hard to audit and often grant far more access than intended. Follow least-privilege principles and specify exactly which actions and key prefixes are needed.

Frequently asked questions

What is the safest default configuration for a new S3 bucket?

Enable Block Public Access at the account level, set Object Ownership to Bucket owner enforced (disables ACLs), leave default SSE-S3 encryption in place, and enable versioning if you need rollback capability. That covers the most common attack surface before you write a single object.

Should I disable ACLs on all S3 buckets?

In almost all cases, yes. Set Object Ownership to "Bucket owner enforced" so all access is controlled through IAM and bucket policies only. ACLs are a legacy mechanism and are harder to audit. The only exception is if you have a specific cross-account upload pattern that relies on ACLs and you have not migrated it yet.

When should I use SSE-KMS instead of default S3 encryption?

Use SSE-KMS when you need an audit trail of who decrypted what and when (CloudTrail logs every KMS API call), when a compliance requirement mandates key separation, or when you need to revoke access to data by disabling the KMS key. For standard workloads where you just want data protected at rest, the default SSE-S3 encryption is sufficient and costs nothing extra.

Do I need both CloudTrail data events and S3 server access logging?

They serve different purposes. CloudTrail data events are structured and reliable, making them the better choice for security investigations and compliance auditing. Server access logs are best-effort and give raw per-request detail. For sensitive buckets, enabling both is reasonable. For most buckets, CloudTrail data events alone is the stronger choice.

Can an S3 bucket be public safely?

Yes, with caveats. Static website hosting and public content distribution are valid use cases. The key controls are: disable Block Public Access only at the specific bucket level (not account level), use a narrow bucket policy that allows only GetObject on specific paths, and never put sensitive data in the same bucket. Regularly verify what is actually public using the S3 console access analyser.

Last verified: 5 April 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.