How to Encrypt Data at Rest in AWS S3, EBS, and RDS

Complete guide to AWS data encryption at rest covering S3 bucket encryption (SSE-S3, SSE-KMS, SSE-C), EBS volume encryption, RDS database encryption, and KMS key management best practices.

10 min readUpdated 2026-01-13

Encryption at rest protects your data from unauthorized access even if storage media is physically compromised or accessed by unauthorized users. AWS provides multiple encryption options for S3, EBS, RDS, and other storage services, allowing you to meet compliance requirements and security best practices with minimal operational overhead.

This article is part of our comprehensive Cloud Security Tips for 2026 guide covering essential practices for protecting your cloud environment.

Understanding AWS Encryption Options

Encryption TypeKey ManagementAudit CapabilityCost
SSE-S3AWS ManagedLimitedFree
SSE-KMSAWS KMSFull CloudTrail$1/key/month + API
SSE-CCustomer ManagedNone (your keys)Free
Client-SideCustomer ManagedYour implementationFree

S3 Bucket Encryption

Enable Default Encryption (SSE-S3)

# Enable SSE-S3 default encryption on bucket
aws s3api put-bucket-encryption \
  --bucket my-secure-bucket \
  --server-side-encryption-configuration '{
    "Rules": [{
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "AES256"
      },
      "BucketKeyEnabled": false
    }]
  }'

# Verify encryption configuration
aws s3api get-bucket-encryption --bucket my-secure-bucket

Enable Default Encryption (SSE-KMS)

# Create KMS key for S3 encryption
aws kms create-key \
  --description "S3 encryption key" \
  --key-usage ENCRYPT_DECRYPT \
  --key-spec SYMMETRIC_DEFAULT

# Get the key ID from response
KEY_ID="12345678-1234-1234-1234-123456789012"

# Create alias for easier management
aws kms create-alias \
  --alias-name alias/s3-encryption \
  --target-key-id $KEY_ID

# Enable SSE-KMS default encryption with S3 Bucket Keys
aws s3api put-bucket-encryption \
  --bucket my-secure-bucket \
  --server-side-encryption-configuration '{
    "Rules": [{
      "ApplyServerSideEncryptionByDefault": {
        "SSEAlgorithm": "aws:kms",
        "KMSMasterKeyID": "alias/s3-encryption"
      },
      "BucketKeyEnabled": true
    }]
  }'

Terraform Configuration for S3 Encryption

resource "aws_kms_key" "s3_key" {
  description             = "S3 bucket encryption key"
  deletion_window_in_days = 30
  enable_key_rotation     = true
}

resource "aws_kms_alias" "s3_key_alias" {
  name          = "alias/s3-encryption"
  target_key_id = aws_kms_key.s3_key.key_id
}

resource "aws_s3_bucket" "secure_bucket" {
  bucket = "my-secure-bucket"
}

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

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

# Deny unencrypted uploads
resource "aws_s3_bucket_policy" "require_encryption" {
  bucket = aws_s3_bucket.secure_bucket.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid       = "DenyUnencryptedUploads"
        Effect    = "Deny"
        Principal = "*"
        Action    = "s3:PutObject"
        Resource  = "${aws_s3_bucket.secure_bucket.arn}/*"
        Condition = {
          StringNotEquals = {
            "s3:x-amz-server-side-encryption" = ["aws:kms", "AES256"]
          }
        }
      }
    ]
  })
}

Upload with Client-Specified Encryption

# Upload with SSE-KMS and specific key
aws s3 cp document.pdf s3://my-secure-bucket/ \
  --sse aws:kms \
  --sse-kms-key-id alias/s3-encryption

# Upload with SSE-C (customer-provided key)
# Key must be base64-encoded 256-bit key
aws s3 cp document.pdf s3://my-secure-bucket/ \
  --sse-c \
  --sse-c-key $(openssl rand -base64 32)

Encrypt Existing S3 Objects

Existing unencrypted objects must be copied to encrypt them:

# Copy single object with encryption
aws s3 cp s3://my-bucket/file.txt s3://my-bucket/file.txt \
  --sse aws:kms \
  --sse-kms-key-id alias/s3-encryption

# Encrypt all objects in bucket using S3 Batch Operations
# First, create inventory of unencrypted objects
aws s3api list-objects-v2 \
  --bucket my-bucket \
  --query 'Contents[?ServerSideEncryption==`null`].Key'

Bulk Encryption with S3 Batch Operations

