Moving from Elasticsearch to OpenSearch looks simple on paper. After all, OpenSearch is a fork of Elasticsearch and should work? However, making the switch without downtime or data loss can feel like a leap of faith. You know they are the same, but then in a way also not the same.

I was in exactly this situation for a small project. We had an existing dev Elasticsearch cluster, some indexes with real production data, and wanted to demonstrate that we can migrate fully to a completely fresh OpenSearch cluster. There were few clear requirements: no custom code needed, and very low downtime with maximum compatibility so the rest of the application didn’t need changing.

The obvious solution for this, and what we ended up using, was a simple snapshot and restore to an S3 bucket. This is available natively to both platforms, and worked exactly as needed in one go. In this post, I’m going to take you through the steps involved for us, from creating the snapshot repo to restoring it in OpenSearch.

I> You understand that I have used some other values in naming.

Tooling

I used a small open-source CLI called esops (Elasticsearch / OpenSearch operations). This is way easier for me when doing things manually in Kibana or via the Elasticsearch API. I personally find this easier, as this cli tool is similar as what I do with kubectl when doing “ops” work with Kubernetes. But your miles may vary.

Snapshot registering

First we need to configure a repository in Elasticsearch with the S3 Bucket, this will allow us to create a snapshot on Elasticsearch and store it on the S3 Bucket.

$ esops --context dev-es001 snapshot repository register \
    --repository backups --type s3 \
    --setting bucket=poc-elasticsearch-opensearch \
    --setting base_path=clusters/dev-es001/snapshots \
    --setting region=us-east-1 \
    --setting client=default
ACTION    DRY_RUN  REPOSITORY  TYPE  ACK
register  false    backups     s3    true

This will register the repository where we store the snapshots in an S3 Bucket named poc-elasticsearch-opensearch, and store the snapshots on a directory clusters/dev-es001/snapshots. This S3 Bucket is located on us-east-1. Not shown with this blog, but I already had this S3 Bucket available and had the Elasticsearch (and also OpenSearch later) configured with access to the S3 Bucket. Output suggest that register actions was successful. We can verify that the backups repository exist in the Elasticsearch cluster.

$ esops --context dev-es001 snapshot verify --repository backups
ACTION  REPOSITORY  NODES
verify  backups     2

And just to be sure, we don’t have any snapshots yet! Just checking.

$ esops --context dev-es001 snapshot list --repository backups
REPOSITORY  NAME  STATE  INDICES  SHARDS  DURATION

Good, no snapshots. As the idea is to restore a snapshot from the S3 Bucket, we need to register the repository on the OpenSearch cluster too. But we have to configure this as read only. It is a development environment, but that doesn’t mean we have to configure it fully writable. Using the same configuration options, like the base_path and region, we use the S3 Bucket in the same was as on Elasticsearch.

$ esops --context poc-os01 snapshot repository register \
    --repository backups --type s3 \
    --setting bucket=poc-elasticsearch-opensearch \
    --setting base_path=clusters/dev-es001/snapshots \
    --setting region=us-east-1 \
    --setting client=default \
    --setting readonly=true
ACTION    DRY_RUN  REPOSITORY  TYPE  ACK
register  false    backups     s3    true

And once the register is completed, just validating that it actually exist.

$ esops --context poc-os01 snapshot verify --repository backups
ACTION  REPOSITORY  NODES
verify  backups     1

The development environment isn’t used that much, as the indexes doesn’t have a lot of data in it. And some days before we rebuild the development environment, otherwise we would have some more data in our cluster.

$ esops --context dev-es001 index list
NAME               HEALTH  STATUS  PRI  REP  DOCS  STORE
backlog-reports-2026.05.10  green   open    1    1    820   194.5kb
backlog-reports-2026.05.11  green   open    1    1    790   157.0kb
backlog-reports-2026.05.12  green   open    1    1    845   166.7kb

We have 3 indexes with some data in it, lets verify that we don’t have any indices in our OpenSearch cluster.

