Back to blog
AWSBest PracticesCloud SecurityOperations & ComplianceStorage

S3 Bucket Has No Encryption Configuration: Detection, Fix, and Prevention

Learn why S3 buckets without a server-side encryption configuration fail audits, and how to enable SSE-KMS, re-encrypt objects, and enforce it in CI/CD.

TL;DR

This check flags S3 buckets that have no server-side encryption configuration, meaning data at rest may be stored without a defined encryption baseline. Fix it by enabling default encryption with SSE-KMS or SSE-S3 using a one-line put-bucket-encryption call.

Server-side encryption is one of those settings everyone assumes is on until an auditor asks for proof. The S3 Bucket Has No Encryption Configuration check looks at each bucket in your account and reports any that lack an explicit default encryption configuration. It is a small gap that becomes a big deal during compliance reviews and incident response.

Let's walk through what the check actually inspects, why an unencrypted bucket is a real risk, and how to remediate and prevent it across your fleet.


What this check detects

The s3_unencrypted check calls the S3 API to read each bucket's encryption configuration. If the bucket has no ServerSideEncryptionConfiguration set, the API returns a ServerSideEncryptionConfigurationNotFoundError, and Lensix flags the bucket as a finding.

Note: Since January 2023, AWS applies SSE-S3 encryption to all new buckets by default. So why does this check still matter? Because many older buckets predate that change, the default can be overridden, and most compliance frameworks require an explicit, documented encryption configuration rather than relying on an implicit account default. This check verifies that the configuration is actually present and intentional.

In short, the check is not asking "is the data encrypted?" but "have you explicitly defined and documented how this bucket encrypts data at rest?" An empty configuration is a governance gap even if AWS happens to be encrypting objects under the hood.


Why it matters

Encryption at rest is a defense-in-depth control. It does not stop someone with valid credentials from reading objects, but it protects against a specific class of failures: someone gaining access to the underlying storage, a misconfigured snapshot, or a stolen backup. Without a defined encryption baseline, you lose several practical protections.

Compliance and audit failures

Almost every major framework requires encryption at rest with documented key management:

  • PCI DSS requires protection of stored cardholder data (Requirement 3).
  • HIPAA treats encryption as an addressable safeguard for ePHI.
  • SOC 2 and ISO 27001 auditors expect explicit, demonstrable encryption controls.
  • FedRAMP mandates FIPS 140-2 validated encryption, which means KMS, not implicit defaults.

A bucket with no encryption configuration will not pass these audits, even if AWS is technically encrypting the objects. Auditors want the configuration on paper.

Key control and revocation

With SSE-S3, AWS manages the keys and you have no visibility or control. With SSE-KMS, you control the key policy, you get CloudTrail logs of every decrypt operation, and you can revoke access by disabling the key. A bucket with no configuration gives you none of that auditability. If you suspect data has been exposed, you cannot cut off access to it at the key level.

Warning: SSE-KMS adds per-request charges for KMS API calls and a monthly cost per customer managed key. For high-throughput buckets, enable S3 Bucket Keys to reduce KMS request volume by up to 99 percent. We cover this below.

Real-world scenario

Consider a logging bucket created years ago that collects access logs and application output. It was set up before default encryption existed and nobody ever revisited it. A developer later configures cross-account replication to a partner account for analytics. Because the source bucket had no defined encryption and no enforcing policy, objects flow out with whatever encryption the destination decides. When a breach investigation later asks "which keys protected this data and who could decrypt it," the answer is "we don't know." That ambiguity is exactly what this check exists to prevent.


How to fix it

You have two main options: SSE-S3 (AES-256, AWS-managed keys, no extra cost) or SSE-KMS (your keys, full audit trail). For anything sensitive or compliance-bound, prefer SSE-KMS.

Option 1: Enable SSE-S3 (quick baseline)

aws s3api put-bucket-encryption \
  --bucket my-example-bucket \
  --server-side-encryption-configuration '{
    "Rules": [
      {
        "ApplyServerSideEncryptionByDefault": {
          "SSEAlgorithm": "AES256"
        },
        "BucketKeyEnabled": true
      }
    ]
  }'

Option 2: Enable SSE-KMS with a customer managed key

aws s3api put-bucket-encryption \
  --bucket my-example-bucket \
  --server-side-encryption-configuration '{
    "Rules": [
      {
        "ApplyServerSideEncryptionByDefault": {
          "SSEAlgorithm": "aws:kms",
          "KMSMasterKeyID": "arn:aws:kms:us-east-1:111122223333:key/abcd1234-..."
        },
        "BucketKeyEnabled": true
      }
    ]
  }'

Note BucketKeyEnabled: true in both examples. This is the S3 Bucket Key feature that dramatically cuts KMS request costs by reducing the number of calls to KMS for object operations.

Verify the change