# Create manifest file listing objects to encrypt
# Then create batch job
aws s3control create-job \
  --account-id 123456789012 \
  --operation '{
    "S3PutObjectCopy": {
      "TargetResource": "arn:aws:s3:::my-bucket",
      "StorageClass": "STANDARD",
      "SSEAwsKmsKeyId": "arn:aws:kms:us-east-1:123456789012:key/12345678-1234-1234-1234-123456789012"
    }
  }' \
  --manifest '{
    "Spec": {"Format": "S3BatchOperations_CSV_20180820", "Fields": ["Bucket", "Key"]},
    "Location": {"ObjectArn": "arn:aws:s3:::manifest-bucket/manifest.csv", "ETag": "abc123"}
  }' \
  --report '{
    "Bucket": "arn:aws:s3:::report-bucket",
    "Prefix": "batch-reports/",
    "Format": "Report_CSV_20180820",
    "Enabled": true
  }' \
  --priority 10 \
  --role-arn arn:aws:iam::123456789012:role/S3BatchRole

EBS Volume Encryption

Enable Default EBS Encryption

# Enable default EBS encryption for the region
aws ec2 enable-ebs-encryption-by-default

# Verify default encryption is enabled
aws ec2 get-ebs-encryption-by-default

# Set default KMS key for EBS
aws ec2 modify-ebs-default-kms-key-id \
  --kms-key-id alias/ebs-encryption

# Get current default key
aws ec2 get-ebs-default-kms-key-id

Create Encrypted EBS Volume

# Create encrypted volume with default key
aws ec2 create-volume \
  --availability-zone us-east-1a \
  --size 100 \
  --volume-type gp3 \
  --encrypted

# Create encrypted volume with specific KMS key
aws ec2 create-volume \
  --availability-zone us-east-1a \
  --size 100 \
  --volume-type gp3 \
  --encrypted \
  --kms-key-id alias/ebs-encryption

Encrypt Existing Unencrypted Volume

# Step 1: Create snapshot of unencrypted volume
aws ec2 create-snapshot \
  --volume-id vol-0123456789abcdef0 \
  --description "Snapshot for encryption"

# Step 2: Copy snapshot with encryption
aws ec2 copy-snapshot \
  --source-region us-east-1 \
  --source-snapshot-id snap-0123456789abcdef0 \
  --destination-region us-east-1 \
  --encrypted \
  --kms-key-id alias/ebs-encryption \
  --description "Encrypted copy"

# Step 3: Create new volume from encrypted snapshot
aws ec2 create-volume \
  --snapshot-id snap-encrypted123456 \
  --availability-zone us-east-1a \
  --volume-type gp3

# Step 4: Attach new volume and migrate data

Terraform Configuration for EBS Encryption

resource "aws_ebs_encryption_by_default" "enabled" {
  enabled = true
}

resource "aws_kms_key" "ebs_key" {
  description             = "EBS volume encryption key"
  deletion_window_in_days = 30
  enable_key_rotation     = true
}

resource "aws_ebs_default_kms_key" "default" {
  key_arn = aws_kms_key.ebs_key.arn
}

resource "aws_ebs_volume" "encrypted_volume" {
  availability_zone = "us-east-1a"
  size              = 100
  type              = "gp3"
  encrypted         = true
  kms_key_id        = aws_kms_key.ebs_key.arn

  tags = {
    Name = "encrypted-data-volume"
  }
}

RDS Database Encryption

Create Encrypted RDS Instance

# Create encrypted RDS instance
aws rds create-db-instance \
  --db-instance-identifier my-encrypted-db \
  --db-instance-class db.t3.medium \
  --engine postgres \
  --engine-version 15.4 \
  --master-username admin \
  --master-user-password "SecurePassword123!" \
  --allocated-storage 100 \
  --storage-encrypted \
  --kms-key-id alias/rds-encryption \
  --vpc-security-group-ids sg-12345678 \
  --db-subnet-group-name my-subnet-group

Encrypt Existing RDS Instance

# Step 1: Create snapshot
aws rds create-db-snapshot \
  --db-instance-identifier my-unencrypted-db \
  --db-snapshot-identifier pre-encryption-snapshot

# Step 2: Copy snapshot with encryption
aws rds copy-db-snapshot \
  --source-db-snapshot-identifier pre-encryption-snapshot \
  --target-db-snapshot-identifier encrypted-snapshot \
  --kms-key-id alias/rds-encryption

# Step 3: Restore encrypted instance
aws rds restore-db-instance-from-db-snapshot \
  --db-instance-identifier my-encrypted-db \
  --db-snapshot-identifier encrypted-snapshot \
  --db-instance-class db.t3.medium

# Step 4: Update application connection strings
# Step 5: Delete old unencrypted instance after verification

Terraform Configuration for RDS Encryption

resource "aws_kms_key" "rds_key" {
  description             = "RDS encryption key"
  deletion_window_in_days = 30
  enable_key_rotation     = true
}

