← All articles
Cloud Security 10 min read

The S3 Security Hardening Checklist: Nine Controls Every Bucket Needs

A practical, copy-paste-ready guide to hardening S3 buckets — covering Block Public Access, encryption enforcement, TLS, VPC endpoints, access logging, Macie, and more.

CloudDefender Team ·
S3 Security Layers — Defense in DepthLAYER 1 — PERIMETER: Block Public Access (account-level SCP enforcement)BlockPublicAcls · IgnorePublicAcls · BlockPublicPolicy · RestrictPublicBuckets — all four, always onLAYER 2 — ACCESS: Bucket Policy (least-privilege) + ACLs disabled (BucketOwnerEnforced)Explicit allow on specific principals + actions · Deny aws:PrincipalOrgID outside org · No s3:* wildcardsLAYER 3 — TRANSIT: Enforce TLS — deny s3:* when aws:SecureTransport = falseAll data in flight encrypted · Prevents cleartext HTTP access to presigned URLs and SDK callsLAYER 4 — AT REST: SSE-KMS default encryption + deny unencrypted PutObjectCMK with key policy · Deny s3:PutObject when s3:x-amz-server-side-encryption ≠ aws:kmsLAYER 5 — NETWORK: VPC Gateway EndpointRestrict bucket access to traffic from VPC only · No internet route neededLAYER 6 — VISIBILITY: Logging + MacieS3 access logs · CloudTrail data events · Macie sensitive data discoveryAUDIT: S3 Inventory (weekly report on encryption status, ACL config, replication) across all bucketsSingle Athena query identifies every non-compliant bucket in the account
S3 security is not a single toggle — it is six independent layers. All six must be in place.

S3 buckets are involved in a disproportionate share of cloud data breaches. They hold structured data exports, backup archives, application logs, and sensitive configuration files — and AWS ships them with a set of defaults that prioritize flexibility over security. The good news is that every meaningful S3 risk has a corresponding control, and all of them can be enforced at scale through policy. This checklist covers each one.

1. Block Public Access — all four settings, at the account level

AWS S3 Block Public Access has four independent settings: BlockPublicAcls, IgnorePublicAcls, BlockPublicPolicy, and RestrictPublicBuckets. Each addresses a different mechanism by which a bucket can become publicly accessible. All four should be enabled, and they should be enabled at the account level — not just the bucket level — so that a misconfigured bucket policy or ACL on a new bucket cannot override them.

Enforce this across your AWS Organization with a Service Control Policy:

{
  "Effect": "Deny",
  "Action": "s3:PutBucketPublicAccessBlock",
  "Resource": "*",
  "Condition": {
    "StringNotEquals": {
      "s3:publicAccessBlockConfiguration/BlockPublicAcls": "true",
      "s3:publicAccessBlockConfiguration/BlockPublicPolicy": "true"
    }
  }
}

An additional SCP should deny s3:PutBucketAcl and s3:PutObjectAcl entirely, preventing ACL-based public grants even if Block Public Access were somehow disabled.

2. Disable ACLs — set Object Ownership to BucketOwnerEnforced

S3 ACLs are a legacy access control mechanism that predates IAM and bucket policies. They are harder to audit, easier to misconfigure, and unnecessary for any modern workload. Set ObjectOwnership to BucketOwnerEnforced on every bucket. This disables ACLs entirely — all access is then controlled exclusively through bucket policies and IAM, which are auditable and enforceable through standard tooling.

Write bucket policies that explicitly allow only the specific IAM principals (roles or users) that need access, to the specific actions they need (s3:GetObject, s3:PutObject), on specific key prefixes where possible. Add a deny on aws:PrincipalOrgID outside your organization ID to prevent unintended cross-account access.

3. Enforce TLS in transit

Add the following statement to every bucket policy. It denies any S3 operation where the request was not made over HTTPS:

{
  "Effect": "Deny",
  "Principal": "*",
  "Action": "s3:*",
  "Resource": ["arn:aws:s3:::your-bucket", "arn:aws:s3:::your-bucket/*"],
  "Condition": {
    "Bool": { "aws:SecureTransport": "false" }
  }
}

This prevents cleartext HTTP access to bucket contents, including via presigned URLs constructed without the HTTPS scheme, and ensures all SDK calls use TLS regardless of client configuration.

