RDS for SQL Server DB instances should publish logs to CloudWatch Logs
SQL Server database logs contain error messages, agent job history, and optionally SQL Server Audit records. Without CloudWatch Logs integration, these logs exist only on the RDS instance's local storage, where they rotate and eventually disappear. You lose the ability to set CloudWatch alarms on error patterns, run Logs Insights queries across your fleet, or retain logs beyond the instance's lifecycle.
Forwarding logs to CloudWatch decouples log retention from instance operations. If someone deletes or resizes an instance, the logs survive independently. That matters during incident investigations where you need weeks or months of history.
Retrofit consideration
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 = "registry.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"]
}This control is enforced automatically with Compliance.tf modules. Start free trial
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"]
}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 = ["agent", "error"]
engine = "sqlserver-ex"
identifier = "pofix-abc123"
instance_class = "db.t3.small"
license_model = "license-included"
monitoring_interval = 60
monitoring_role_arn = "arn:aws:iam::123456789012:role/example-role"
password = "ChangeMe123!"
skip_final_snapshot = true
username = "dbadmin"
}What this control checks
This control validates that the aws_db_instance resource for a SQL Server engine family has enabled_cloudwatch_logs_exports set with at least one valid log type. For SQL Server engines (sqlserver-ee, sqlserver-se, sqlserver-ex, sqlserver-web), the accepted log types are "error" and "agent". An instance passes when enabled_cloudwatch_logs_exports contains one or both values. It fails when the argument is omitted entirely or set to an empty list. The control may optionally require specific log types depending on the parameter configuration. No additional IAM or CloudWatch resources need to be declared in Terraform; RDS automatically creates the /aws/rds/instance/<instance-id>/<log-type> log groups.
Common pitfalls
Empty list still fails
Terraform treats enabled_cloudwatch_logs_exports = [] the same as omitting the argument entirely. No log types are sent to the API, and the control reports a failure. You must include at least one valid value, for example ["error"].
SQL Server Audit logs require additional setup
If your organization needs SQL Server Audit, configure it through a custom option group with the SQLSERVER_AUDIT option (via aws_db_option_group) and the relevant SQL Server settings. Don't add "audit" to enabled_cloudwatch_logs_exports for RDS SQL Server. The only valid CloudWatch export types are "error" and "agent".
CloudWatch log group retention defaults to never expire
RDS auto-creates CloudWatch log groups with no expiration policy. Left alone, logs accumulate indefinitely and CloudWatch Logs storage costs grow with them. Pre-create the log groups in Terraform using aws_cloudwatch_log_group with retention_in_days set before enabling exports.
Engine filter matters
Get the engine check wrong in a conditional module and this control silently misapplies. All four SQL Server variants must be handled explicitly: sqlserver-ee, sqlserver-se, sqlserver-ex, and sqlserver-web. A prefix match on sqlserver alone can collide with future engine names and produce unexpected behavior.
Audit evidence
Auditors expect AWS Config rule evaluation results showing each SQL Server RDS instance as COMPLIANT. Supporting evidence includes the RDS instance detail page in the AWS Console (under the "Logs & events" or "Configuration" tab) showing the published log types, or the output of aws rds describe-db-instances where the EnabledCloudwatchLogsExports array contains "error" and/or "agent".
CloudWatch Logs console screenshots showing active log groups under /aws/rds/instance/ with recent log streams confirm log delivery is working. Retention policy settings on those log groups show logs are preserved for the required period.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
- Compliance.tf Control:
rds_db_instance_sql_server_logging_enabled - AWS Config Managed Rule:
RDS_SQL_SERVER_LOGS_TO_CLOUDWATCH - Checkov Check:
CKV_AWS_129 - Powerpipe Control:
aws_compliance.control.rds_db_instance_sql_server_logging_enabled - Prowler Check:
rds_instance_integration_cloudwatch_logs - AWS Security Hub Controls:
RDS.40,RDS.9 - KICS Query:
8d7f7b8c-6c7c-40f8-baa6-62006c6c7b56
Last reviewed: 2026-03-09