S3 buckets should have default encryption enabled
Since January 2023, AWS applies SSE-S3 encryption to new objects by default, but an explicit bucket-level encryption configuration is the auditable proof that encryption at rest is intentional policy rather than an inherited platform default. Without it, you cannot enforce KMS-based encryption or specify a particular CMK, and your compliance posture depends on an AWS platform behavior that could theoretically change.
Explicit default encryption also protects against human error. If a developer uploads an object via the SDK without encryption headers, the bucket configuration is the safety net. For regulated data, relying on implicit behavior instead of declared configuration will not satisfy most auditors.
Retrofit consideration
Enabling default encryption on an existing bucket doesn't retroactively encrypt objects already stored. Adding aws_s3_bucket_server_side_encryption_configuration to Terraform state applies to new uploads only. Identify pre-existing unencrypted objects with aws s3api head-object or S3 Inventory, then re-encrypt using aws s3 cp --sse or S3 Batch Operations.
Implementation
Choose the approach that matches how you manage Terraform.
Use the compliance.tf module to enforce this control by default. See get started with compliance.tf.
module "s3_bucket" {
source = "soc2.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "pcidss.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "hipaa.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "gdpr.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nist80053.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nistcsf.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "fedrampmoderate.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "cisv80ig1.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nist800171.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "cisacyberessentials.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nydfs23.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "eugmpannex11.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "cfrpart11.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "rbicybersecurity.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "hipaasecurity2003.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nistcsfv11.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "nist80053rev4.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
module "s3_bucket" {
source = "pcidssv321.compliance.tf/terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
}
If you use terraform-aws-modules/s3-bucket/aws, set the right module inputs for this control. You can later migrate to the compliance.tf module with minimal changes because it is compatible by design.
module "s3_bucket" {
source = "terraform-aws-modules/s3-bucket/aws"
version = ">=5.0.0"
bucket = "abc123"
server_side_encryption_configuration = {
rule = {
apply_server_side_encryption_by_default = {
sse_algorithm = "AES256"
}
}
}
}
Use AWS provider resources directly. See docs for the resources involved: aws_s3_bucket_server_side_encryption_configuration.
resource "aws_s3_bucket" "this" {
bucket = "pofix-abc123"
force_destroy = true
}
resource "aws_s3_bucket_server_side_encryption_configuration" "this" {
bucket = "example-bucket-abc123"
rule {
apply_server_side_encryption_by_default {
kms_master_key_id = "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
sse_algorithm = "aws:kms"
}
}
}
What this control checks
This control validates that an aws_s3_bucket_server_side_encryption_configuration resource is associated with each aws_s3_bucket. The rule block must contain an apply_server_side_encryption_by_default block with sse_algorithm set to AES256, aws:kms, or aws:kms:dsse for dual-layer encryption. When sse_algorithm is aws:kms, kms_master_key_id is optional; omitting it defaults to the AWS-managed aws/s3 key. It fails when no encryption configuration resource exists or when sse_algorithm is absent. bucket_key_enabled is optional but reduces KMS API call costs when using KMS encryption.
Common pitfalls
Deprecated inline encryption block
Migrate to the standalone
aws_s3_bucket_server_side_encryption_configurationresource. The inlineserver_side_encryption_configurationblock insideaws_s3_bucketwas deprecated in provider v4 and can conflict with the separate resource, causing silent drift you won't catch until plan output looks wrong.Default encryption does not encrypt existing objects
Objects uploaded before you enable default encryption remain unencrypted. Adding the Terraform resource to state doesn't backfill anything. Use
aws s3api head-objector S3 Inventory to identify pre-existing unencrypted objects, then re-copy usingaws s3 cp --sseor S3 Batch Operations.AWS-managed key vs. customer-managed key
Omitting
kms_master_key_idwhensse_algorithmisaws:kmsdefaults to theaws/s3AWS-managed key. That passes this control but will fail any check requiring a customer-managed CMK, which stricter FedRAMP and PCI mappings typically do. Setkms_master_key_idexplicitly if CMK usage is a requirement.Bucket policy not enforcing encryption headers
Default encryption is a fallback, not a mandate. Clients can still upload with an explicit
x-amz-server-side-encryptionheader specifying a weaker algorithm, and the bucket default won't override it. To enforce a minimum algorithm, add a bucket policy that deniess3:PutObjectrequests where thes3:x-amz-server-side-encryptioncondition key doesn't match your required value.
Audit evidence
Auditors expect AWS Config rule evaluation results for s3-bucket-server-side-encryption-enabled showing all buckets as COMPLIANT. Per-bucket CLI confirmation: aws s3api get-bucket-encryption --bucket <name> should return a ServerSideEncryptionConfiguration with SSEAlgorithm of AES256 or aws:kms. For KMS-encrypted buckets, include the key policy to verify access controls are appropriately scoped.
Console screenshots showing the "Default encryption" panel on each bucket, along with a compliance report from AWS Security Hub or AWS Config showing passing evaluations, complete the evidence package.
Framework-specific interpretation
SOC 2: SOC 2 doesn't define a single mandatory encryption control. Encryption at rest typically maps to C1.2 under the Confidentiality category and related Security criteria. Default S3 encryption supports those mappings, though trust services criteria give auditors some latitude in how they evaluate the specifics.
PCI DSS v4.0: Requirement 3.4 says stored account data, including PAN, must be rendered unreadable using strong cryptography. SSE-S3 (AES-256) or SSE-KMS supports this objective for cardholder data in S3. Requirement 3.5 covers key management, so for KMS-encrypted buckets the key policy is also in scope for assessments.
HIPAA Omnibus Rule 2013: 45 CFR 164.312(a)(2)(iv) classifies encryption as an addressable safeguard for ePHI at rest. S3 default encryption satisfies this for covered entities and business associates, ensuring any ePHI in S3 is encrypted automatically without depending on application-level headers.
GDPR: Article 32 explicitly cites encryption as an appropriate technical measure for protecting personal data. For S3 buckets holding personal data of EU data subjects, default encryption addresses this obligation.
NIST SP 800-53 Rev 5: SC-28 and SC-28(1) require cryptographic protection of data at rest. S3 default encryption satisfies both for S3-stored data. If you're using KMS, the key configuration also supports SC-12 (Cryptographic Key Establishment and Management), provided key management is properly scoped.
NIST Cybersecurity Framework v2.0: S3 default encryption maps to PR.DS under the Protect function, the subcategory covering cryptographic protection of data at rest. Enabling it on every bucket directly satisfies that requirement for S3-stored data.
FedRAMP Moderate Baseline Rev 4: At the Moderate baseline, SC-28 requires protection of information at rest on digital media. SSE-S3 or SSE-KMS covers this for S3 storage within the system boundary, as part of the broader set of SC-family controls.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
s3_bucket_default_encryption_enabledAWS Config Managed Rules:
S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED,S3_DEFAULT_ENCRYPTION_KMSCheckov Check:
CKV_AWS_19Powerpipe Control:
aws_compliance.control.s3_bucket_default_encryption_enabledProwler Checks:
s3_bucket_default_encryption,s3_bucket_kms_encryptionAWS Security Hub Control:
S3.17Trivy Check:
AWS-0088
Last reviewed: 2026-03-09