Elasticsearch domain error logging to CloudWatch Logs should be enabled
Elasticsearch error logs carry diagnostic information that's difficult to reconstruct after the fact: index mapping conflicts, deprecated API warnings, query failures, and JVM garbage collection issues. Without routing these to CloudWatch Logs, operators have no durable, searchable record of domain-level errors and are left querying cluster health APIs after something has already gone wrong.
CloudWatch integration also unlocks metric filters and alarms on error patterns, so teams can catch misconfigurations or degraded nodes before they cascade into data loss or an availability incident.
Retrofit consideration
Enabling log publishing on an existing domain does not cause downtime, but the target CloudWatch log group must already exist with a resource policy granting es.amazonaws.com the actions logs:PutLogEvents, logs:CreateLogStream, and logs:CreateLogGroup. Terraform will apply cleanly without it, but no logs will flow.
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 "opensearch" {
source = "nistcsf.compliance.tf/terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
If you use terraform-aws-modules/opensearch/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 "opensearch" {
source = "terraform-aws-modules/opensearch/aws"
version = ">=2.0.0,<3.0.0"
advanced_security_options = {
enabled = true
internal_user_database_enabled = true
master_user_options = {
master_user_name = "admin"
master_user_password = "P0fix-Test-2026!"
}
}
auto_tune_options = {
desired_state = "DISABLED"
}
cluster_config = {
dedicated_master_enabled = false
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
domain_endpoint_options = {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "abc123"
ebs_options = {
ebs_enabled = true
volume_size = 20
volume_type = "gp3"
}
engine_version = "OpenSearch_2.11"
}
Use AWS provider resources directly. See docs for the resources involved: aws_opensearch_domain.
resource "aws_opensearch_domain" "this" {
advanced_security_options {
enabled = true
internal_user_database_enabled = true
master_user_options {
master_user_name = "admin"
master_user_password = "ChangeMe123!"
}
}
auto_tune_options {
desired_state = "DISABLED"
}
cluster_config {
instance_count = 1
instance_type = "t3.small.search"
zone_awareness_enabled = false
}
cognito_options {
enabled = true
identity_pool_id = "us-east-1:12345678-1234-1234-1234-123456789012"
role_arn = "arn:aws:iam::123456789012:role/example-role"
user_pool_id = "us-east-1_AbCdEfGhI"
}
domain_endpoint_options {
enforce_https = true
tls_security_policy = "Policy-Min-TLS-1-2-2019-07"
}
domain_name = "pofix-abc123"
ebs_options {
ebs_enabled = true
volume_size = 10
volume_type = "gp3"
}
encrypt_at_rest {
enabled = true
}
engine_version = "OpenSearch_2.11"
log_publishing_options {
cloudwatch_log_group_arn = local.es_log_group_arn
log_type = "AUDIT_LOGS"
}
log_publishing_options {
cloudwatch_log_group_arn = local.es_log_group_arn
log_type = "ES_APPLICATION_LOGS"
}
log_publishing_options {
cloudwatch_log_group_arn = local.es_log_group_arn
log_type = "SEARCH_SLOW_LOGS"
}
log_publishing_options {
cloudwatch_log_group_arn = local.es_log_group_arn
log_type = "INDEX_SLOW_LOGS"
}
node_to_node_encryption {
enabled = true
}
vpc_options {
security_group_ids = ["sg-12345678"]
subnet_ids = ["subnet-12345678"]
}
}
What this control checks
The control checks for a log_publishing_options block on aws_elasticsearch_domain or aws_opensearch_domain with log_type = "ES_APPLICATION_LOGS" and cloudwatch_log_group_arn set to a valid log group ARN. Setting enabled = false within that block fails, as does omitting the block entirely. A companion aws_cloudwatch_log_resource_policy must grant es.amazonaws.com write access to the target log group; without it, the domain appears configured in Terraform but produces no log data.
Common pitfalls
Missing CloudWatch Logs resource policy
Log delivery silently fails if no
aws_cloudwatch_log_resource_policygrantses.amazonaws.comthe required actions on the target log group:logs:PutLogEvents,logs:CreateLogStream, andlogs:CreateLogGroup. The domain shows as configured in Terraform but produces no log data. Manage the resource policy in the same module as the domain so the dependency is explicit.Confusing log_type values
There are four valid
log_typevalues:INDEX_SLOW_LOGS,SEARCH_SLOW_LOGS,ES_APPLICATION_LOGS, andAUDIT_LOGS. This control requiresES_APPLICATION_LOGSspecifically. Configuring only slow log types won't pass the check, even if multiplelog_publishing_optionsblocks are present.Using aws_opensearch_domain vs aws_elasticsearch_domain
After migrating to
aws_opensearch_domain, thelog_publishing_optionsblock arguments are identical, but double-check the service principal in the CloudWatch Logs resource policy. In most regions it should still bees.amazonaws.com, notopensearchservice.amazonaws.com. Verify against your region's service endpoint before applying.Explicitly setting enabled to false
Setting
enabled = falsein alog_publishing_optionsblock fails this control even whencloudwatch_log_group_arnis present. Terraform won't error, but log delivery is off and nothing in the plan output will flag it as a problem. Always setenabled = trueexplicitly rather than relying on the default.
Audit evidence
An auditor expects the AWS Config rule elasticsearch-logs-to-cloudwatch (or equivalent Security Hub check) evaluating each Elasticsearch domain as COMPLIANT. A populated log group with recent ES application entries confirms the pipeline is active. The aws es describe-elasticsearch-domain-config output should show LogPublishingOptions with an ES_APPLICATION_LOGS key, Enabled: true, and a valid CloudWatchLogsLogGroupArn.
For deeper assurance, auditors may also request the CloudWatch Logs resource policy confirming es.amazonaws.com write access, and a sample Logs Insights query showing error events are actually flowing.
Framework-specific interpretation
NIST Cybersecurity Framework v2.0: DE.CM (Continuous Monitoring) and DE.AE (Adverse Event Analysis) both call for sustained visibility into events that could affect system integrity or availability. Routing Elasticsearch error logs to CloudWatch covers that directly: operators get a queryable record of index errors, deprecated API usage, and JVM issues, and can build metric filters to alert on anomalous patterns before they escalate.
Related controls
Tool mappings
Use these identifiers to cross-reference this control across tools, reports, and evidence.
Compliance.tf Control:
es_domain_error_logging_enabledAWS Config Managed Rule:
ELASTICSEARCH_LOGS_TO_CLOUDWATCHCheckov Check:
CKV_AWS_84Powerpipe Controls:
aws_compliance.control.es_domain_error_logging_enabled,aws_compliance.control.es_domain_logs_to_cloudwatchAWS Security Hub Controls:
ES.4,Opensearch.4KICS Query:
acb6b4e2-a086-4f35-aefd-4db6ea51ada2Trivy Check:
AWS-0042
Last reviewed: 2026-03-09