OpenSearch is an Apache 2.0 licensed search and analytics engine that started as a fork of Elasticsearch 7.10. It ships with a security plugin, index lifecycle management and a web interface, OpenSearch Dashboards. In this tutorial you will install OpenSearch 3.x on Ubuntu 24.04 from the official APT repository, configure a single-node instance with TLS and authentication, index and search documents, add a retention policy, and connect OpenSearch Dashboards.

Prerequisites

To follow this guide you need:

  • A server running Ubuntu 24.04 LTS with at least 4 GB of RAM and 2 vCPUs, for example a CubePath VPS. OpenSearch uses a Java heap plus operating system cache, so 8 GB is a better starting point for real data.
  • A non-root user with sudo privileges.
  • SSH access from your local machine, used to open a tunnel to Dashboards in Step 7.

OpenSearch bundles its own Java runtime, so you do not need to install Java.

Step 1 - Preparing the system

OpenSearch uses memory-mapped files for its indexes and needs a higher limit on memory map areas than the Ubuntu default. Set vm.max_map_count persistently:

echo 'vm.max_map_count=262144' | sudo tee /etc/sysctl.d/99-opensearch.conf
sudo sysctl --system

Verify the new value:

sysctl vm.max_map_count
vm.max_map_count = 262144

Install the tools needed to add the repository:

sudo apt update
sudo apt install -y curl gnupg jq

Step 2 - Adding the OpenSearch APT repository

Download the OpenSearch release signing key into /etc/apt/keyrings:

sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://artifacts.opensearch.org/publickeys/opensearch-release.pgp \
  | sudo gpg --dearmor -o /etc/apt/keyrings/opensearch-release.gpg

Add the repository for the 3.x release line:

echo "deb [signed-by=/etc/apt/keyrings/opensearch-release.gpg] https://artifacts.opensearch.org/releases/bundle/opensearch/3.x/apt stable main" \
  | sudo tee /etc/apt/sources.list.d/opensearch-3.x.list

Update the package index and list the available versions:

sudo apt update
apt-cache policy opensearch
opensearch:
  Installed: (none)
  Candidate: 3.8.0
  Version table:
     3.8.0 500
        500 https://artifacts.opensearch.org/releases/bundle/opensearch/3.x/apt stable/main amd64 Packages
...

Step 3 - Installing OpenSearch

Since version 2.12, the package requires an initial password for the built-in admin user. It must be at least 8 characters long and include uppercase and lowercase letters, a digit and a special character; weak passwords make the installation fail. Replace your_strong_password with your own:

sudo env OPENSEARCH_INITIAL_ADMIN_PASSWORD='your_strong_password' apt install -y opensearch

With this variable set, the installer runs the demo security configuration: it generates self-signed TLS certificates, enables HTTPS on port 9200 and creates the admin user with your password.

Step 4 - Configuring a single node

Open the main configuration file:

sudo nano /etc/opensearch/opensearch.yml

Set a cluster name and node name, keep the node bound to localhost and tell OpenSearch not to look for other nodes. Add or edit these lines near the top of the file and leave the plugins.security block that the installer appended at the end unchanged:

cluster.name: search-cluster
node.name: node-1
path.data: /var/lib/opensearch
path.logs: /var/log/opensearch
network.host: 127.0.0.1
http.port: 9200
discovery.type: single-node

Next, size the Java heap. The rule of thumb is half the RAM of the server, never more than about 31 GB, with the minimum and maximum set to the same value. Edit the JVM options:

sudo nano /etc/opensearch/jvm.options

For a server with 4 GB of RAM, change the existing -Xms and -Xmx lines to:

-Xms2g
-Xmx2g

Enable and start the service. The first start takes up to a minute while the security index is initialised:

sudo systemctl daemon-reload
sudo systemctl enable --now opensearch

Check the node over HTTPS. -k accepts the self-signed demo certificate:

