Skip to main content
RDS Enhanced Monitoring collects operating system-level metrics (CPU, memory, disk I/O, network) from the hypervisor running the DB instance. These metrics are published to CloudWatch Logs under the RDSOSMetrics log group and are distinct from the standard CloudWatch metrics available at the instance level. This example shows how to use an externally created IAM role for enhanced monitoring, demonstrating the monitoring_role_arn approach as an alternative to create_monitoring_role = true.

Configuration

main.tf
provider "aws" {
  region = local.region
}

data "aws_availability_zones" "available" {}

locals {
  name   = "enhanced-monitoring"
  region = "eu-west-1"

  vpc_cidr = "10.0.0.0/16"
  azs      = slice(data.aws_availability_zones.available.names, 0, 3)

  tags = {
    Name       = local.name
    Example    = local.name
    Repository = "https://github.com/terraform-aws-modules/terraform-aws-rds"
  }
}

################################################################################
# RDS Module
################################################################################

module "db" {
  source = "terraform-aws-modules/rds/aws"

  identifier = local.name

  # All available versions: http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/CHAP_MySQL.html#MySQL.Concepts.VersionMgmt
  engine               = "mysql"
  engine_version       = "8.0"
  family               = "mysql8.0" # DB parameter group
  major_engine_version = "8.0"      # DB option group
  instance_class       = "db.t4g.large"

  allocated_storage     = 20
  max_allocated_storage = 100

  db_name  = "completeMysql"
  username = "complete_mysql"
  port     = 3306

  multi_az               = true
  db_subnet_group_name   = module.vpc.database_subnet_group
  vpc_security_group_ids = [module.security_group.security_group_id]

  maintenance_window              = "Mon:00:00-Mon:03:00"
  backup_window                   = "03:00-06:00"
  enabled_cloudwatch_logs_exports = ["audit", "general"]

  backup_retention_period = 0
  skip_final_snapshot     = true
  deletion_protection     = false

  # Enhanced monitoring
  monitoring_interval = 30
  monitoring_role_arn = aws_iam_role.rds_enhanced_monitoring.arn

  performance_insights_enabled          = true
  performance_insights_retention_period = 7
  create_monitoring_role                = true

  tags = local.tags
}

################################################################################
# Create an IAM role to allow enhanced monitoring
################################################################################

resource "aws_iam_role" "rds_enhanced_monitoring" {
  name_prefix        = "rds-enhanced-monitoring-"
  assume_role_policy = data.aws_iam_policy_document.rds_enhanced_monitoring.json
}

resource "aws_iam_role_policy_attachment" "rds_enhanced_monitoring" {
  role       = aws_iam_role.rds_enhanced_monitoring.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole"
}

data "aws_iam_policy_document" "rds_enhanced_monitoring" {
  statement {
    actions = [
      "sts:AssumeRole",
    ]

    effect = "Allow"

    principals {
      type        = "Service"
      identifiers = ["monitoring.rds.amazonaws.com"]
    }
  }
}

################################################################################
# Supporting Resources
################################################################################

module "vpc" {
  source  = "terraform-aws-modules/vpc/aws"
  version = "~> 6.0"

  name = local.name
  cidr = local.vpc_cidr

  azs              = local.azs
  public_subnets   = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k)]
  private_subnets  = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 3)]
  database_subnets = [for k, v in local.azs : cidrsubnet(local.vpc_cidr, 8, k + 6)]

  create_database_subnet_group = true

  tags = local.tags
}

module "security_group" {
  source  = "terraform-aws-modules/security-group/aws"
  version = "~> 5.0"

  name        = local.name
  description = "Enhanced monitoring MySQL example security group"
  vpc_id      = module.vpc.vpc_id

  # ingress
  ingress_with_cidr_blocks = [
    {
      from_port   = 3306
      to_port     = 3306
      protocol    = "tcp"
      description = "MySQL access from within VPC"
      cidr_blocks = module.vpc.vpc_cidr_block
    },
  ]

  tags = local.tags
}

monitoring_interval values

