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

Want us to handle this for you?

Get expert help →

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.

For S3, you cannot directly encrypt existing objects in place. You must copy the object to itself with encryption enabled using the COPY operation, or use S3 Batch Operations for bulk encryption. For EBS, you cannot encrypt an existing unencrypted volume directly. Create an encrypted snapshot of the volume, then create a new encrypted volume from that snapshot. For RDS, you cannot encrypt an existing unencrypted database. Create an encrypted snapshot, restore to a new encrypted instance, then switch your application to the new database.

S3 encryption has negligible performance impact as encryption/decryption is handled by AWS infrastructure. EBS encryption uses dedicated hardware on the instance with no noticeable performance difference. RDS encryption also uses hardware acceleration and typically shows no measurable performance degradation. KMS key operations add minimal latency (typically less than 10ms). The main performance consideration is KMS API rate limits, which are 50,000 requests per second per region for standard keys.

Use AWS KMS multi-region keys for data that needs to be accessed across regions. Create a primary key in your main region, then create replica keys in other regions. Multi-region keys share the same key material and key ID, allowing encrypted data to be decrypted in any region where a replica exists. For disaster recovery, replicate encrypted snapshots to other regions using multi-region keys to enable cross-region restoration without re-encryption.

Most compliance frameworks require or strongly recommend encryption at rest. PCI DSS requires encryption of cardholder data at rest. HIPAA requires technical safeguards for PHI, which typically means encryption. SOC 2 evaluates encryption as a key security control. GDPR requires appropriate technical measures including encryption for personal data. FedRAMP and NIST 800-53 mandate encryption for federal data. AWS provides compliance mappings showing which services and configurations meet specific framework requirements.

Need AWS Infrastructure Expertise?

Our team designs, migrates, and manages AWS environments with 24/7 support and cost optimization.