OpenSearch in 2026: Feature Divergence from Elastic, Plugins, and Migration

Five years is a long time in the search engine world. When Amazon forked Elasticsearch 7.10.2 in early 2021 and called it OpenSearch, most people assumed it would just be a lagging copy with AWS branding slapped on. That assumption is now badly outdated.

By 2026, OpenSearch and Elasticsearch have diverged enough that you genuinely cannot treat them as drop-in replacements for each other — and if you’re still running one while wishing you had the other, the migration cost is real. This article covers what actually changed, which OpenSearch plugins are worth knowing, and how to run a migration that doesn’t end in a 3 AM incident.

Official GitHub: https://github.com/opensearch-project/OpenSearch


The License Plot Twist You Need to Know

Before anything else: in February 2024, Elastic reversed course and re-licensed Elasticsearch under AGPL v3. That’s a real open-source license. OpenSearch remained Apache 2.0. Both are now technically "open source" again, which muddies the original moral argument for the fork.

But here’s the thing — the codebases have already diverged significantly. The license question is largely settled. What matters in 2026 is which one fits your use case, your stack, and your operational capability. That’s a technical question now, not a political one.


Where the Codebases Actually Stand

At the time of writing, OpenSearch ships in the 2.x series (currently 2.18.x), while Elasticsearch is deep into 8.x. They share a common ancestor at ES 7.10, but the distance from that ancestor is now substantial in both directions.

Shared foundation still present:

  • Lucene under the hood (both track different Lucene versions now)
  • REST-based query DSL — most basic match, bool, term, aggs queries still work identically
  • Index format compatible only within version bands — cross-cluster replication between ES and OS is not supported

Where OpenSearch went its own way:

  • Security is bundled and free. Full RBAC, TLS everywhere, field- and document-level security — no separate subscription required
  • ML Commons framework for running inference models directly on the cluster (sentence transformers, sparse encoders, cross-encoders)
  • Neural Search plugin built on top of ML Commons — k-NN vector search is first-class, not bolted on
  • PPL (Piped Processing Language) — a SQL-like query language borrowed from Splunk semantics
  • Alerting, Anomaly Detection, Index Management all ship as plugins but are maintained by the OpenSearch project itself and don’t require a paid tier
  • OpenSearch Dashboards as a hard fork of Kibana 7.10, now with its own plugin API

Where Elasticsearch pulled ahead:

  • ES|QL — Elasticsearch’s new query language introduced in 8.11, substantially more capable than PPL for complex analytics
  • Logsdb index mode — a storage format optimized for log data with significant compression improvements
  • Inference endpoints abstraction (connecting external LLMs to search pipelines)
  • Kibana Lens and the overall analytics UX is more polished
  • Elasticsearch Serverless, which abstracts compute from storage in a way AWS OpenSearch Serverless mimics but doesn’t replicate identically

Neither is "ahead" overall. They’re optimizing for different things. OpenSearch is excellent for self-hosters who want the full feature set without paying Elastic’s licensing fees. Elasticsearch is better if you’re already on Elastic Cloud and want the freshest query tooling.


OpenSearch Plugins Worth Knowing

The OpenSearch plugin system is how nearly all differentiated features are delivered. Some ship bundled in the distribution, others you install separately. Here are the ones that actually matter in production.

Security Plugin

Installed by default. Handles authentication (basic, LDAP, SAML, OpenID Connect), authorization (roles, role mappings), TLS enforcement, and audit logging. In 2021 this was a dealbreaker advantage over "vanilla" Elasticsearch, which locked security behind a subscription. With AGPL Elasticsearch now, the gap has narrowed — but OpenSearch Security is still mature and battle-tested.

Gotcha: If you’re running OpenSearch locally for development and don’t want TLS headaches, set plugins.security.disabled: true in opensearch.yml. Just don’t do this in production. Obviously.

ML Commons

This is OpenSearch’s most ambitious bet. It lets you register, deploy, and invoke ML models directly within the cluster using a task-based API. Supported model formats include TorchScript and ONNX. You can load sentence transformers, run them on ingestion pipelines to generate embeddings, and do semantic search without a separate inference service.

# Register a pre-trained model from the ML Commons model registry
POST /_plugins/_ml/models/_register
{
  "name": "huggingface/sentence-transformers/all-MiniLM-L6-v2",
  "version": "1.0.1",
  "model_format": "TORCH_SCRIPT"
}

Gotcha: ML models run on the data nodes by default. If you enable ML on a cluster that also serves search traffic, you will starve your search JVM heap during model loading. Use dedicated ML nodes:

# opensearch.yml on dedicated ML node
node.roles: [ ml ]
plugins.ml_commons.only_run_on_ml_node: true

k-NN Plugin

Vector search. FAISS and NMSLIB under the hood for approximate nearest neighbor, Lucene for exact (brute-force) search on smaller datasets. The hnsw engine is the default recommendation for production — good recall at reasonable latency.