$ esops --context poc-os01 index list
NAME               HEALTH  STATUS  PRI  REP  DOCS  STORE

Good! Nothing exists (well, not the ones we will be restoring as there are some hidden indexes on it.). That means we are ready with our preparations and we can create the snapshot on the Elasticsearch cluster.

Snapshot creation

We will create a snapshot daily-2026.05.12 on our backup repository (we created earlier) and specify all backlog-reports-* indices. With the --wait we will wait till the snapshot process is done and afterwards we get our terminal back.

$ esops --context dev-es001 snapshot create \
    --repository backups \
    --snapshot daily-2026.05.12 \
    --indices 'backlog-reports-*' --wait
ACTION  DRY_RUN  REPOSITORY  SNAPSHOT          ACCEPTED  WAIT   STATE
create  false    backups     daily-2026.05.12  true      false  STARTED
snapshot backups/daily-2026.05.12 state=STARTED shards=0/3 (init=3 started=0 failed=0)
snapshot backups/daily-2026.05.12 state=SUCCESS shards=3/3 (init=0 started=0 failed=0)
ACTION  DRY_RUN  REPOSITORY  SNAPSHOT          ACCEPTED  WAIT   STATE
create  false    backups     daily-2026.05.12  true      false  SUCCESS

That went quick, a few seconds. That is nice that our development environment doesn’t have a lot of data in it. Lets verify that the snapshot is a success.

$ esops --context dev-es001 snapshot list --repository backups
REPOSITORY  NAME              STATE    INDICES  SHARDS  DURATION
backups     daily-2026.05.12  SUCCESS  3        3/3     3.407s

Yes, snapshot went successful and it took indeed several seconds, 3.4 which is fine. In production environment it will be a lot longer. Now we have created our snapshot, let us check if the OpenSearch cluster is able to see that snapshot too.

Snapshot check

We use the esops migrate check command with the 2 cluster names to see if the snapshot from source Elasticsearch is able to migrate the target OpenSearch.

$ esops migrate check --source dev-es001 --target poc-os01
SOURCE  context=dev-es001  dialect=elasticsearch  version=9.3.3  url=https://dev-es001.infra.local
TARGET  context=poc-os01  dialect=opensearch  version=3.6.0  url=https://poc-os01.infra.local:9200

STATUS  CHECK                    DETAIL
OK      source.connectivity      dev-es001 reachable (elasticsearch 9.3.3)
OK      target.connectivity      poc-os01 reachable (opensearch 3.6.0)
WARN    dialect                  cross-dialect elasticsearch → opensearch — security model, ILM/ISM, and pipelines do not migrate
WARN    version                  downgrade elasticsearch 9.3.3 → opensearch 3.6.0 — index format compatibility is one-way; reind…
OK      target.remote_whitelist  dev-es001-01.infra.local:9200 allowed by target reindex.remote.allowlist (dev-es001-01.infra.local:9200, dev-es001-02.infra.local:9200)

SUMMARY  status=WARN  blockers=0  warnings=2

In our OpenSearch cluster we had to add an extra configuration option reindex.remote.allowlist with the hostnames of the nodes that are part of the Elasticsearch cluster You also see that we do a “downgrade”, from Elasticsearch 9.3.3 to OpenSearch 3.6.0. It is debatable if this is a “downgrade”, that is why I put quotation marks on “downgrade”. But the migrate check doesn’t show any blockers, so lets see if we can plan the migration (sort of a dry-run).

$ esops migrate plan  --source dev-es001 --target poc-os01 \
    --indices 'backlog-reports-2026.05.*'
SOURCE  context=dev-es001  dialect=elasticsearch  version=9.3.3
TARGET  context=poc-os01  dialect=opensearch  version=3.6.0
THROUGHPUT  5000 docs/sec

STATUS  INDEX              DOCS  PRIMARY_STORE  ISSUES  EST_DURATION
OK      backlog-reports-2026.05.10  820   103.5kb        0       0s
OK      backlog-reports-2026.05.11  790   78.6kb         0       0s
OK      backlog-reports-2026.05.12  845   83.4kb         0       0s