curl -sk -u 'admin:your_strong_password' https://localhost:9200
{
  "name" : "node-1",
  "cluster_name" : "search-cluster",
  "version" : {
    "distribution" : "opensearch",
    "number" : "3.8.0",
    ...
  },
  "tagline" : "The OpenSearch Project: https://opensearch.org/"
}

Check cluster health:

curl -sk -u 'admin:your_strong_password' "https://localhost:9200/_cluster/health?pretty"

A "status" : "green" or "yellow" means the node is working. Yellow on a single node usually means some indexes request replicas that cannot be placed; you will avoid that in the next step by setting replicas to zero.

To shorten the next commands, store the password in a variable for this shell session:

export OS_PASS='your_strong_password'

Step 5 - Creating an index and searching

Create a logs-app index with an explicit mapping. keyword fields are used for exact matches and aggregations, text fields for full-text search. Replicas are set to 0 because a single node cannot hold a copy of its own shards:

curl -sk -u "admin:${OS_PASS}" -X PUT "https://localhost:9200/logs-app" \
  -H 'Content-Type: application/json' \
  -d '{
    "settings": {
      "number_of_shards": 1,
      "number_of_replicas": 0
    },
    "mappings": {
      "properties": {
        "timestamp":   {"type": "date"},
        "level":       {"type": "keyword"},
        "service":     {"type": "keyword"},
        "message":     {"type": "text"},
        "duration_ms": {"type": "float"}
      }
    }
  }'
{"acknowledged":true,"shards_acknowledged":true,"index":"logs-app"}

Index several documents at once with the _bulk API. Each document is preceded by an action line, and refresh=true makes them searchable immediately:

curl -sk -u "admin:${OS_PASS}" -X POST "https://localhost:9200/_bulk?refresh=true" \
  -H 'Content-Type: application/x-ndjson' \
  --data-binary $'{"index":{"_index":"logs-app"}}\n{"timestamp":"2026-09-25T10:00:00Z","level":"ERROR","service":"api","message":"Connection refused by database","duration_ms":12.5}\n{"index":{"_index":"logs-app"}}\n{"timestamp":"2026-09-25T10:01:00Z","level":"INFO","service":"api","message":"Request completed","duration_ms":48.1}\n{"index":{"_index":"logs-app"}}\n{"timestamp":"2026-09-25T10:02:00Z","level":"ERROR","service":"worker","message":"Database connection timeout","duration_ms":3000}\n' \
  | jq '.errors'
false

Search for errors that mention "database", newest first, and count errors per service with an aggregation:

curl -sk -u "admin:${OS_PASS}" "https://localhost:9200/logs-app/_search" \
  -H 'Content-Type: application/json' \
  -d '{
    "query": {
      "bool": {
        "must":   [{"match": {"message": "database"}}],
        "filter": [{"term": {"level": "ERROR"}}]
      }
    },
    "sort": [{"timestamp": "desc"}],
    "aggs": {"by_service": {"terms": {"field": "service"}}}
  }' | jq '[.hits.hits[]._source.message], .aggregations.by_service.buckets'
[
  "Database connection timeout",
  "Connection refused by database"
]
[
  {
    "key": "api",
    "doc_count": 1
  },
  {
    "key": "worker",
    "doc_count": 1
  }
]

List indexes with their size and document count at any time:

curl -sk -u "admin:${OS_PASS}" "https://localhost:9200/_cat/indices?v"

Step 6 - Deleting old indexes with Index State Management

Log data grows without limit unless something deletes it. The Index State Management (ISM) plugin applies policies to indexes automatically. This policy moves every new index whose name starts with logs- into a hot state and deletes it 30 days after creation:

curl -sk -u "admin:${OS_PASS}" -X PUT "https://localhost:9200/_plugins/_ism/policies/logs-retention" \
  -H 'Content-Type: application/json' \
  -d '{
    "policy": {
      "description": "Delete logs indexes after 30 days",
      "default_state": "hot",
      "states": [
        {
          "name": "hot",
          "actions": [],
          "transitions": [
            {"state_name": "delete", "conditions": {"min_index_age": "30d"}}
          ]
        },
        {
          "name": "delete",
          "actions": [{"delete": {}}],
          "transitions": []
        }
      ],
      "ism_template": [
        {"index_patterns": ["logs-*"], "priority": 100}
      ]
    }
  }' | jq '._id'