PUT /my-embeddings-index
{
  "settings": {
    "index": {
      "knn": true
    }
  },
  "mappings": {
    "properties": {
      "embedding": {
        "type": "knn_vector",
        "dimension": 384,
        "method": {
          "engine": "faiss",
          "name": "hnsw",
          "parameters": {
            "m": 16,
            "ef_construction": 100
          }
        }
      }
    }
  }
}

Gotcha: ef_construction and m are set at index creation time and cannot be changed without reindexing. Undersizing m (default is 16, minimum useful is 8) tanks recall. Oversizing bloats the graph and slows indexing. Profile before you commit.

Index State Management (ISM)

Automated index lifecycle policies — rollover, transition, deletion. The equivalent of Elasticsearch ILM. The API is different, the concepts are the same.

PUT _plugins/_ism/policies/logs-policy
{
  "policy": {
    "description": "Hot-warm-delete for logs",
    "default_state": "hot",
    "states": [
      {
        "name": "hot",
        "actions": [
          {
            "rollover": {
              "min_size": "10gb",
              "min_index_age": "1d"
            }
          }
        ],
        "transitions": [{ "state_name": "warm" }]
      },
      {
        "name": "warm",
        "actions": [{ "warm_migration": {} }],
        "transitions": [
          {
            "state_name": "delete",
            "conditions": { "min_index_age": "30d" }
          }
        ]
      },
      {
        "name": "delete",
        "actions": [{ "delete": {} }],
        "transitions": []
      }
    ]
  }
}

Running OpenSearch with Docker Compose

Here’s a production-shaped single-node setup. Yes, single-node — most self-hosted use cases don’t need a 3-node cluster and the operational overhead that comes with it. If you’re not indexing hundreds of GB/day and don’t need HA, one node is fine.

# docker-compose.yml
version: "3.9"

services:
  opensearch:
    image: opensearchproject/opensearch:2.18.0
    container_name: opensearch
    environment:
      - cluster.name=local-cluster
      - node.name=opensearch-node1
      - discovery.type=single-node
      - bootstrap.memory_lock=true
      # Heap: set to half of available RAM, never more than 31GB
      - OPENSEARCH_JAVA_OPTS=-Xms2g -Xmx2g
      # Disable security for local dev only — remove in prod
      # - plugins.security.disabled=true
      - OPENSEARCH_INITIAL_ADMIN_PASSWORD=${OPENSEARCH_ADMIN_PASSWORD}
    ulimits:
      memlock:
        soft: -1
        hard: -1
      nofile:
        soft: 65536
        hard: 65536
    volumes:
      - opensearch-data:/usr/share/opensearch/data
      - ./opensearch.yml:/usr/share/opensearch/config/opensearch.yml:ro
    ports:
      - "9200:9200"
      - "9600:9600"  # Performance Analyzer
    restart: unless-stopped

  opensearch-dashboards:
    image: opensearchproject/opensearch-dashboards:2.18.0
    container_name: opensearch-dashboards
    ports:
      - "5601:5601"
    environment:
      - OPENSEARCH_HOSTS=["https://opensearch:9200"]
    depends_on:
      - opensearch
    restart: unless-stopped

volumes:
  opensearch-data:
    driver: local
# opensearch.yml — minimal production config
cluster.name: local-cluster
node.name: opensearch-node1

# Network
network.host: 0.0.0.0
http.port: 9200

# Discovery
discovery.type: single-node

# Security (TLS) — adjust paths to your actual certs
plugins.security.ssl.transport.pemcert_filepath: esnode.pem
plugins.security.ssl.transport.pemkey_filepath: esnode-key.pem
plugins.security.ssl.transport.pemtrustedcas_filepath: root-ca.pem
plugins.security.ssl.http.enabled: true
plugins.security.ssl.http.pemcert_filepath: esnode.pem
plugins.security.ssl.http.pemkey_filepath: esnode-key.pem
plugins.security.ssl.http.pemtrustedcas_filepath: root-ca.pem
plugins.security.allow_unsafe_democertificates: true  # remove in prod
plugins.security.allow_default_init_securityindex: true

# Index settings
action.auto_create_index: false  # explicit index creation only

# Memory
indices.memory.index_buffer_size: 20%

Gotcha: The vm.max_map_count kernel parameter must be at least 262144. If you hit max virtual memory areas vm.max_map_count [65530] is too low, run this on the host (not inside the container):

sudo sysctl -w vm.max_map_count=262144
# Make it permanent:
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf

Migrating from Elasticsearch to OpenSearch

This is where most people get burned. The migration path depends heavily on which ES version you’re coming from.

From Elasticsearch 7.x

This is the cleanest migration. Since OpenSearch forked from 7.10, the index format is broadly compatible. The recommended approach is snapshot-and-restore.

Step 1: Take a snapshot on the Elasticsearch side

# Register an S3 (or filesystem) snapshot repository on ES
PUT /_snapshot/my-backup
{
  "type": "fs",
  "settings": {
    "location": "/mnt/snapshots"
  }
}