4. Encrypt at rest — SSE-KMS with a deny on unencrypted uploads

Set the default encryption on every bucket to aws:kms using a customer-managed KMS key (CMK). This ensures objects written without an explicit encryption header are encrypted using your CMK. But default encryption alone does not prevent callers from explicitly requesting no encryption — add a bucket policy deny for that case:

{
  "Effect": "Deny",
  "Principal": "*",
  "Action": "s3:PutObject",
  "Resource": "arn:aws:s3:::your-bucket/*",
  "Condition": {
    "StringNotEquals": {
      "s3:x-amz-server-side-encryption": "aws:kms"
    }
  }
}

Use SSE-S3 (AES-256, AWS-managed keys) for non-sensitive workloads where auditability of key access is not required. Use SSE-KMS with a CMK for any bucket containing PII, financial data, credentials, or backup archives — the CMK’s key policy gives you control over who can decrypt, and every kms:Decrypt call is logged in CloudTrail.

5. VPC gateway endpoint with an endpoint policy

For workloads that access S3 from within a VPC, provision an S3 gateway endpoint (com.amazonaws.region.s3). This routes S3 traffic over AWS’s private network rather than the public internet, eliminating a class of network-level interception risk. More importantly, attach an endpoint policy that restricts which buckets can be accessed through the endpoint:

{
  "Effect": "Allow",
  "Principal": "*",
  "Action": ["s3:GetObject", "s3:PutObject"],
  "Resource": "arn:aws:s3:::your-bucket/*"
}

Pair this with a bucket policy condition on aws:sourceVpce to ensure the bucket can only be accessed through the approved endpoint — not from the public internet, even with valid credentials.

6. S3 access logging and CloudTrail data events

These are two distinct logging mechanisms that complement each other. S3 server access logging captures HTTP-level requests to the bucket: method, requester IP, request URI, HTTP status, bytes transferred. It is written to a target bucket you designate and is low-cost but eventually-consistent.

CloudTrail data events capture IAM-level API calls: which IAM principal called s3:GetObject or s3:PutObject, from which IP, at what time, on which object key. They are authoritative for security investigations but carry per-event cost (~$0.10 per 100,000 events). Enable data events on buckets containing sensitive data. For all other buckets, S3 server access logging provides a cost-effective baseline.

7. S3 Inventory for fleet-wide auditing

S3 Inventory produces weekly or daily CSV or ORC reports listing every object in a bucket along with its encryption status, replication status, storage class, and ACL configuration. Query these reports with Athena to identify non-compliant objects at scale — for example, objects where EncryptionStatus is NOT-SSE or SSE-S3 when the policy requires SSE-KMS.

Enable Inventory at the bucket level and route reports to a centralized audit bucket. A single Athena query across the inventory data identifies every unencrypted or misconfigured object across all buckets in the account.

8. Replication encryption

If you use S3 Cross-Region Replication or Same-Region Replication, configure the replication rule to require SSE-KMS encryption on replicated objects. Without this, objects encrypted with SSE-S3 in the source bucket may be replicated without re-encryption using your CMK, leaving replicated copies outside your key policy’s control.

9. Amazon Macie for sensitive data discovery

Even with all technical controls in place, data classification tells you whether the controls matter for a given bucket. Amazon Macie scans bucket contents using machine learning and pattern matching to identify PII (names, email addresses, SSNs, credit card numbers), credentials (API keys, private keys), and other sensitive data types.

Run a Macie discovery job against all buckets on initial deployment and schedule recurring jobs for buckets that receive new data. Macie findings surface buckets that contain sensitive data but may not have the appropriate controls applied — closing the gap between policy intent and actual data risk.

Takeaway

S3 security is not a single toggle — it is six independent layers applied consistently across every bucket in every account. The controls above are not optional hardening for high-risk environments; they are the baseline for any environment that stores data it cannot afford to lose or expose. Apply them at account creation via IaC, enforce non-negotiable settings through SCPs, and audit compliance continuously with Inventory and Config rules. A breach traced to an S3 misconfiguration is, at this point in the history of cloud security, entirely preventable.

CloudDefender

Defend your cloud. Continuously.

CloudDefender Suite gives security teams continuous posture management, threat detection, and compliance automation across AWS, Azure, and GCP — with zero false-positive fatigue.

Try CloudDefender →