resource "aws_db_instance" "encrypted_db" {
  identifier              = "my-encrypted-db"
  engine                  = "postgres"
  engine_version          = "15.4"
  instance_class          = "db.t3.medium"
  allocated_storage       = 100
  storage_type            = "gp3"

  username                = "admin"
  password                = var.db_password

  storage_encrypted       = true
  kms_key_id              = aws_kms_key.rds_key.arn

  vpc_security_group_ids  = [aws_security_group.db.id]
  db_subnet_group_name    = aws_db_subnet_group.main.name

  backup_retention_period = 7
  skip_final_snapshot     = false
  final_snapshot_identifier = "my-encrypted-db-final"
}

KMS Key Management Best Practices

Enable Key Rotation

# Enable automatic key rotation (annually)
aws kms enable-key-rotation --key-id alias/my-encryption-key

# Verify rotation is enabled
aws kms get-key-rotation-status --key-id alias/my-encryption-key

Create Key Policy for Separation of Duties

aws kms put-key-policy \
  --key-id alias/my-encryption-key \
  --policy-name default \
  --policy '{
    "Version": "2012-10-17",
    "Statement": [
      {
        "Sid": "Enable IAM Policies",
        "Effect": "Allow",
        "Principal": {"AWS": "arn:aws:iam::123456789012:root"},
        "Action": "kms:*",
        "Resource": "*"
      },
      {
        "Sid": "Allow Key Administrators",
        "Effect": "Allow",
        "Principal": {"AWS": "arn:aws:iam::123456789012:role/KeyAdminRole"},
        "Action": [
          "kms:Create*",
          "kms:Describe*",
          "kms:Enable*",
          "kms:List*",
          "kms:Put*",
          "kms:Update*",
          "kms:Revoke*",
          "kms:Disable*",
          "kms:Get*",
          "kms:Delete*",
          "kms:TagResource",
          "kms:UntagResource"
        ],
        "Resource": "*"
      },
      {
        "Sid": "Allow Key Usage",
        "Effect": "Allow",
        "Principal": {"AWS": "arn:aws:iam::123456789012:role/ApplicationRole"},
        "Action": [
          "kms:Encrypt",
          "kms:Decrypt",
          "kms:GenerateDataKey*",
          "kms:DescribeKey"
        ],
        "Resource": "*"
      }
    ]
  }'

Audit Encryption Status

#!/bin/bash
# Encryption audit script

echo "=== AWS Encryption Audit ==="

# Check S3 buckets
echo -e "\n1. Unencrypted S3 Buckets:"
for bucket in $(aws s3api list-buckets --query 'Buckets[].Name' --output text); do
  ENCRYPTION=$(aws s3api get-bucket-encryption --bucket "$bucket" 2>&1)
  if [[ "$ENCRYPTION" == *"ServerSideEncryptionConfigurationNotFoundError"* ]]; then
    echo "  UNENCRYPTED: $bucket"
  fi
done

# Check EBS volumes
echo -e "\n2. Unencrypted EBS Volumes:"
aws ec2 describe-volumes \
  --filters Name=encrypted,Values=false \
  --query 'Volumes[*].[VolumeId,Size,State]' \
  --output table

# Check RDS instances
echo -e "\n3. Unencrypted RDS Instances:"
aws rds describe-db-instances \
  --query 'DBInstances[?StorageEncrypted==`false`].[DBInstanceIdentifier,Engine,DBInstanceClass]' \
  --output table

# Check default EBS encryption
echo -e "\n4. Default EBS Encryption Status:"
aws ec2 get-ebs-encryption-by-default

echo -e "\n=== Audit Complete ==="

Best Practices Summary

PracticeRecommendation
S3 BucketsEnable SSE-KMS with S3 Bucket Keys by default
EBS VolumesEnable default encryption in all regions
RDS DatabasesAlways create with encryption enabled
KMS KeysEnable automatic annual rotation
Key PoliciesSeparate admin and usage permissions
MonitoringAudit unencrypted resources monthly

Frequently Asked Questions

Find answers to common questions

SSE-S3 uses Amazon-managed keys with AES-256 encryption. It's the simplest option with no additional cost but limited audit capabilities. SSE-KMS uses AWS Key Management Service for key management, providing CloudTrail logging of key usage, key rotation policies, and granular access controls. SSE-C (Customer-Provided Keys) requires you to manage and provide encryption keys with each request, giving maximum control but highest operational complexity. For most organizations, SSE-KMS provides the best balance of security, compliance, and manageability.

Need Professional Help?

Our team of experts can help you implement and configure these solutions for your organization.