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.
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 →