OpenSearch Phase 1: a bad OpenSearch index permission stops dotCMS from creating any index on a new instance
@fabrizzio-dotCMS is already working on this.
Since Aug 10, 2026.
- Dominant language
- Java
- Stars
- 970
- Forks
- 486
- Avg merge
- 3d 33m
- Merged PRs (30d)
- 170
Description
Problem Statement
During the OpenSearch migration, Phase 1 is meant to keep Elasticsearch in charge of the site while OpenSearch runs alongside as a shadow copy. If OpenSearch is misconfigured, the site should not care.
It does. On a newly provisioned instance, an OpenSearch role that is not allowed to create dotCMS's indexes stops dotCMS from creating any index at all — including the Elasticsearch ones the site actually runs on. The instance comes up with no search index and cannot serve content.
| Expected in Phase 1 | Actual | |
|---|---|---|
| Elasticsearch index | created, instance works | not created ❌ |
| OpenSearch index | not created, no pointer registered | not created ✅ |
building process in Admin → System → Index |
visible | absent ❌ |
| Migration phase | switches itself off to Phase 0 | — |
| Log | one warning naming the rejected index pattern and how to fix it | no such warning ❌ |
Why this is a product bug, not a setup mistake
All of that behavior is already implemented and correct in the build tested (5ea351f), which is what makes this worth filing:
- Elasticsearch is created before OpenSearch is attempted — deliberately (
ContentletIndexAPIImpl.bootstrapAndPoint:890-894, comment at:875-876). - The OpenSearch 403 is caught and absorbed in Phase 1 (
:1092→handleOsBootstrapFailure:1142). - The exact warning being asked for already exists —
:1155-1178logslikelyCause=AUTH_FORBIDDENplus remediation naming the role's index pattern andDOT_DOTCMS_CLUSTER_ID. - The two engines' index pointers are written independently, so there is no all-or-nothing commit (
pointES:1214-1230vspointOS:1250-1269). - Elasticsearch cannot be skipped on an empty database —
esNeededistrue(initIndex:842-849).
The two obvious alternative explanations are both ruled out:
- "The phase could not be reset." Real hole, but not this: a phase set via the system table cannot be reset (
IndexConfigHelper.java:187-193), whereas this was set with theDOT_FEATURE_FLAG_OPEN_SEARCH_PHASEenvironment variable, which resets correctly. - "The build predates the fixes."
5ea351fcontains #36783, #36903 and #36933 as verified ancestors.
Where the failure must be
Nothing in the Phase 1 OpenSearch path can suppress the Elasticsearch half. If no Elasticsearch index exists, execution never reached the code that creates one — the fault is upstream of everything previously fixed. On a new instance the first index creation runs on the starter-load path:
MainServlet.init → StartupTasksExecutor → Task00004LoadStarter
→ DotCMSInitDb.loadStarterSite:105 → refreshAllContent()
→ fullReindexStart() → initIndex() → bootstrapAndPoint(ts, true, true)
├─ bootstrapAndPointES ← should always run
└─ bootstrapAndPointOS ← where the 403 happens
Most likely causes, in order:
- The OpenSearch client is resolved before anyone checks the phase.
ContentletIndexAPIImpl():199-201andIndexAPIImpl():95-97eagerly resolve the OpenSearch CDI beans in every phase. If the bean cannot be produced,APILocator.getContentletIndexAPI()throws and nothing is created in either engine, with nothing about index patterns in the log — matching the symptom exactly. - Startup aborts and rolls the Elasticsearch pointer back.
fullReindexStartis@WrapInTransaction(:1367) andDotCMSInitDb.java:56rethrows any starter-load failure. Two escape routes:pointOS(:1109) sits outside the OpenSearch try/catch, andbootstrapAndPointEScatches onlyIOException(:1004-1008). - The starter load skipped index creation.
refreshAllContent()returns early whenisInFullReindex()is true (ESContentletAPIImpl:4049-4051) — e.g. if the starter import left rows indist_reindex_journal. Unrelated to the phase. - The role also blocks
cluster:monitor/main. A different path then runs first: a ~120 s retry loop (OSIndexAPIImpl:595-597) ending innot reachable after 24 attempt(s)rather than the index-pattern message. Elasticsearch is still created here, so it does not explain the missing index, but it is a real diagnostic trap and would explain a boot that looks stalled.
Also worth fixing while in here: ESIndexAPI.indexExists:359-363 calls indexName.toLowerCase() with no null guard, and on an empty database those names are null. The NPE is swallowed at :481-484 and happens to give the right answer — if it ever returned true, Elasticsearch would be skipped outright.
Why CI does not catch it
- The new-instance path is untested.
bootstrapAndPoint(has zero test callers in the repo.OsBootstrapForbiddenIndexTestcallsbootstrapAndPointOS,initAndPointReindexandfullReindexStartdirectly on an already-initialised fixture; it never boots from an empty database. - Tests check bookkeeping, not the cluster. Assertions are on the database pointer (
esAfter.getReindexWorking()), never on whether the index physically exists (esImpl().indexExists(...)). The only tests that do check the cluster run against a healthy OpenSearch.
Steps to Reproduce
Instance configuration Video: https://dotcms.slack.com/archives/C0B5XTNK0CV/p1786393580926099
Video screencasts:
https://github.com/user-attachments/assets/e5366ba5-cdb5-49a6-b56a-157d1f38b744
https://github.com/user-attachments/assets/5e5a419b-4c77-4c98-b6ce-3d5fafda6af6
An initial-setup walkthrough exists internally — ask @rjvelazco.
Order matters. Configure OpenSearch and the phase before the instance first boots. dotCMS writes the cluster id into
dot_clusteron first boot and the stored value wins from then on, so a reused instance cannot reproduce this.
- In OpenSearch, confirm the non-admin user and role exist and look correct in the admin panel — they should already be provisioned.
- In Dashboards → Security → Roles → the role mapped to the dotCMS user → Index permissions, change the index pattern to one that cannot match dotCMS's index names, e.g.
cluster_whatever_*. Leave the second block (pattern*with the monitor/stats actions) alone — this removes create and write in one move, which is what the scenario needs. - Set the migration phase to 1 using the
DOT_FEATURE_FLAG_OPEN_SEARCH_PHASEenvironment variable. Do not use the system table — a phase set there cannot be reset (IndexConfigHelper.java:187-193). - Provision a fresh dotCMS instance with the correct cluster configuration. It must be a new instance with a clean database volume.
- Once it boots, check Admin → System → Index and both search dashboards.
Result: no index in either engine, and no building index process in Admin → System → Index.
First triage step — these log lines identify the cause
Filter the boot log (unfiltered, INFO and above) for:
| # | String | Source |
|---|---|---|
| 1 | Error creating index: ….os |
OSIndexAPIImpl:234 |
| 2 | OpenSearch index creation failed — …likelyCause=AUTH_FORBIDDEN |
ContentletIndexAPIImpl:1173-1178 |
| 3 | Migration phase reset to PHASE_0_MIGRATION_NOT_STARTED |
IndexConfigHelper:201-205 |
| 4 | Failed to create new indexes: |
ContentletIndexAPIImpl:575 |
| 5 | Building Initial Index |
starter load |
- 1–3 present → the OpenSearch failure was handled correctly; the missing Elasticsearch index is a separate problem downstream.
- All absent → startup aborted before index initialisation ran → cause 1, 2 or 3. This is what the symptom predicts.
- 2 present but reporting
not reachable after 24 attempt(s)→ cause 4.
Please attach the log as plain text rather than a share link, so the assignee can read it without extra access.
Acceptance Criteria
Behavior
- On a new instance in Phase 1, with an OpenSearch role whose index pattern cannot match dotCMS's index names, the Elasticsearch indexes are physically created and registered and the instance comes up working.
- The boot log carries one clear warning naming the cause and the fix. The message already exists at
ContentletIndexAPIImpl:1155-1178— confirm it is reached on this path. - No OpenSearch index is created and no OpenSearch pointer is registered for indexes that do not exist.
- The migration halts itself to Phase 0; the Index screen lists only the Elasticsearch indexes.
- Publishing and searching work end to end, served by Elasticsearch.
Root cause
- Identify why
bootstrapAndPointESis never reached, using the log strings above, and fix it. - A failure resolving the OpenSearch CDI beans (
ContentletIndexAPIImpl:199-201,IndexAPIImpl:95-97) can no longer block Elasticsearch index creation in phases 0–2. -
pointOS(:1109) can no longer roll back the already-committedpointESwrite inside the@WrapInTransactionfullReindexStart(:1367). -
bootstrapAndPointES'sIOException-only catch (:1004-1008) no longer lets an unchecked exception abort the starter load. -
ESIndexAPI.indexExists:359-363null-guards its argument. - When the role also blocks
cluster:monitor/main, the log points at the permission fix rather than only reporting the cluster unreachable.
Test coverage
- An integration test covers empty database, Phase 1, OpenSearch role that cannot create the indexes, end to end through
bootstrapAndPoint(ts, true, true)— which has no test callers today. - It asserts the Elasticsearch indexes physically exist via
esImpl().indexExists(...), not just that a pointer was written, and that the warning is emitted. - Registered in
OpenSearchUpgradeSuiteso it runs in CI.
Regression
- The same scenario behaves correctly when triggered by Reindex on an existing instance, not only on first boot.
- Restoring the index pattern and setting the phase back to 1 returns the instance to normal Phase 1 dual-write behavior.
- Phase 3 with the same misconfiguration still fails loudly and does not switch the phase off — there is nothing to fall back to.
dotCMS Version
dotcms-core 26.08.10-01 — August 10, 2026, buildNumber 5ea351f.
Confirmed to contain all three prior OpenSearch index-bootstrap fixes as ancestors: #36783 (merged Aug 1), #36903 (Aug 6), #36933 (Aug 7).
Environment: separate Elasticsearch and OpenSearch 3.x dev clusters, OpenSearch security plugin enabled, non-admin user and role.
Severity
High - Major functionality broken
Links
- NA — no Freshdesk ticket
- Prior QA history and the three fixes that came out of it: #36222, #36783, #36903, #36933
- Related: #36360 (OpenSearch site-search validation), #36244 (unify OpenSearch startup connection gate)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.