TOTALS  indices=3  docs=2455  primary_store=265.5kb  estimated_duration=0s
SUMMARY  status=OK  blockers=0  warnings=0

The plan all looks fine and status=ok is what we want to see. Now we can start the actual reindex, thus the snapshot will be restored and made available in the OpenSearch cluster.

$ esops migrate reindex \
    --source dev-es001 --target poc-os01 \
    --indices 'backlog-reports-2026.05.*' \
    --requests-per-second 5000
SOURCE  context=dev-es001  dialect=elasticsearch  version=9.3.3  url=https://dev-es001.infra.local
TARGET  context=poc-os01  dialect=opensearch  version=3.6.0  url=https://poc-os01.infra.local:9200

STATUS  SOURCE             TARGET             DOCS  DETAIL
OK      backlog-reports-2026.05.10  backlog-reports-2026.05.10  820   created=820 updated=0 deleted=0
OK      backlog-reports-2026.05.11  backlog-reports-2026.05.11  790   created=790 updated=0 deleted=0
OK      backlog-reports-2026.05.12  backlog-reports-2026.05.12  845   created=845 updated=0 deleted=0

SUMMARY  status=OK  blockers=0  warnings=0

That too went rather quick, but then again it wasn’t a lot of data. With the migrate reindex command we see again the status=OK in the output and I like that. What we can do is to run the migrate verify to check again that the snapshot restore was successful.

$ esops migrate verify \
    --source dev-es001 --target poc-os01 \
    --indices 'backlog-reports-2026.05.*'
SOURCE  context=dev-es001  dialect=elasticsearch  version=9.3.3
TARGET  context=poc-os01  dialect=opensearch  version=3.6.0

STATUS  SOURCE             TARGET             SOURCE_DOCS  TARGET_DOCS  DIFF  DETAIL
OK      backlog-reports-2026.05.10  backlog-reports-2026.05.10  820          820          +0    -
OK      backlog-reports-2026.05.11  backlog-reports-2026.05.11  790          790          +0    -
OK      backlog-reports-2026.05.12  backlog-reports-2026.05.12  845          845          +0    -

SUMMARY  status=OK  blockers=0  warnings=0

Yes, it really looks like that all the data was properly reindexed from Elasticsearch to OpenSearch. ## Result

As the migrate verify didn’t had any issues, means that we now should see the indexes in our OpenSearch cluster.

$ esops --context poc-os01 index list
NAME               HEALTH  STATUS  PRI  REP  DOCS  STORE
backlog-reports-2026.05.10  green   open    1    1    820   194.5kb
backlog-reports-2026.05.11  green   open    1    1    790   157.0kb
backlog-reports-2026.05.12  green   open    1    1    845   166.7kb

Yes! And with status green too, so that is good! So we have the same amount of documents and they also have similar size, so everything is completely migrated/reindexed. So looking back from the process, it is an easy way to migrate from Elasticsearch from OpenSearch.

Production considerations

In our light development environment, everything finished in seconds. In production you should plan for: * Longer snapshot creation and restore times (hours for large clusters) * Proper throttling (--requests-per-second and max_bytes_per_sec) * Matching or larger cluster sizing on the target side * Manual migration of templates, ingest pipelines, or ILM/ISM policies (they don’t transfer automatically) * A short parallel-run period with the old cluster as a safety net (Just to be sure)

Final thoughs

To get back to the beginning of thus blog post, what started as an exercise of faith proved rather simple after all. By relying solely on the native S3 snapshot storage, we successfully migrated indexes from Elasticsearch into an entirely new OpenSearch cluster without any data loss. We have minimal downtime, and I think also very importantly, zero changes to anything else in the application.

After migrating, the indexes automatically had the green status in our light-dev setup. Of course, in production the procedure would have take longer (as it would be larger in size). In case you find yourself facing a similar task, there’s no need to overanalyze. Snapshot and reindex migration can be as foolproof as it gets, as long as you put enough verification steps in place.

May the force be with you!