Skip to content

RDS DB instance encryption at rest should be enabled

Unencrypted RDS instances store data, automated backups, read replicas, and snapshots in plaintext on the underlying EBS volumes. Anyone who gains physical access to the storage media or exfiltrates a snapshot to another AWS account can read the data directly. Enabling encryption at rest wraps every block with an AES-256 envelope key managed through AWS KMS, and the performance overhead on most instance classes is negligible.

Encryption at rest cannot be enabled on an existing unencrypted instance without downtime. You must snapshot the instance, copy the snapshot with encryption enabled, and restore from the encrypted copy. Catching this requirement early in the infrastructure lifecycle prevents painful migrations later.

Retrofit consideration

Enabling encryption on an existing unencrypted RDS instance requires creating an encrypted snapshot copy and restoring a new instance from it. This involves downtime, a new endpoint, and potential DNS or connection string changes across every consumer of that database.

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 "rds" {
  source  = "soc2.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "pcidss.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "hipaa.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "gdpr.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "nist80053.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "nistcsf.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "fedrampmoderate.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "cis.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "cisv80ig1.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "nist800171.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "awscontroltower.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "cisacyberessentials.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "nydfs23.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "cisv500.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "ffiec.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "eugmpannex11.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "cfrpart11.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "rbicybersecurity.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "hipaasecurity2003.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "nistcsfv11.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "nist80053rev4.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

module "rds" {
  source  = "pcidssv321.compliance.tf/terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]
}

If you use terraform-aws-modules/rds/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 "rds" {
  source  = "terraform-aws-modules/rds/aws"
  version = ">=7.0.0"

  allocated_storage      = 20
  db_name                = "myapp"
  db_subnet_group_name   = "example-db-subnet-group"
  engine                 = "mysql"
  engine_version         = "8.0.41"
  family                 = "mysql8.0"
  identifier             = "abc123"
  instance_class         = "db.t3.micro"
  major_engine_version   = "8.0"
  password_wo            = "change-me-in-production"
  skip_final_snapshot    = true
  username               = "dbadmin"
  vpc_security_group_ids = ["sg-12345678"]

  storage_encrypted = true
}

Use AWS provider resources directly. See docs for the resources involved: aws_db_instance.

resource "aws_db_instance" "this" {
  allocated_storage               = 20
  enabled_cloudwatch_logs_exports = ["general", "slowquery"]
  engine                          = "mysql"
  identifier                      = "pofix-abc123"
  instance_class                  = "db.t3.micro"
  monitoring_interval             = 60
  monitoring_role_arn             = "arn:aws:iam::123456789012:role/example-role"
  password                        = "ChangeMe123!"
  skip_final_snapshot             = true
  username                        = "dbadmin"

  storage_encrypted = true
}

What this control checks

The control validates that the aws_db_instance resource has storage_encrypted set to true. When omitted, it defaults to false and the control fails. You can optionally specify a kms_key_id pointing to a customer-managed KMS key ARN. If kms_key_id is omitted but storage_encrypted is true, RDS uses the default aws/rds service key. Both configurations pass. Setting storage_encrypted = false or omitting it entirely fails the control. For Aurora clusters, the equivalent argument is storage_encrypted on the aws_rds_cluster resource, though this control targets aws_db_instance specifically.

Common pitfalls

  • Encryption cannot be toggled on existing instances

    Unlike S3 buckets, you cannot flip storage_encrypted from false to true on a running aws_db_instance. Terraform will plan a destroy-and-recreate. If you lack prevent_destroy lifecycle rules or forget to snapshot first, you risk data loss.

  • Default KMS key limits cross-account snapshot sharing

    When kms_key_id is omitted, RDS encrypts with the AWS-managed aws/rds key. Snapshots encrypted with that key cannot be shared across AWS accounts. If you need cross-account DR or migration capability, specify a customer-managed KMS key from the start, retrofitting it later follows the same snapshot-copy-restore path as enabling encryption itself.

  • Read replicas inherit encryption from the source

    You cannot create an encrypted replica from an unencrypted primary. A replica created from an unencrypted source will itself be unencrypted regardless of what you specify on the replica resource. The source must go through the snapshot-copy-restore workflow first.

  • Small instance classes may not support encryption

    Before setting storage_encrypted = true on a legacy workload, verify instance class support. Some older micro-tier classes (for example, db.t2.micro in certain regions) did not support encryption. Most current-generation classes do, but a failed deployment on a legacy class is harder to recover from than checking the compatibility matrix first.

Audit evidence

Auditors expect Config rule evaluation results from the managed rule rds-storage-encrypted, showing all RDS instances as COMPLIANT. Supporting evidence includes aws rds describe-db-instances output filtered to the StorageEncrypted and KmsKeyId fields for each instance, confirming encryption is active and identifying which KMS key protects each database.

For deeper assurance, provide the KMS key policy for any customer-managed keys referenced by KmsKeyId, showing that key access is appropriately restricted. CloudTrail events for CreateDBInstance calls confirm that StorageEncrypted was set to true at creation time.

Framework-specific interpretation

SOC 2: CC6.1 and CC6.7 both reference encryption as a mechanism to restrict logical access and protect confidential information. Database storage encryption supports these criteria, though full CC6.1 coverage depends on the surrounding access control environment, key policies, and IAM boundaries that govern who can decrypt.

PCI DSS v4.0: For environments storing cardholder data in RDS, Requirement 3.4 mandates that PAN be rendered unreadable wherever it is stored. AES-256 encryption through KMS satisfies the strong cryptography requirement for data at rest on those volumes.

HIPAA Omnibus Rule 2013: Under 45 CFR 164.312(a)(2)(iv), encryption of ePHI at rest is an addressable specification, meaning covered entities must either implement it or document why it isn't reasonable and appropriate for their environment. Encrypting RDS storage with KMS satisfies the specification and, where the encryption key is not compromised, qualifies for the breach notification safe harbor under the HIPAA Omnibus Rule.

GDPR: Article 32 explicitly names encryption as an appropriate technical measure for protecting personal data proportional to the risk. For RDS instances holding personal data of EU residents, enabling storage encryption limits the exposure of a breach and supports the accountability principle under Article 5(2).

NIST SP 800-53 Rev 5: SC-28 calls for protecting information at rest using cryptographic mechanisms. RDS storage encryption uses FIPS 140-3 validated modules (140-2 where applicable) across EBS volumes, automated backups, and snapshots, which is exactly what SC-28 asks for.

NIST Cybersecurity Framework v2.0: PR.DS-01 expects data at rest to be protected through cryptographic controls. Enabling storage_encrypted on RDS instances is the direct implementation of that expectation for the database tier.

FedRAMP Moderate Baseline Rev 4: At the Moderate baseline, SC-28 is required, not tailorable. Federal data stored in RDS must be protected at rest using FIPS-validated cryptographic modules, and storage_encrypted = true is how you meet that requirement for the database tier.

Tool mappings

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

  • Compliance.tf Control: rds_db_instance_encryption_at_rest_enabled

  • AWS Config Managed Rule: RDS_STORAGE_ENCRYPTED

  • Checkov Check: CKV_AWS_16

  • Powerpipe Control: aws_compliance.control.rds_db_instance_encryption_at_rest_enabled

  • Prowler Check: rds_instance_storage_encrypted

  • AWS Security Hub Control: RDS.3

  • KICS Query: 08bd0760-8752-44e1-9779-7bb369b2b4e4

  • Trivy Checks: AWS-0079, AWS-0080

Last reviewed: 2026-03-09