This check flags Elasticsearch (OpenSearch) domains running without audit or application logging. Without those logs you have no record of who queried your data or why a node fell over, which kills incident response. Fix it by enabling log publishing to CloudWatch Logs and turning on audit logs via fine-grained access control.
Amazon Elasticsearch Service (now Amazon OpenSearch Service) often ends up holding some of the most sensitive data in your stack: application logs, user activity, search indexes built from customer records. Despite that, logging on the domain itself is off by default. The elasticsearch_nologging check catches domains that have no audit logging and no application logging configured, leaving you blind to both security events and operational failures.
This post walks through what the check looks for, why an unlogged search cluster is a real problem, and how to fix it cleanly with the CLI, console, and Terraform.
What this check detects
The check inspects each Elasticsearch/OpenSearch domain in your AWS account and looks at its LogPublishingOptions. It flags a domain when neither of the following is enabled:
- Audit logs — records of authentication attempts, queries, and changes made to indexes. These depend on fine-grained access control being enabled.
- Application logs — Elasticsearch error logs and (optionally) search and index slow logs, useful for debugging and capacity issues.
A domain with no log publishing options set at all is the most common failing case. You can confirm the current state with a quick describe call:
aws opensearch describe-domain \
--domain-name my-search-domain \
--query 'DomainStatus.LogPublishingOptions'
If that returns null or an empty object, the domain is logging nothing.
Note: AWS renamed the service from Elasticsearch Service to OpenSearch Service in 2021. The older aws es CLI commands still work for legacy domains, but the aws opensearch commands are the current ones. Examples below use both where it helps.
Why it matters
Logging on a search cluster is one of those things nobody misses until they desperately need it. Here is what you lose by skipping it.
You cannot answer "who accessed this data?"
Audit logs are the only record of which principal ran which query against which index. If a credential leaks or an internal user pulls data they should not, audit logs are how you reconstruct the blast radius. Without them, an incident response turns into guesswork, and you may be legally unable to confirm whether sensitive records were actually exposed.
Danger: If your domain holds PII, PHI, cardholder data, or anything covered by GDPR, HIPAA, or PCI DSS, the absence of audit logging can put you in direct breach of those frameworks. Several require you to log and retain access to protected data. A breach you cannot scope is treated as a breach of everything.
Operational failures become invisible
Application logs surface JVM out-of-memory errors, mapping conflicts, and rejected bulk requests. Slow logs tell you which queries are dragging the cluster down. With logging off, the first sign of trouble is usually a red cluster status and a paging alert, with no breadcrumbs explaining how you got there.
Detection coverage gaps
If audit logs flow into CloudWatch Logs, you can build metric filters and alarms on suspicious patterns, such as a spike in failed authentications or queries from an unexpected IAM role. No logs means no alarms, and an attacker who pivots to your search cluster operates in total silence.
How to fix it
Enabling logging has two layers. Application logs can be turned on right away. Audit logs require fine-grained access control (FGAC) to be enabled on the domain first.
Step 1: Create the CloudWatch log groups
OpenSearch publishes to CloudWatch Logs, so create the destination groups first.
aws logs create-log-group --log-group-name /aws/opensearch/my-search-domain/audit-logs
aws logs create-log-group --log-group-name /aws/opensearch/my-search-domain/application-logs
aws logs create-log-group --log-group-name /aws/opensearch/my-search-domain/search-slow-logs
aws logs create-log-group --log-group-name /aws/opensearch/my-search-domain/index-slow-logs
Step 2: Grant OpenSearch permission to write
OpenSearch needs a resource policy on CloudWatch Logs allowing the service to deliver log events. Save this as logs-policy.json:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowOpenSearchLogs",
"Effect": "Allow",
"Principal": { "Service": "es.amazonaws.com" },
"Action": [
"logs:PutLogEvents",
"logs:CreateLogStream"
],
"Resource": "arn:aws:logs:us-east-1:111122223333:log-group:/aws/opensearch/my-search-domain/*:*"
}
]
}
aws logs put-resource-policy \
--policy-name opensearch-logs-policy \
--policy-document file://logs-policy.json
Step 3: Enable application and slow logs
aws opensearch update-domain-config \
--domain-name my-search-domain \
--log-publishing-options '{
"ES_APPLICATION_LOGS": {
"CloudWatchLogsLogGroupArn": "arn:aws:logs:us-east-1:111122223333:log-group:/aws/opensearch/my-search-domain/application-logs",
"Enabled": true
},
"SEARCH_SLOW_LOGS": {
"CloudWatchLogsLogGroupArn": "arn:aws:logs:us-east-1:111122223333:log-group:/aws/opensearch/my-search-domain/search-slow-logs",
"Enabled": true
},
"INDEX_SLOW_LOGS": {
"CloudWatchLogsLogGroupArn": "arn:aws:logs:us-east-1:111122223333:log-group:/aws/opensearch/my-search-domain/index-slow-logs",
"Enabled": true
}
}'
Step 4: Enable fine-grained access control (required for audit logs)
Audit logs only work when FGAC is on. If your domain does not have it yet, enabling it is a configuration change.
Warning: Turning on fine-grained access control reconfigures the domain and can take significant time on large clusters, sometimes with a blue/green deployment. Test in a non-production domain first and schedule the change during a maintenance window. FGAC also requires node-to-node encryption and encryption at rest, which may force a redeploy if they are currently off.
aws opensearch update-domain-config \
--domain-name my-search-domain \
--advanced-security-options '{
"Enabled": true,
"InternalUserDatabaseEnabled": true,
"MasterUserOptions": {
"MasterUserName": "admin",
"MasterUserPassword": "REPLACE_WITH_STRONG_PASSWORD"
}
}'
Step 5: Enable audit logs
Once FGAC is active, switch on audit log publishing.
aws opensearch update-domain-config \
--domain-name my-search-domain \
--log-publishing-options '{
"AUDIT_LOGS": {
"CloudWatchLogsLogGroupArn": "arn:aws:logs:us-east-1:111122223333:log-group:/aws/opensearch/my-search-domain/audit-logs",
"Enabled": true
}
}'
Finally, configure which events are captured from the OpenSearch Dashboards Security plugin or the audit config API. By default audit logging captures both the request and the authenticated user, which is what you want for compliance.
Tip: Audit logs can be high volume on a busy cluster. Exclude noisy categories like GRANTED_PRIVILEGES for read traffic if cost is a concern, and keep FAILED_LOGIN, AUTHENTICATED, and index write events. Set a retention policy on the log group so you are not paying to store logs forever: aws logs put-retention-policy --log-group-name ... --retention-in-days 90.
Doing it in Terraform
If you manage domains as code, bake logging into the resource so it never drifts. Here is the relevant configuration for the aws_opensearch_domain resource:
resource "aws_cloudwatch_log_group" "opensearch_audit" {
name = "/aws/opensearch/my-search-domain/audit-logs"
retention_in_days = 90
}
resource "aws_cloudwatch_log_resource_policy" "opensearch" {
policy_name = "opensearch-logs-policy"
policy_document = jsonencode({
Version = "2012-10-17"
Statement = [{
Effect = "Allow"
Principal = { Service = "es.amazonaws.com" }
Action = ["logs:PutLogEvents", "logs:CreateLogStream"]
Resource = "${aws_cloudwatch_log_group.opensearch_audit.arn}:*"
}]
})
}
resource "aws_opensearch_domain" "this" {
domain_name = "my-search-domain"
advanced_security_options {
enabled = true
internal_user_database_enabled = true
master_user_options {
master_user_name = "admin"
master_user_password = var.master_password
}
}
encrypt_at_rest { enabled = true }
node_to_node_encryption { enabled = true }
log_publishing_options {
cloudwatch_log_group_arn = aws_cloudwatch_log_group.opensearch_audit.arn
log_type = "AUDIT_LOGS"
}
log_publishing_options {
cloudwatch_log_group_arn = aws_cloudwatch_log_group.opensearch_application.arn
log_type = "ES_APPLICATION_LOGS"
}
}
How to prevent it from happening again
Manual remediation fixes today's domain. Policy enforcement stops the next one from shipping without logs.
Gate it in CI/CD
Run a policy check against your Terraform plan before it applies. With Checkov you can fail the pipeline when a domain lacks audit logging:
checkov -d ./infra --check CKV_AWS_317,CKV_AWS_318
Those checks cover OpenSearch audit and application log publishing. Wire the command into your pull request pipeline so a non-compliant plan blocks the merge.
Use an OPA/Conftest rule for custom guardrails
package opensearch
deny[msg] {
resource := input.resource.aws_opensearch_domain[name]
not has_audit_log(resource)
msg := sprintf("OpenSearch domain '%s' must publish AUDIT_LOGS", [name])
}
has_audit_log(resource) {
some block in resource.log_publishing_options
block.log_type == "AUDIT_LOGS"
}
Detect drift continuously
Code gates only catch what flows through the pipeline. Someone can still toggle logging off in the console during an incident and forget to turn it back on. Continuous scanning with Lensix re-evaluates the elasticsearch_nologging check on a schedule and surfaces any domain that regresses, so you catch out-of-band changes that never touched your IaC.
Tip: Pair the check with an AWS Config rule (elasticsearch-logs-to-cloudwatch) for an in-account second layer. Config can trigger an automatic remediation via SSM Automation to re-enable logging, giving you self-healing on top of detection.
Best practices
- Always enable fine-grained access control on domains holding sensitive data. It is the prerequisite for audit logs and gives you index-level and document-level authorization.
- Ship logs out of the account. Forward CloudWatch Logs to a central security account or SIEM so an attacker with access to the workload account cannot tamper with the audit trail.
- Set retention deliberately. Match log group retention to your compliance requirements, then archive to S3 with Glacier for long-term storage instead of paying CloudWatch rates.
- Alarm on the signals that matter. Build metric filters for failed logins, access from unexpected principals, and bulk delete operations, and route them to your on-call channel.
- Never expose domains publicly. Logging is a detective control, not a preventive one. Keep domains in a VPC and restrict access with security groups and resource policies so audit logs stay the backstop rather than the front line.
Turning on logging is a small, mostly one-time change with an outsized payoff. The day you need to answer "what did the attacker touch?" you will be very glad the logs were already running.

