Skip to content

OpenSearch domains node-to-node encryption should be enabled

OpenSearch clusters distribute data across multiple nodes for indexing and search. Without node-to-node encryption, that data traverses the internal VPC network unencrypted, exposed to packet capture or man-in-the-middle attacks by anything with network-level access. Clusters storing PII, financial records, or log data containing credentials are the obvious targets.

Enabling this adds a small latency overhead but closes an entire class of internal eavesdropping risk. AWS charges nothing extra for it.

Retrofit consideration

Enabling node-to-node encryption on an existing domain triggers a blue/green deployment. AWS spins up a new cluster behind the scenes, which can take considerable time on large domains and may cause temporary performance degradation. For domains originally created as Elasticsearch domains before the OpenSearch migration, you may need to recreate the domain entirely if the instance type doesn't support this feature.

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  = "pcidss.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"
}

module "opensearch" {
  source  = "hipaa.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"
}

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"
}

module "opensearch" {
  source  = "nist800171.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"
}

module "opensearch" {
  source  = "nydfs23.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"
}

module "opensearch" {
  source  = "eugmpannex11.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"
}

module "opensearch" {
  source  = "cfrpart11.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"
}

module "opensearch" {
  source  = "hipaasecurity2003.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"
}

module "opensearch" {
  source  = "nistcsfv11.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"

  node_to_node_encryption = {
    enabled = true
  }
}

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"
  }

  vpc_options {
    security_group_ids = ["sg-12345678"]
    subnet_ids         = ["subnet-12345678"]
  }

  node_to_node_encryption {
    enabled = true
  }
}

What this control checks

In the aws_opensearch_domain resource, the node_to_node_encryption block must be present with enabled set to true. It fails when the block is omitted or enabled is false. No KMS key configuration is needed; this feature uses AWS-managed TLS certificates for inter-node communication. This is separate from encryption at rest (the encrypt_at_rest block) and HTTPS enforcement (the domain_endpoint_options block). All three should typically be enabled together, but this control validates only the node-to-node setting.

Common pitfalls

  • Legacy Elasticsearch resource type

    The deprecated aws_elasticsearch_domain resource uses the same node_to_node_encryption block with the same enabled flag, but you should migrate to aws_opensearch_domain. Beyond the compliance paperwork, the legacy resource won't support newer OpenSearch engine versions, so this catches up with you eventually regardless.

  • Instance type restrictions

    Terraform will return an API error if you try to enable node-to-node encryption on an instance type that doesn't support it. T2 instances are the common offender. Before applying, confirm your engine version and instance family against current AWS OpenSearch Service documentation, especially if you inherited a domain you didn't provision.

  • Cannot disable once enabled

    Once enabled, node-to-node encryption cannot be turned off. The API rejects any attempt to set enabled = false on an existing domain. Treat this as a one-way door when planning migrations or rollbacks.

  • Blue/green deployment timeout

    Set explicit timeouts in the resource block before applying to large existing domains. The blue/green deployment this triggers routinely exceeds Terraform's default update timeout, and Terraform will mark the apply as failed even though the deployment is still running successfully in the background. Something like update = "120m" is a reasonable starting point.

Audit evidence

An auditor expects to see the AWS Config rule opensearch-node-to-node-encryption-check showing compliant evaluations across all in-scope OpenSearch domains. The domain configuration page in the AWS Console shows node-to-node encryption status under the "Security configuration" tab. The CLI equivalent is aws opensearch describe-domain --domain-name <name>, which should return "NodeToNodeEncryptionOptions": {"Enabled": true}.

For continuous compliance, Security Hub findings showing this control as passing across all accounts and regions, or equivalent output from a CSPM tool, provides strong ongoing evidence.

Framework-specific interpretation

PCI DSS v4.0: Requirement 4.2.1 mandates strong cryptography for cardholder data in transit, including traffic that stays within a private VPC. If your OpenSearch domain indexes payment data, inter-node traffic counts and must be encrypted.

HIPAA Omnibus Rule 2013: The transmission security standard at 45 CFR 164.312(e)(1) requires protecting ePHI in transit. Node-to-node encryption covers the path ePHI takes as it replicates and moves between cluster nodes, which would otherwise traverse the VPC unencrypted.

NIST Cybersecurity Framework v2.0: PR.DS covers data-in-transit protection. Encrypting inter-node traffic limits the blast radius of an internal network compromise, which is precisely the exposure PR.DS targets for data at rest and in motion within the infrastructure.

Tool mappings

Use these identifiers to cross-reference this control across tools, reports, and evidence.

  • Compliance.tf Control: opensearch_domain_node_to_node_encryption_enabled

  • AWS Config Managed Rules: ELASTICSEARCH_NODE_TO_NODE_ENCRYPTION_CHECK, OPENSEARCH_NODE_TO_NODE_ENCRYPTION_CHECK

  • Checkov Check: CKV_AWS_6

  • Powerpipe Controls: aws_compliance.control.es_domain_node_to_node_encryption_enabled, aws_compliance.control.opensearch_domain_node_to_node_encryption_enabled

  • Prowler Check: opensearch_service_domains_node_to_node_encryption_enabled

  • AWS Security Hub Controls: ES.3, Opensearch.3

  • KICS Query: 967eb3e6-26fc-497d-8895-6428beb6e8e2

  • Trivy Check: AWS-0043

Last reviewed: 2026-03-09