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
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.
This control is enforced automatically with Compliance.tf modules. Start free trial
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_policy grants es.amazonaws.com the required actions on the target log group: logs:PutLogEvents, logs:CreateLogStream, and logs: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_type values: INDEX_SLOW_LOGS, SEARCH_SLOW_LOGS, ES_APPLICATION_LOGS, and AUDIT_LOGS. This control requires ES_APPLICATION_LOGS specifically. Configuring only slow log types won't pass the check, even if multiple log_publishing_options blocks are present.
Using aws_opensearch_domain vs aws_elasticsearch_domain
After migrating to aws_opensearch_domain, the log_publishing_options block arguments are identical, but double-check the service principal in the CloudWatch Logs resource policy. In most regions it should still be es.amazonaws.com, not opensearchservice.amazonaws.com. Verify against your region's service endpoint before applying.
Explicitly setting enabled to false
Setting enabled = false in a log_publishing_options block fails this control even when cloudwatch_log_group_arn is present. Terraform won't error, but log delivery is off and nothing in the plan output will flag it as a problem. Always set enabled = true explicitly 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_enabled - AWS Config Managed Rule:
ELASTICSEARCH_LOGS_TO_CLOUDWATCH - Checkov Check:
CKV_AWS_84 - Powerpipe Controls:
aws_compliance.control.es_domain_error_logging_enabled,aws_compliance.control.es_domain_logs_to_cloudwatch - AWS Security Hub Controls:
ES.4,Opensearch.4 - KICS Query:
acb6b4e2-a086-4f35-aefd-4db6ea51ada2 - Trivy Check:
AWS-0042
Last reviewed: 2026-03-09