Understanding Elasticsearch and OpenSearch
Elasticsearch is a distributed, RESTful search and analytics engine built on top of Apache Lucene. For years, it was the go-to solution for full-text search, log analytics, and real-time data exploration. OpenSearch is an open-source fork of Elasticsearch 7.10.2, created by AWS in 2021 after Elastic changed its licensing model. OpenSearch maintains Apache 2.0 licensing while offering a compatible API, making it a compelling alternative for organizations concerned about licensing restrictions and vendor lock-in.
Key Differences at a Glance
- Licensing: OpenSearch uses Apache 2.0; Elasticsearch 7.11+ uses SSPL or Elastic License
- API Compatibility: OpenSearch 1.x is API-compatible with Elasticsearch 7.10.2
- Security Features: OpenSearch bundles security (RBAC, audit logging) for free; Elasticsearch requires paid licenses for advanced security
- Alerting and Anomaly Detection: Built into OpenSearch by default
- Visualization: OpenSearch Dashboards replaces Kibana, with near-identical functionality
Why Migration Matters
Organizations choose to migrate for several reasons: avoiding proprietary licensing risks, reducing costs (free security features), embracing a community-driven open-source project, or complying with organizational open-source policies. The migration itself is straightforward when planned properly, especially if you are currently on Elasticsearch 7.x. The compatibility layer means your existing client code, queries, and ingestion pipelines will largely continue working with minimal changes.
Pre-Migration Assessment
π Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Compatibility Matrix
Before starting your migration, verify your Elasticsearch version against the OpenSearch compatibility table. This is the single most important stepβit determines which migration path you must take.
ββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββ
β Elasticsearch Version β OpenSearch Migration Path β
ββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββ€
β 7.10.2 or earlier (7.x) β Direct upgrade to OS 1.x β
β 7.11 - 7.17.x β Reindex or snapshot restore β
β β with compatibility mode β
β 6.x β Upgrade to ES 7.10.2 first, β
β β then migrate to OS 1.x β
β 8.x β Reindex from scratch β
β β (no direct compatibility) β
ββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββ
Check Your Current Setup
Run the following command to determine your Elasticsearch version and index settings:
# Check Elasticsearch version
curl -X GET "http://localhost:9200/_nodes/plugins?pretty"
# List all indices with their settings
curl -X GET "http://localhost:9200/_cat/indices?v&s=index"
# Check cluster settings
curl -X GET "http://localhost:9200/_cluster/settings?pretty"
Identify Breaking Changes
OpenSearch 1.x is compatible with Elasticsearch 7.10.2, but there are subtle differences you must audit:
- X-Pack features: ML, Watcher, and some monitoring features have different implementations
- Index metadata: OpenSearch adds
_opendistrometadata fields for certain features - Plugin ecosystem: Custom Elasticsearch plugins need to be rebuilt for OpenSearch
- License checks: Code that relies on Elasticsearch license API endpoints will break
Migration Path 1: Snapshot and Restore (Recommended for 7.x)
This is the cleanest migration method when moving from Elasticsearch 7.10.2 or earlier. It preserves all index data, mappings, and settings.
Step 1: Register a Snapshot Repository on Elasticsearch
Create a shared file system repository accessible to both clusters, or use S3 if you are on AWS:
# File-system based repository (local/shared mount)
curl -X PUT "http://localhost:9200/_snapshot/migration_repo" -H 'Content-Type: application/json' -d '{
"type": "fs",
"settings": {
"location": "/mnt/shared_snapshots",
"compress": true,
"max_snapshot_bytes_per_sec": "100mb",
"max_restore_bytes_per_sec": "100mb"
}
}'
# S3-based repository (AWS)
curl -X PUT "http://localhost:9200/_snapshot/s3_migration_repo" -H 'Content-Type: application/json' -d '{
"type": "s3",
"settings": {
"bucket": "my-snapshot-bucket",
"region": "us-east-1",
"compress": true
}
}'
Step 2: Create a Snapshot of All Indices
# Create snapshot of all indices (excluding system indices if desired)
curl -X PUT "http://localhost:9200/_snapshot/migration_repo/snapshot_2024_migration?wait_for_completion=true" -H 'Content-Type: application/json' -d '{
"indices": "*,-.kibana*,-.elastic*",
"ignore_unavailable": true,
"include_global_state": false
}'
Important: Set include_global_state to false to avoid importing Elasticsearch-specific cluster metadata into OpenSearch. The wait_for_completion=true parameter makes this a synchronous callβremove it for large snapshots and poll the status instead.
Step 3: Verify the Snapshot
# Check snapshot status
curl -X GET "http://localhost:9200/_snapshot/migration_repo/snapshot_2024_migration?pretty"
# List all snapshots in the repository
curl -X GET "http://localhost:9200/_snapshot/migration_repo/_all?pretty"
Step 4: Install and Start OpenSearch
Download OpenSearch and OpenSearch Dashboards. For a production setup, use the tarball or Docker images:
# Docker-based OpenSearch
docker run -d \
--name opensearch \
-p 9200:9200 \
-p 9600:9600 \
-e "discovery.type=single-node" \
-e "OPENSEARCH_INITIAL_ADMIN_PASSWORD=YourStrongPassword123!" \
-v /mnt/shared_snapshots:/mnt/shared_snapshots \
opensearchproject/opensearch:latest
# Docker-based OpenSearch Dashboards
docker run -d \
--name opensearch-dashboards \
-p 5601:5601 \
-e "OPENSEARCH_HOSTS=http://opensearch:9200" \
opensearchproject/opensearch-dashboards:latest
Step 5: Register the Same Repository in OpenSearch
curl -X PUT "http://localhost:9200/_snapshot/migration_repo" -H 'Content-Type: application/json' -d '{
"type": "fs",
"settings": {
"location": "/mnt/shared_snapshots",
"compress": true
}
}'
Step 6: Restore the Snapshot into OpenSearch
# Restore all indices from the snapshot
curl -X POST "http://localhost:9200/_snapshot/migration_repo/snapshot_2024_migration/_restore?wait_for_completion=true" -H 'Content-Type: application/json' -d '{
"indices": "*",
"ignore_unavailable": true,
"include_global_state": false,
"rename_pattern": "(.+)",
"rename_replacement": "$1"
}'
Step 7: Verify the Restoration
# Check cluster health
curl -X GET "http://localhost:9200/_cluster/health?pretty"
# Verify document counts
curl -X GET "http://localhost:9200/_cat/indices?v&s=index"
# Run a sample search to confirm data integrity
curl -X GET "http://localhost:9200/your_index_name/_search?pretty" -H 'Content-Type: application/json' -d '{
"query": {
"match_all": {}
},
"size": 5
}'
Migration Path 2: Reindex API (For Cross-Version Migration)
When you cannot use snapshot/restore (e.g., migrating from Elasticsearch 8.x to OpenSearch 2.x), use the Reindex API. This method streams documents from the source cluster to the destination cluster.
Step 1: Configure OpenSearch to Allow Remote Reindex
Add the source Elasticsearch cluster as a reindex whitelist entry in opensearch.yml:
# opensearch.yml
reindex.remote.whitelist: "old-elasticsearch-host:9200"
Restart OpenSearch after this change.
Step 2: Execute Remote Reindex
# Reindex a single index from remote Elasticsearch
curl -X POST "http://localhost:9200/_reindex?pretty&refresh=true&wait_for_completion=false" -H 'Content-Type: application/json' -d '{
"source": {
"remote": {
"host": "http://old-elasticsearch-host:9200",
"socket_timeout": "60s",
"max_retry_timeout": "60s"
},
"index": "source_index_name",
"size": 1000
},
"dest": {
"index": "destination_index_name",
"op_type": "create"
},
"conflicts": "proceed"
}'
Step 3: Monitor the Reindex Task
# Get the task ID from the reindex response, then check status
curl -X GET "http://localhost:9200/_tasks/your_task_id?pretty"
# List all running tasks
curl -X GET "http://localhost:9200/_tasks?actions=*reindex&pretty"
Step 4: Reindex Multiple Indices with a Script
#!/bin/bash
# reindex_all.sh β Reindex all indices from remote Elasticsearch to OpenSearch
SOURCE_HOST="http://old-elasticsearch-host:9200"
DEST_HOST="http://localhost:9200"
# Fetch all indices from source (excluding system indices)
INDICES=$(curl -s "${SOURCE_HOST}/_cat/indices?h=index" | grep -v -E "^\.(kibana|elastic|opensearch)")
for INDEX in $INDICES; do
echo "Reindexing: $INDEX"
curl -X POST "${DEST_HOST}/_reindex?wait_for_completion=false" \
-H 'Content-Type: application/json' \
-d "{
\"source\": {
\"remote\": {
\"host\": \"${SOURCE_HOST}\"
},
\"index\": \"${INDEX}\"
},
\"dest\": {
\"index\": \"${INDEX}\"
},
\"conflicts\": \"proceed\"
}"
echo ""
done
echo "Reindex tasks submitted. Monitor with: curl ${DEST_HOST}/_tasks?actions=*reindex"
Client Library Migration
Client applications must be updated to use OpenSearch client libraries. While the REST API is compatible, official client libraries differ.
Java High-Level REST Client Migration
// Old Elasticsearch dependency (Maven)
// Remove this:
// <dependency>
// <groupId>org.elasticsearch.client</groupId>
// <artifactId>elasticsearch-rest-high-level-client</artifactId>
// <version>7.17.x</version>
// </dependency>
// New OpenSearch dependency (Maven)
// Add this:
<dependency>
<groupId>org.opensearch.client</groupId>
<artifactId>opensearch-rest-high-level-client</artifactId>
<version>2.11.1</version>
</dependency>
// Java code changes
// Old Elasticsearch imports:
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.action.search.SearchRequest;
// New OpenSearch imports:
import org.opensearch.client.RestHighLevelClient;
import org.opensearch.client.RequestOptions;
import org.opensearch.action.search.SearchRequest;
// Client initialization remains nearly identical:
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")
)
);
// Most query builders work unchanged:
SearchRequest searchRequest = new SearchRequest("your_index");
searchRequest.source(new SearchSourceBuilder()
.query(QueryBuilders.matchQuery("field_name", "search_term"))
.size(10));
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
client.close();
Python Client Migration
# Uninstall old Elasticsearch client
# pip uninstall elasticsearch
# Install OpenSearch client
# pip install opensearch-py
# Python code changes
# Old:
# from elasticsearch import Elasticsearch
# es = Elasticsearch(['http://localhost:9200'])
# New:
from opensearchpy import OpenSearch
client = OpenSearch(
hosts=['http://localhost:9200'],
http_auth=('admin', 'YourStrongPassword123!'),
use_ssl=False,
verify_certs=False
)
# Search example β API is nearly identical
response = client.search(
index='your_index',
body={
'query': {
'match': {'field_name': 'search_term'}
}
}
)
for hit in response['hits']['hits']:
print(hit['_source'])
Node.js Client Migration
// Uninstall old client:
// npm uninstall @elastic/elasticsearch
// Install OpenSearch client:
// npm install @opensearch-project/opensearch
// Code changes:
// Old:
// const { Client } = require('@elastic/elasticsearch');
// const client = new Client({ node: 'http://localhost:9200' });
// New:
const { Client } = require('@opensearch-project/opensearch');
const client = new Client({
node: 'http://localhost:9200',
auth: {
username: 'admin',
password: 'YourStrongPassword123!'
}
});
// Search example
async function search() {
const response = await client.search({
index: 'your_index',
body: {
query: {
match: { field_name: 'search_term' }
}
}
});
console.log(response.body.hits.hits);
}
search().catch(console.error);
Configuration and Security Migration
Enabling Security in OpenSearch
Unlike Elasticsearch where security requires a paid license, OpenSearch includes a full security framework. Configure it in opensearch.yml:
# opensearch.yml security configuration
plugins.security.disabled: false
plugins.security.ssl.transport.enabled: true
plugins.security.ssl.http.enabled: true
plugins.security.authcz.admin_dn:
- "CN=admin,O=Example Com Inc.,C=US"
# Internal user database
plugins.security.allow_default_init_securityindex: true
plugins.security.authcz.rest_roles_roles_enabled: true
plugins.security.nodes_dn:
- "CN=node1,O=Example Com Inc.,C=US"
- "CN=node2,O=Example Com Inc.,C=US"
Migrating User Accounts and Roles
Export Elasticsearch users (if you had a commercial license) and recreate them in OpenSearch:
# Create an OpenSearch internal user
curl -X PUT "http://localhost:9200/_plugins/_security/api/internalusers/app_user" \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic YWRtaW46WW91clN0cm9uZ1Bhc3N3b3JkMTIzIQ==' \
-d '{
"password": "SecureAppPassword456!",
"opendistro_security_roles": ["readall", "writeall"],
"attributes": {
"department": "engineering"
}
}'
# Create a custom role
curl -X PUT "http://localhost:9200/_plugins/_security/api/roles/custom_read_role" \
-H 'Content-Type: application/json' \
-H 'Authorization: Basic YWRtaW46WW91clN0cm9uZ1Bhc3N3b3JkMTIzIQ==' \
-d '{
"cluster_permissions": ["cluster_composite_ops_ro"],
"index_permissions": [{
"index_patterns": ["app-logs-*"],
"allowed_actions": ["read", "search"]
}],
"tenant_permissions": []
}'
# Verify user was created
curl -X GET "http://localhost:9200/_plugins/_security/api/internalusers" \
-H 'Authorization: Basic YWRtaW46WW91clN0cm9uZ1Bhc3N3b3JkMTIzIQ=='
Migrating Kibana Saved Objects to OpenSearch Dashboards
Export saved objects from Kibana and import them into OpenSearch Dashboards:
# Export from Kibana (Elasticsearch 7.x)
# Use Kibana's saved objects UI: Management β Saved Objects β Export
# Or use the export API:
curl -X POST "http://localhost:5601/api/saved_objects/_export" \
-H 'kbn-xsrf: true' \
-H 'Content-Type: application/json' \
-d '{
"type": ["dashboard", "visualization", "search", "index-pattern"],
"excludeExportDetails": true
}' > kibana_export.ndjson
# Import into OpenSearch Dashboards
curl -X POST "http://localhost:5601/api/saved_objects/_import?overwrite=true" \
-H 'osd-xsrf: true' \
-H 'Content-Type: application/ndjson' \
--data-binary @kibana_export.ndjson
Note: The header changed from kbn-xsrf to osd-xsrf. Index patterns referencing .kibana may need adjustment to .opensearch equivalents.
Ingestion Pipeline Migration
Logstash Migration
Logstash continues to work with OpenSearch using the same output plugin. Update the host and authentication:
# logstash.conf β Update the OpenSearch output plugin
output {
opensearch {
hosts => ["http://opensearch-host:9200"]
user => "admin"
password => "YourStrongPassword123!"
index => "logs-%{+YYYY.MM.dd}"
ssl => false
ilm_enabled => false
manage_template => true
template_name => "logstash-template"
}
}
Fluentd / Fluent Bit Migration
# fluent-bit.conf β Update output configuration
[OUTPUT]
Name opensearch
Match *
Host opensearch-host
Port 9200
HTTP_User admin
HTTP_Passwd YourStrongPassword123!
Index app-logs
Logstash_Format On
Logstash_Prefix app-logs
Custom Ingest Pipelines
Elasticsearch ingest pipelines work in OpenSearch with minor modifications. Remove Elasticsearch-specific processors:
# Define an ingest pipeline in OpenSearch
curl -X PUT "http://localhost:9200/_ingest/pipeline/log_pipeline" -H 'Content-Type: application/json' -d '{
"description": "Parse application logs",
"processors": [
{
"grok": {
"field": "message",
"patterns": ["%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:level} %{GREEDYDATA:log_message}"]
}
},
{
"date": {
"field": "timestamp",
"formats": ["ISO8601"]
}
},
{
"set": {
"field": "ingested_by",
"value": "opensearch"
}
}
]
}'
# Apply pipeline during indexing
curl -X POST "http://localhost:9200/app-logs/_doc?pipeline=log_pipeline" -H 'Content-Type: application/json' -d '{
"message": "2024-03-15T10:30:00Z ERROR Database connection failed"
}'
Testing Your Migration
Functional Test Checklist
Run these validation queries against your OpenSearch cluster to ensure everything works:
# 1. Cluster health
curl -X GET "http://localhost:9200/_cluster/health?pretty"
# 2. Verify all expected indices exist
curl -X GET "http://localhost:9200/_cat/indices?v&s=index"
# 3. Compare document count for critical indices
curl -X GET "http://localhost:9200/critical_index/_count?pretty"
# 4. Run a complex search query
curl -X POST "http://localhost:9200/your_index/_search?pretty" -H 'Content-Type: application/json' -d '{
"query": {
"bool": {
"must": [
{"match": {"field1": "value"}}
],
"filter": [
{"range": {"@timestamp": {"gte": "now-7d"}}}
]
}
},
"aggregations": {
"top_terms": {
"terms": {"field": "category", "size": 10}
}
}
}'
# 5. Test bulk indexing
curl -X POST "http://localhost:9200/_bulk" -H 'Content-Type: application/x-ndjson' -d '
{"index": {"_index": "test_index", "_id": "1"}}
{"field1": "value1", "field2": "value2"}
{"index": {"_index": "test_index", "_id": "2"}}
{"field1": "value3", "field2": "value4"}
'
# 6. Verify OpenSearch Dashboards is accessible
curl -X GET "http://localhost:5601/api/status" -H 'osd-xsrf: true'
Performance Comparison
# Benchmark search performance
# Run this against both Elasticsearch and OpenSearch for comparison
curl -X POST "http://localhost:9200/benchmark_index/_search" -H 'Content-Type: application/json' -d '{
"query": {
"multi_match": {
"query": "performance test query",
"fields": ["title^3", "body", "tags"]
}
},
"track_total_hits": true,
"profile": true
}'
Best Practices for Migration
- Test in a Staging Environment First: Never migrate production data directly. Set up a staging OpenSearch cluster, restore your snapshot, and run your full application test suite against it before touching production.
- Keep Elasticsearch Running During Migration: Maintain your original Elasticsearch cluster as a fallback. Run both clusters in parallel during the transition period. Use a load balancer or application feature flags to gradually shift traffic.
- Monitor Resource Usage: Snapshot restore and reindex operations are resource-intensive. Monitor CPU, memory, and disk I/O on both source and destination clusters. Schedule migration during low-traffic periods.
- Update Index Templates Before Data Ingest: If you use index templates, migrate them first. OpenSearch handles template precedence slightly differently. Verify template mappings in OpenSearch before new indices are created.
- Handle System Indices Separately: Do not migrate
.kibana,.elastic, or other system indices. OpenSearch has its own system indices (.opensearch). Migrate only your application data indices. - Validate Security Settings: OpenSearch security is more granular. Audit your role mappings, action groups, and tenant configurations. The free security plugin includes features that were paid in Elasticsearchβtake advantage of them.
- Update Monitoring and Alerting: If you used Elastic Watcher or commercial alerting, reconfigure alerts in OpenSearch's built-in alerting plugin. OpenSearch Alerting uses a different API and configuration format.
- Document Your Migration: Keep a detailed log of every step, including timestamps, commands run, and any errors encountered. This documentation will be invaluable if you need to troubleshoot or roll back.
- Plan for Client Library Rollout: Client library changes require application deployments. Coordinate with your development team to schedule the switch. Use feature flags to toggle between Elasticsearch and OpenSearch clients during rollout.
- Benchmark Before Cutting Over: Run your typical query load against OpenSearch and measure p95/p99 latency. Compare with Elasticsearch baselines. OpenSearch performance is generally comparable, but specific query patterns may differ.
Common Pitfalls and Troubleshooting
Snapshot Restore Failures
# Error: "snapshot is incompatible with this version"
# Cause: Trying to restore an ES 8.x snapshot into OS 2.x
# Fix: Use reindex API instead, or downgrade source to 7.10.2
# Error: "repository missing"
# Cause: Repository path not accessible from OpenSearch container/node
# Fix: Verify shared filesystem mount, check permissions (chmod 755)
Client Compatibility Issues
# Common Java error: NoClassDefFoundError
# Cause: Mixing Elasticsearch and OpenSearch JARs on classpath
# Fix: Exclude all Elasticsearch dependencies, use only OpenSearch artifacts
# Common Python error: AuthenticationException
# Cause: OpenSearch security enabled but no credentials provided
# Fix: Add http_auth parameter to OpenSearch client constructor
Index Mapping Conflicts
# Check for mapping incompatibilities
curl -X GET "http://localhost:9200/problem_index/_mapping?pretty"
# Compare with source Elasticsearch mapping
curl -X GET "http://old-es-host:9200/problem_index/_mapping?pretty"
# If mappings differ, create the index manually in OpenSearch first,
# then reindex data without mapping inference:
curl -X POST "http://localhost:9200/_reindex" -H 'Content-Type: application/json' -d '{
"source": {
"remote": {
"host": "http://old-es-host:9200"
},
"index": "problem_index"
},
"dest": {
"index": "problem_index",
"op_type": "create"
}
}'
Rollback Plan
Always prepare a rollback strategy before starting migration. If issues arise after switching to OpenSearch:
- Keep Elasticsearch cluster untouched β do not delete it after migration
- Maintain dual client configuration in your application for at least 2 weeks
- Use DNS or load balancer to redirect traffic back to Elasticsearch quickly
- Keep snapshot repositories on both sides for emergency data recovery
# Quick rollback via load balancer redirect (example HAProxy)
# Switch backend from OpenSearch pool to Elasticsearch pool:
# backend opensearch_backend β backend elasticsearch_backend
# Verify Elasticsearch is still healthy
curl -X GET "http://old-elasticsearch-host:9200/_cluster/health?pretty"
# Application rollback: redeploy with Elasticsearch client configuration
# Revert environment variable: SEARCH_HOST=old-elasticsearch-host:9200
Conclusion
Migrating from Elasticsearch to OpenSearch is a well-defined process that, when executed methodically, causes minimal disruption to your applications. The snapshot/restore method provides the cleanest path for Elasticsearch 7.x users, while the reindex API handles cross-version scenarios. Client library changes are mechanicalβprimarily import path updates with nearly identical APIs. The real value comes after migration: you gain a fully open-source search engine with bundled security, alerting, and anomaly detection, all under the Apache 2.0 license. By following the steps outlined in this guide, testing thoroughly in staging, and maintaining a solid rollback plan, your team can complete this migration with confidence and minimal downtime.