aws s3api get-bucket-encryption --bucket my-example-bucket

You should see your rule returned instead of the ServerSideEncryptionConfigurationNotFoundError.

Note: Enabling default encryption only affects new objects written after the change. Existing objects keep whatever encryption state they already had. To encrypt existing objects, you need to re-copy them in place. See the batch step below.

Re-encrypt existing objects

For a small bucket, a copy-in-place loop works. For large buckets, use S3 Batch Operations with a copy job.

Danger: Copying objects in place overwrites them and creates new versions. On a versioned bucket this increases storage and cost. On a non-versioned bucket, an interrupted copy can leave objects in an inconsistent state. Test on a non-production bucket first and confirm you have a backup or versioning enabled before running this against production data.

# Small bucket: re-copy each object in place to apply new encryption
aws s3 cp s3://my-example-bucket/ s3://my-example-bucket/ \
  --recursive \
  --sse aws:kms \
  --sse-kms-key-id arn:aws:kms:us-east-1:111122223333:key/abcd1234-...

Console steps

  1. Open the S3 console and select the bucket.
  2. Go to the Properties tab.
  3. Scroll to Default encryption and click Edit.
  4. Choose Server-side encryption with AWS KMS keys (SSE-KMS).
  5. Select your KMS key and enable Bucket Key.
  6. Click Save changes.

How to prevent it from happening again

Fixing one bucket is easy. Keeping every future bucket compliant is the real work. Encode encryption into your provisioning and your guardrails so a misconfigured bucket cannot reach production.

Terraform

resource "aws_s3_bucket" "logs" {
  bucket = "my-example-bucket"
}

resource "aws_s3_bucket_server_side_encryption_configuration" "logs" {
  bucket = aws_s3_bucket.logs.id

  rule {
    apply_server_side_encryption_by_default {
      sse_algorithm     = "aws:kms"
      kms_master_key_id = aws_kms_key.s3.arn
    }
    bucket_key_enabled = true
  }
}

CloudFormation

{
  "Type": "AWS::S3::Bucket",
  "Properties": {
    "BucketEncryption": {
      "ServerSideEncryptionConfiguration": [
        {
          "ServerSideEncryptionByDefault": {
            "SSEAlgorithm": "aws:kms",
            "KMSMasterKeyID": "arn:aws:kms:us-east-1:111122223333:key/abcd1234-..."
          },
          "BucketKeyEnabled": true
        }
      ]
    }
  }
}

Block unencrypted uploads with a bucket policy

Default encryption handles objects that arrive without an encryption header. To go further and reject any upload that explicitly asks for the wrong encryption, add a deny policy.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "DenyWrongEncryptionHeader",
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::my-example-bucket/*",
      "Condition": {
        "StringNotEquals": {
          "s3:x-amz-server-side-encryption": "aws:kms"
        }
      }
    }
  ]
}

Catch it in CI/CD

Run a policy-as-code scan on your IaC before it merges. Tools like Checkov, tfsec, or OPA/Conftest can fail the pipeline when a bucket lacks encryption.

# Checkov check for unencrypted S3 buckets in a Terraform plan
checkov -d . --check CKV_AWS_19

Tip: Pair the CI scan with an organization-wide guardrail. An AWS Config rule (s3-bucket-server-side-encryption-enabled) deployed via a Config conformance pack or an SCP that denies s3:CreateBucket without encryption-related tags closes the gap for resources created outside your pipelines. Lensix continuously re-scans so anything that slips past both still surfaces as a finding.


Best practices

  • Prefer SSE-KMS with customer managed keys for anything sensitive. You get key rotation, granular key policies, and a full CloudTrail audit trail of decrypt activity.
  • Always enable S3 Bucket Keys when using SSE-KMS. It cuts KMS request costs significantly with no downside for default encryption scenarios.
  • Enable automatic key rotation on your KMS keys so material rotates yearly without manual effort.
  • Scope your KMS key policy tightly. Only the roles that need to read the bucket should have kms:Decrypt. This is where KMS encryption earns its keep over SSE-S3.
  • Combine encryption with Block Public Access. Encryption protects data at rest, but a public bucket policy can still expose objects to anyone who can authenticate against the key. Treat the two controls as a pair.
  • Standardize through modules. Wrap your bucket creation in a Terraform module that bakes in encryption, versioning, and access blocks so teams cannot create a bucket the wrong way.
  • Re-encrypt legacy objects when you remediate, rather than only fixing the default for new writes. Otherwise old data keeps its old, possibly absent, encryption state.

Default encryption is the floor, not the ceiling. The goal is a documented, auditable, revocable encryption posture for every bucket, not a vague assumption that AWS is handling it.

Enable default encryption everywhere, enforce it in code, and let continuous scanning catch the buckets that slip through. That combination turns a recurring audit headache into a solved problem.