# Trigger a snapshot
PUT /_snapshot/my-backup/snapshot_pre_migration?wait_for_completion=true
{
  "indices": "*",
  "ignore_unavailable": true,
  "include_global_state": false
}

Step 2: Register the same repository on OpenSearch and restore

# Same API — OpenSearch is compatible here
PUT /_snapshot/my-backup
{
  "type": "fs",
  "settings": {
    "location": "/mnt/snapshots"
  }
}

POST /_snapshot/my-backup/snapshot_pre_migration/_restore
{
  "indices": "*",
  "ignore_unavailable": true,
  "include_global_state": false
}

Gotcha: Index aliases, templates, and ILM policies are in global state. If you used include_global_state: false, you need to recreate these manually. Export them from ES first:

# Dump all templates
curl -s https://es-host:9200/_template?pretty > templates.json

# Dump all aliases
curl -s https://es-host:9200/_alias?pretty > aliases.json

ILM policies from Elasticsearch don’t translate to ISM on OpenSearch. You’ll rewrite them using the ISM API shown earlier.

From Elasticsearch 8.x

This is harder. ES 8.x introduced a new index format (Lucene 9+), and you cannot directly restore ES 8.x snapshots into OpenSearch 2.x.

Your options, in order of preference:

Option A: Reindex via Logstash/Data Prepper

Use OpenSearch Data Prepper (the ES-to-OS ETL tool) or Logstash with the OpenSearch output plugin to reindex live. This is zero-downtime but adds latency and complexity.

# data-prepper-pipeline.yaml
source:
  elasticsearch:
    hosts: ["https://es-host:9200"]
    username: elastic
    password: ${ES_PASSWORD}
    index_regex: "logs-.*"
    fetch_size: 1000

sink:
  opensearch:
    hosts: ["https://os-host:9200"]
    username: admin
    password: ${OS_PASSWORD}
    index: "%{[_index]}"
    bulk_size: 5

Option B: Dual-write during transition

Point your application at both clusters simultaneously, write to both, let OpenSearch catch up on historical data via reindex, then cut over. This is operationally expensive but gives you a real rollback path.

Option C: Accept a cold-migration window

For non-critical data or log archives where downtime is acceptable, export to JSON (via _search scroll API or elasticdump), then bulk-import to OpenSearch. Simple, slow, no dependencies.

Gotcha for ES 8.x migrations: Security configuration is completely different. ES 8.x uses native realm with elastic superuser and auto-generated TLS. OpenSearch uses the Security plugin with its own internal user DB and demo certificates. You cannot migrate users directly — recreate them via OpenSearch’s security API.


Production Gotchas: The List No One Writes

JVM heap sizing. Half of available RAM, hard cap at 31GB. Above 31GB the JVM can’t use compressed ordinary object pointers (CompressedOops), and you actually lose performance. A box with 64GB RAM gets two 28GB heaps if you’re running two nodes — don’t give it all to one.

Slow log your queries before tuning. Before you touch any index settings, enable slow logs and let them run for a day:

index.search.slowlog.threshold.query.warn: 5s
index.search.slowlog.threshold.query.info: 1s

Most "OpenSearch is slow" problems are actually unmapped fields, wildcard queries on unanalyzed text, or missing keyword mappings — none of which are fixed by hardware.

Dynamic mapping in production. Turn it off. A single misdirected log field that arrives as a string instead of a number will cause a mapping conflict that breaks your entire data stream. Use dynamic: strict on production indices and define your mappings explicitly.

Snapshot before any upgrade. OpenSearch minor upgrades (2.15 → 2.18) are generally rolling and safe. But the upgrade path from 1.x to 2.x required a full reindex for some index configurations. Always have a recoverable snapshot before touching the version.

Dashboard plugin compatibility. OpenSearch Dashboards plugins are versioned against a specific OS release. A plugin built for 2.15 will refuse to load on 2.18. Maintain a list of which plugins you use and check compatibility before upgrading the cluster.


Should You Switch in 2026?

If you’re currently on self-hosted Elasticsearch 7.x and running a tight budget: migrate to OpenSearch. You get identical functionality plus the bundled security and ML Commons ecosystem without paying for a subscription.

If you’re on Elasticsearch 8.x and deeply invested in ES|QL, Kibana Lens, or Elastic’s AI Search features: the migration cost likely isn’t worth it. ES 8.x is a genuinely better product for analytics-heavy workloads.

If you’re building something new on your own infrastructure: OpenSearch is the pragmatic choice. Apache 2.0 license, active development, no license ambiguity, strong AWS backing, and a plugin ecosystem that covers 90% of what you’d need from Elastic’s paid tiers.

The fork that was supposed to be a political statement turned into a legitimately competitive product. Five years in, OpenSearch earned its place — not because of the license drama, but because the engineering actually delivered.

👁 Views: 202,922 · Unique visitors: 57,427