Skip to content

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_configuration resource. The inline server_side_encryption_configuration block inside aws_s3_bucket was 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-object or S3 Inventory to identify pre-existing unencrypted objects, then re-copy using aws s3 cp --sse or S3 Batch Operations.

  • AWS-managed key vs. customer-managed key

    Omitting kms_master_key_id when sse_algorithm is aws:kms defaults to the aws/s3 AWS-managed key. That passes this control but will fail any check requiring a customer-managed CMK, which stricter FedRAMP and PCI mappings typically do. Set kms_master_key_id explicitly 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-encryption header specifying a weaker algorithm, and the bucket default won't override it. To enforce a minimum algorithm, add a bucket policy that denies s3:PutObject requests where the s3:x-amz-server-side-encryption condition 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.

Tool mappings

Use these identifiers to cross-reference this control across tools, reports, and evidence.

  • Compliance.tf Control: s3_bucket_default_encryption_enabled

  • AWS Config Managed Rules: S3_BUCKET_SERVER_SIDE_ENCRYPTION_ENABLED, S3_DEFAULT_ENCRYPTION_KMS

  • Checkov Check: CKV_AWS_19

  • Powerpipe Control: aws_compliance.control.s3_bucket_default_encryption_enabled

  • Prowler Checks: s3_bucket_default_encryption, s3_bucket_kms_encryption

  • AWS Security Hub Control: S3.17

  • Trivy Check: AWS-0088

Last reviewed: 2026-03-09