The monitoring_interval variable controls how frequently (in seconds) Enhanced Monitoring metrics are sampled and sent to CloudWatch Logs. Valid values are:
ValueBehavior
0Enhanced Monitoring disabled (default)
1Metrics collected every second
5Metrics collected every 5 seconds
10Metrics collected every 10 seconds
15Metrics collected every 15 seconds
30Metrics collected every 30 seconds
60Metrics collected every 60 seconds
Lower intervals provide more granular data but increase the volume of data written to CloudWatch Logs. For most production workloads, 30 or 60 seconds provides sufficient granularity.
When monitoring_interval is set to any non-zero value, you must supply either monitoring_role_arn (an existing role ARN) or set create_monitoring_role = true (to have the module create the role automatically). Failing to provide a role when monitoring_interval > 0 will result in an apply error.

Two approaches to the monitoring IAM role

Option 1: Let the module create the role

Set create_monitoring_role = true and optionally customize the role name:
module "db" {
  source = "terraform-aws-modules/rds/aws"

  # ...

  monitoring_interval            = 30
  create_monitoring_role         = true
  monitoring_role_name           = "my-rds-monitoring-role"
  monitoring_role_use_name_prefix = true
  monitoring_role_description    = "RDS enhanced monitoring role for production"
}
The module creates an IAM role with the AmazonRDSEnhancedMonitoringRole managed policy and the trust policy for monitoring.rds.amazonaws.com.

Option 2: Bring your own role

Create the IAM role outside the module and pass its ARN via monitoring_role_arn:
resource "aws_iam_role" "rds_enhanced_monitoring" {
  name_prefix        = "rds-enhanced-monitoring-"
  assume_role_policy = data.aws_iam_policy_document.rds_enhanced_monitoring.json
}

resource "aws_iam_role_policy_attachment" "rds_enhanced_monitoring" {
  role       = aws_iam_role.rds_enhanced_monitoring.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole"
}

data "aws_iam_policy_document" "rds_enhanced_monitoring" {
  statement {
    actions = ["sts:AssumeRole"]
    effect  = "Allow"
    principals {
      type        = "Service"
      identifiers = ["monitoring.rds.amazonaws.com"]
    }
  }
}

module "db" {
  source = "terraform-aws-modules/rds/aws"

  # ...

  monitoring_interval = 30
  monitoring_role_arn = aws_iam_role.rds_enhanced_monitoring.arn
}
This approach is useful when you need to share a single monitoring role across multiple RDS instances in the same account, or when your organization manages IAM resources separately from application infrastructure.

CloudWatch log exports

The enabled_cloudwatch_logs_exports variable controls which database log types are shipped to CloudWatch Logs. The example exports audit and general logs for MySQL. Available log types by engine:
EngineValid log types
MySQLgeneral, slowquery, error, audit
PostgreSQLpostgresql, upgrade
SQL Servererror, agent, trace
Oraclealert, audit, listener, trace
When create_cloudwatch_log_group = true, the module creates a CloudWatch log group for each log type listed in enabled_cloudwatch_logs_exports. The log groups are retained for cloudwatch_log_group_retention_in_days days (default: 7).
module "db" {
  source = "terraform-aws-modules/rds/aws"

  # ...

  enabled_cloudwatch_logs_exports         = ["postgresql", "upgrade"]
  create_cloudwatch_log_group             = true
  cloudwatch_log_group_retention_in_days  = 30
  cloudwatch_log_group_kms_key_id         = aws_kms_key.cloudwatch.arn
}

Outputs

OutputDescription
db_instance_addressDNS hostname of the RDS instance
db_instance_endpointFull connection endpoint including port
db_instance_identifierThe RDS instance identifier
db_instance_engine_version_actualThe resolved engine version running
db_instance_portDatabase port
db_instance_nameThe database name
db_instance_usernameMaster username (sensitive)
db_instance_master_user_secret_arnARN of the Secrets Manager secret
db_parameter_group_idThe parameter group name
db_parameter_group_arnARN of the parameter group
db_instance_cloudwatch_log_groupsMap of CloudWatch log group names and ARNs

Build docs developers (and LLMs) love