"logs-retention"

The ism_template only applies to indexes created after the policy. Attach it to the existing logs-app index manually:

curl -sk -u "admin:${OS_PASS}" -X POST "https://localhost:9200/_plugins/_ism/add/logs-app" | jq
{
  "updated_indices": 1,
  "failures": false,
  "failed_indices": []
}

Confirm which policy manages the index:

curl -sk -u "admin:${OS_PASS}" "https://localhost:9200/_plugins/_ism/explain/logs-app" | jq '."logs-app"."index.plugins.index_state_management.policy_id"'
"logs-retention"

A common next step for daily indexes (logs-2026.09.25, and so on) is to have your log shipper write one index per day so this policy deletes whole days at a time.

Step 7 - Installing OpenSearch Dashboards

Dashboards is distributed through its own repository, signed with the same key:

echo "deb [signed-by=/etc/apt/keyrings/opensearch-release.gpg] https://artifacts.opensearch.org/releases/bundle/opensearch-dashboards/3.x/apt stable main" \
  | sudo tee /etc/apt/sources.list.d/opensearch-dashboards-3.x.list
sudo apt update
sudo apt install -y opensearch-dashboards

Install the same major and minor version as OpenSearch; Dashboards refuses to connect to a mismatched version. Compare them with:

dpkg -l opensearch opensearch-dashboards | grep ^ii

Open the Dashboards configuration:

sudo nano /etc/opensearch-dashboards/opensearch_dashboards.yml

The packaged file already points Dashboards at https://localhost:9200 with the demo kibanaserver user. Make sure these settings are present and uncommented:

server.host: "127.0.0.1"
server.port: 5601
opensearch.hosts: ["https://localhost:9200"]
opensearch.ssl.verificationMode: none
opensearch.username: kibanaserver
opensearch.password: kibanaserver

verificationMode: none is needed because the demo certificate is self-signed. Enable and start Dashboards:

sudo systemctl enable --now opensearch-dashboards

Dashboards listens only on localhost, so reach it through an SSH tunnel. On your local machine run:

ssh -L 5601:127.0.0.1:5601 your_user@your_server_ip

Then open http://localhost:5601 in your browser and log in as admin with your password. Under Dashboards Management > Index patterns, create an index pattern for logs-* with timestamp as the time field, and your test documents appear in Discover.

Troubleshooting

  • The service fails to start: read the log with sudo journalctl -u opensearch -n 100 and sudo tail -n 100 /var/log/opensearch/search-cluster.log. The most common causes are a heap larger than the available RAM and a YAML indentation error in opensearch.yml.
  • apt install fails during the security setup: the initial admin password was missing or too weak. Rerun the install with a longer password that meets the complexity rules.
  • 401 Unauthorized: the username or password is wrong. Wrap the password in single quotes so the shell does not expand characters such as $ or !.
  • Cluster health is red: at least one primary shard is unassigned. Run curl -sk -u "admin:${OS_PASS}" "https://localhost:9200/_cluster/allocation/explain?pretty" to see why; on a single node, a full disk is the usual reason.
  • Dashboards shows "OpenSearch Dashboards server is not ready yet": OpenSearch is down or the versions do not match. Check both services with systemctl status.

Conclusion

You now have OpenSearch 3.x running on Ubuntu 24.04 with TLS and authentication, an index with an explicit mapping, a retention policy and OpenSearch Dashboards reachable through an SSH tunnel. Before you use it in production, replace the demo certificates and the default kibanaserver password with your own, and create dedicated users with limited roles for each application. When one node is no longer enough, add two more nodes to form a three-node cluster with replicas, and configure snapshots to an object storage repository for backups.