Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Opensearch Ubuntu
GUIDEInstall Guides

How to Install OpenSearch on Ubuntu 24.04 VPS: AWS-Backed Elasticsearch Alternative

29 min read

How to Install OpenSearch on Ubuntu 24.04 VPS: AWS-Backed Elasticsearch Alternative

OpenSearch is the Apache 2.0-licensed fork of Elasticsearch 7.10.2 that AWS, SAP, Uber and dozens of other companies now maintain through the OpenSearch Software Foundation under the Linux Foundation. It gives you the same distributed full-text search, JSON document store, analytics engine and Kibana-equivalent dashboards you got from the old Elastic Stack, without the SSPL/ELv2 license restrictions that shipped with Elasticsearch 7.11 and later. This guide walks you through installing and operating OpenSearch 2.x on an Ubuntu 24.04 VPS: apt repository setup, opensearch.yml tuning, JVM heap sizing, the security plugin with real TLS certificates, OpenSearch Dashboards behind Nginx, S3/MinIO snapshots, reusable index templates and Index State Management policies that keep log indices under control.

Skip the plumbing? Launch an 8 GB CloudCore Professional VPS and follow this guide end to end in under an hour. Single-node OpenSearch plus Dashboards runs comfortably for up to 200 GB of hot data.

Table of Contents

  • What is OpenSearch?
  • Why Self-Host OpenSearch Instead of Elastic Cloud?
  • Prerequisites
  • Step 1: Prepare the Ubuntu 24.04 System
  • Step 2: Add the OpenSearch APT Repository
  • Step 3: Install OpenSearch
  • Step 4: Configure opensearch.yml
  • Step 5: Tune JVM Heap in jvm.options
  • Step 6: Replace Demo Certificates with Real TLS
  • Step 7: Manage Internal Users and Roles
  • Step 8: Install OpenSearch Dashboards
  • Step 9: Publish Dashboards Behind Nginx with Let's Encrypt
  • Step 10: Snapshots to S3 or MinIO
  • Step 11: Index Templates and ISM Policies
  • Performance Tuning
  • Troubleshooting
  • FAQ
  • Next Steps
  • What is OpenSearch?

    OpenSearch is a distributed search and analytics engine built on Apache Lucene. AWS forked the project from Elasticsearch 7.10.2 in April 2021 after Elastic changed its license from Apache 2.0 to the dual SSPL / ELv2 model, and in September 2024 AWS transferred ownership to the OpenSearch Software Foundation under the Linux Foundation. The project is now Apache 2.0 licensed in perpetuity and maintained by a cross-vendor community including AWS, SAP, Uber, Aryn, Aiven and Canonical.

    Functionally, OpenSearch covers the same use cases as the original Elastic Stack. Full-text search is the flagship workload: OpenSearch powers e-commerce search bars, documentation portals, SaaS "command palette" features and internal knowledge bases with features like BM25 ranking, edge n-grams, synonyms, phonetic matching, and the newer k-NN and neural search plugins for vector and hybrid semantic search. Log and event analytics is the other dominant use case: applications, reverse proxies and Kubernetes clusters ship structured JSON events through Fluent Bit, Vector or Logstash into OpenSearch, and operators query and visualise them in OpenSearch Dashboards. APM and observability workloads use the Data Prepper pipeline plus the Trace Analytics plugin to store OpenTelemetry traces alongside logs and metrics. Security analytics uses prebuilt Sigma rule packs to detect suspicious activity across firewall, VPN and endpoint logs.

    OpenSearch 2.x ships the core engine, the Security plugin (role-based access control, TLS, SAML/OIDC SSO), the Alerting plugin (per-query monitors with destinations like Slack, email and webhooks), Index State Management (ILM equivalent), Anomaly Detection (Random Cut Forest), the SQL and PPL query plugins, Cross-Cluster Replication, and the Machine Learning Commons plugin that lets you deploy embeddings and re-ranking models directly inside the cluster. All of it is open source, licensed Apache 2.0, and included in the default distribution.

    Why Self-Host OpenSearch Instead of Elastic Cloud?

    Picking OpenSearch over Elasticsearch or a hosted Elastic Cloud cluster is usually a license, cost or data-sovereignty decision:

    • Truly open-source license — OpenSearch is Apache 2.0. You can run it in commercial products, embed it in appliances you ship to customers, or offer it as a managed service without the SSPL restrictions that block you from doing the same with Elasticsearch 7.11+.
    • Security plugin included, no paywall — Role-based access control, TLS on every transport and HTTP port, audit logging, SAML and OIDC SSO, and field/document-level security are all in the default distribution. On Elasticsearch, several of those live behind the Platinum or Enterprise subscription.
    • Predictable flat-rate cost — A CloudCore Professional VPS is EUR 19.99 per month regardless of how many documents you index. An equivalent Elastic Cloud deployment (2 zones, hot + Kibana + ML) typically starts around USD 150-200 per month and scales linearly with retained data.
    • Data residency and GDPR — Hosting in an EU data centre you know the exact location of is much easier to defend to auditors than a shared SaaS tenant. You control retention, backups, and who has access to the encryption keys.
    • No telemetry, no forced upgrades — Self-hosted OpenSearch never phones home, and you choose when to take 2.x point releases. Elastic Cloud forces minor-version upgrades on a schedule that may not line up with your change-management window.
    • Full plugin control — Install repository-s3, analysis-icu, analysis-kuromoji, k-NN, community plugins like opensearch-cjk-nori, or write your own Java plugins. Managed services typically restrict you to a curated list.
    • Ecosystem compatibility — Most Elasticsearch clients, Logstash pipelines, Fluent Bit outputs, Beats shippers and Grafana data sources work against OpenSearch with a one-line compatibility header or the native OpenSearch plugin.

    Cost Comparison: Elastic Cloud vs. Self-Hosted OpenSearch

    ScenarioElastic Cloud (Standard)Elastic Cloud (Platinum)Self-Hosted OpenSearch (VPS)
    Monthly cost, 8 GB hot tier~USD 95/mo~USD 175/moEUR 19.99/mo
    Security (RBAC, TLS, SSO)Included from StandardIncludedIncluded
    Machine learning / anomaly detectionNoYesYes (ML Commons + Anomaly Detection)
    Cross-cluster replicationPlatinum onlyYesYes
    Snapshot destinationElastic-managedElastic-managedAny S3 / MinIO / Azure Blob / GCS
    Data egress chargesYesYesNone on VPS
    Typical break-even vs. self-host~10 GB retained~5 GB retained--
    For logging and search workloads beyond a tiny proof of concept, self-hosting pays for itself within the first month.

    Prerequisites

    Before you begin, make sure you have:

    • A VPS running Ubuntu 24.04 LTS with root or sudo access
    • SSH access to your server
    • At least 8 GB of RAM (4 GB minimum for a dev node, 16 GB+ recommended for production)
    • At least 50 GB of free SSD disk space (100 GB+ recommended for logging workloads)
    • A domain name with an A record pointing to the VPS (for the Dashboards TLS certificate)
    Recommended Plan: CloudCore Professional
    >
    For a single-node OpenSearch + Dashboards deployment retaining 30 days of application logs, we recommend the CloudCore Professional plan:
    >
    - 6 vCPU cores
    - 12 GB RAM (4-6 GB JVM heap, rest for page cache)
    - 100 GB NVMe SSD
    - Unmetered bandwidth
    - EUR 19.99/month
    >
    For multi-node production clusters or heavier ingest (> 50 GB/day), scale up to 16 GB or 32 GB plans, or distribute across three nodes for true high availability.

    Connect to your server via SSH:

    bash
    ssh root@your-server-ip

    Step 1: Prepare the Ubuntu 24.04 System

    Update the package index and install prerequisites used throughout this guide.

    bash
    sudo apt update && sudo apt upgrade -y
    sudo apt install -y curl gnupg2 ca-certificates apt-transport-https lsb-release ufw

    OpenSearch 2.x bundles its own OpenJDK runtime, so you do not need to install Java separately. If you want to point OpenSearch at a system JDK, install openjdk-21-jdk-headless and set OPENSEARCH_JAVA_HOME, but the bundled runtime is the supported path.

    Raise vm.max_map_count

    Lucene memory-maps index segments, and OpenSearch refuses to start until the kernel allows enough mappings:

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

    Verify:

    bash
    sysctl vm.max_map_count

    Expected output:

    text
    vm.max_map_count = 262144

    Disable swap

    Swapping kills a search node's latency. Disable it for this boot and permanently in /etc/fstab:

    bash
    sudo swapoff -a
    sudo sed -i '/ swap / s/^/#/' /etc/fstab

    Raise file descriptor and memlock limits

    Create a systemd drop-in for the opensearch service (it will be installed in Step 3):

    bash
    sudo mkdir -p /etc/systemd/system/opensearch.service.d
    sudo tee /etc/systemd/system/opensearch.service.d/override.conf > /dev/null <<'EOF'
    [Service]
    LimitMEMLOCK=infinity
    LimitNOFILE=65535
    LimitNPROC=4096
    EOF

    Step 2: Add the OpenSearch APT Repository

    OpenSearch publishes signed .deb packages at artifacts.opensearch.org. Add the signing key and the 2.x apt source:

    bash
    curl -o- https://artifacts.opensearch.org/publickeys/opensearch.pgp | \
      sudo gpg --dearmor --batch --yes -o /usr/share/keyrings/opensearch-keyring

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

    echo "deb [signed-by=/usr/share/keyrings/opensearch-keyring] https://artifacts.opensearch.org/releases/bundle/opensearch-dashboards/2.x/apt stable main" | \ sudo tee /etc/apt/sources.list.d/opensearch-dashboards-2.x.list

    sudo apt update

    Expected output (abbreviated):

    text
    Get:1 https://artifacts.opensearch.org/releases/bundle/opensearch/2.x/apt stable InRelease
    Get:2 https://artifacts.opensearch.org/releases/bundle/opensearch-dashboards/2.x/apt stable InRelease
    ...
    Reading package lists... Done

    Step 3: Install OpenSearch

    Since OpenSearch 2.12 the security plugin requires an initial admin password at install time. Set one as an environment variable before calling apt:

    bash
    sudo env OPENSEARCH_INITIAL_ADMIN_PASSWORD='ReplaceWithAStrongPassphrase!2026' \
      apt install -y opensearch

    Pick a password that is at least 16 characters, mixes cases, digits and symbols. Record it in your password manager — you will need it for the first login to Dashboards and for calls with curl -u admin:....

    Enable and start the service:

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

    Wait 30-60 seconds for the first startup (the security plugin initialises the .opendistro_security index), then verify:

    bash
    curl -k -u admin:'ReplaceWithAStrongPassphrase!2026' https://localhost:9200

    Expected output:

    json
    {
      "name" : "node-1",
      "cluster_name" : "opensearch",
      "cluster_uuid" : "X8WZrH3tSfGq-...",
      "version" : {
        "distribution" : "opensearch",
        "number" : "2.17.1",
        "build_type" : "deb",
        "lucene_version" : "9.11.1"
      },
      "tagline" : "The OpenSearch Project: https://opensearch.org/"
    }

    If the call hangs or returns a connection refused, check sudo journalctl -u opensearch -n 100 --no-pager.

    Step 4: Configure opensearch.yml

    The primary configuration file lives at /etc/opensearch/opensearch.yml. Back up the default and replace the relevant sections:

    bash
    sudo cp /etc/opensearch/opensearch.yml /etc/opensearch/opensearch.yml.orig
    sudo nano /etc/opensearch/opensearch.yml

    For a single-node VPS deployment, use:

    yaml
    # ---------- Cluster ----------
    cluster.name: dm-search
    node.name: node-1
    node.roles: [cluster_manager, data, ingest]

    ---------- Paths ----------

    path.data: /var/lib/opensearch path.logs: /var/log/opensearch

    ---------- Network ----------

    network.host: 0.0.0.0 http.port: 9200 transport.port: 9300

    ---------- Discovery ----------

    discovery.type: single-node

    ---------- Memory ----------

    bootstrap.memory_lock: true

    ---------- Security plugin (left enabled, demo certs for now) ----------

    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.transport.enforce_hostname_verification: false 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_default_init_securityindex: true plugins.security.authcz.admin_dn: - 'CN=A,OU=UNIT,O=ORG,L=TORONTO,ST=ONTARIO,C=CA' plugins.security.audit.type: internal_opensearch plugins.security.enable_snapshot_restore_privilege: true plugins.security.check_snapshot_restore_write_privileges: true plugins.security.restapi.roles_enabled: ["all_access", "security_rest_api_access"]

    If you plan to run a three-node cluster instead, replace discovery.type: single-node with:

    yaml
    discovery.seed_hosts: ["10.0.0.11", "10.0.0.12", "10.0.0.13"]
    cluster.initial_cluster_manager_nodes: ["node-1", "node-2", "node-3"]

    …and set network.host to the private IP on each node. You will also need matching node certificates (covered in Step 6).

    Save and exit, then restart:

    bash
    sudo systemctl restart opensearch

    Step 5: Tune JVM Heap in jvm.options

    OpenSearch reads JVM options from /etc/opensearch/jvm.options and any files in /etc/opensearch/jvm.options.d/. The default is a tiny 1 GB heap, which you will outgrow within minutes of real ingest.

    Create a drop-in:

    bash
    sudo tee /etc/opensearch/jvm.options.d/heap.options > /dev/null <<'EOF'
    -Xms4g
    -Xmx4g
    EOF

    Rules for sizing:

    • Set -Xms and -Xmx to the same value so the JVM allocates the heap upfront and never resizes.
    • Allocate no more than 50 percent of physical RAM — the rest goes to Lucene's memory-mapped index segments and the OS page cache.
    • Never exceed 31 GB (technically ~32766m). Above that threshold the JVM loses compressed object pointers and memory efficiency drops sharply.
    Recommended starting points:
    VPS RAMHeap (-Xms = -Xmx)
    4 GB2g
    8 GB4g
    12 GB (CloudCore Professional)6g
    16 GB8g
    32 GB16g
    64 GB+31g (hard cap)
    Restart to apply:

    bash
    sudo systemctl restart opensearch

    Confirm the new heap from the cluster:

    bash
    curl -sk -u admin:'ReplaceWithAStrongPassphrase!2026' \
      'https://localhost:9200/_cat/nodes?v&h=name,heap.max'

    Expected output:

    text
    name    heap.max
    node-1     4gb

    Step 6: Replace Demo Certificates with Real TLS

    Fresh installs ship with a self-signed demo certificate set (esnode.pem, esnode-key.pem, root-ca.pem, and the kirk/kirk-key.pem admin cert) in /etc/opensearch/. These are only for testing — the public key is the same on every install, so anyone can impersonate your nodes. Before you trust the cluster with real data, generate your own certificate authority and node certificates.

    Create a working directory:

    bash
    sudo mkdir -p /etc/opensearch/certs
    cd /etc/opensearch/certs

    6a. Generate a private root CA

    bash
    sudo openssl genrsa -out root-ca-key.pem 4096
    sudo openssl req -new -x509 -sha256 -key root-ca-key.pem -out root-ca.pem -days 3650 \
      -subj "/C=CA/ST=ON/L=Toronto/O=DM/OU=Infra/CN=DM Root CA"

    6b. Generate an admin certificate

    The admin certificate is how securityadmin.sh authenticates when it pushes configuration into the cluster. Its DN must match the plugins.security.authcz.admin_dn entry in opensearch.yml.

    bash
    sudo openssl genrsa -out admin-key-temp.pem 4096
    sudo openssl pkcs8 -inform PEM -outform PEM -in admin-key-temp.pem -topk8 -nocrypt -v1 PBE-SHA1-3DES -out admin-key.pem
    sudo openssl req -new -key admin-key.pem -out admin.csr \
      -subj "/C=CA/ST=ONTARIO/L=TORONTO/O=ORG/OU=UNIT/CN=A"
    sudo openssl x509 -req -in admin.csr -CA root-ca.pem -CAkey root-ca-key.pem -CAcreateserial -sha256 -out admin.pem -days 730

    6c. Generate a node certificate

    bash
    sudo openssl genrsa -out node1-key-temp.pem 4096
    sudo openssl pkcs8 -inform PEM -outform PEM -in node1-key-temp.pem -topk8 -nocrypt -v1 PBE-SHA1-3DES -out node1-key.pem
    sudo openssl req -new -key node1-key.pem -out node1.csr \
      -subj "/C=CA/ST=ON/L=Toronto/O=DM/OU=Infra/CN=node-1"

    SAN extension so transport TLS works for localhost + private IP + hostname

    sudo tee node1.ext > /dev/null <<EOF subjectAltName = DNS:node-1,DNS:localhost,IP:127.0.0.1,IP:10.0.0.11 EOF

    sudo openssl x509 -req -in node1.csr -CA root-ca.pem -CAkey root-ca-key.pem -CAcreateserial \ -sha256 -out node1.pem -days 730 -extfile node1.ext

    6d. Wire the new certs into opensearch.yml

    bash
    sudo chown -R opensearch:opensearch /etc/opensearch/certs
    sudo chmod 600 /etc/opensearch/certs/*-key.pem
    sudo chmod 644 /etc/opensearch/certs/*.pem

    Edit /etc/opensearch/opensearch.yml and replace the demo cert paths:

    yaml
    plugins.security.ssl.transport.pemcert_filepath: certs/node1.pem
    plugins.security.ssl.transport.pemkey_filepath: certs/node1-key.pem
    plugins.security.ssl.transport.pemtrustedcas_filepath: certs/root-ca.pem
    plugins.security.ssl.http.pemcert_filepath: certs/node1.pem
    plugins.security.ssl.http.pemkey_filepath: certs/node1-key.pem
    plugins.security.ssl.http.pemtrustedcas_filepath: certs/root-ca.pem
    plugins.security.authcz.admin_dn:
      - 'CN=A,OU=UNIT,O=ORG,L=TORONTO,ST=ONTARIO,C=CA'

    Restart:

    bash
    sudo systemctl restart opensearch

    Confirm with curl (note the new CA bundle):

    bash
    curl --cacert /etc/opensearch/certs/root-ca.pem \
      -u admin:'ReplaceWithAStrongPassphrase!2026' \
      https://localhost:9200

    Step 7: Manage Internal Users and Roles

    The security plugin stores users, roles and role mappings in a hidden index. You manage it through YAML files in /etc/opensearch/opensearch-security/ and push changes with securityadmin.sh.

    7a. Rotate the default passwords

    Edit /etc/opensearch/opensearch-security/internal_users.yml and replace the password hashes for admin, kibanaserver and logstash. Generate bcrypt hashes with the bundled tool:

    bash
    sudo /usr/share/opensearch/plugins/opensearch-security/tools/hash.sh -p 'YourNewAdminPassphrase!'

    Paste each returned hash into the corresponding hash: line in internal_users.yml.

    7b. Create an ingest user for Logstash/Fluent Bit

    Add a new user block:

    yaml
    ingest-writer:
      hash: "$2y$12$...bcrypt_hash_here..."
      reserved: false
      backend_roles:
        - "logstash"
      description: "Log shipper write-only account"

    7c. Create a read-only role for Dashboards users

    In /etc/opensearch/opensearch-security/roles.yml:

    yaml
    dm_reader:
      reserved: false
      cluster_permissions:
        - "cluster_composite_ops_ro"
      index_permissions:
        - index_patterns:
            - "logs-*"
            - "metrics-*"
          allowed_actions:
            - "read"
            - "search"

    Map it to a user in /etc/opensearch/opensearch-security/roles_mapping.yml:

    yaml
    dm_reader:
      reserved: false
      users:
        - "alice"
        - "bob"

    7d. Push the configuration into the cluster

    bash
    sudo /usr/share/opensearch/plugins/opensearch-security/tools/securityadmin.sh \
      -cd /etc/opensearch/opensearch-security/ \
      -icl -nhnv \
      -cacert /etc/opensearch/certs/root-ca.pem \
      -cert   /etc/opensearch/certs/admin.pem \
      -key    /etc/opensearch/certs/admin-key.pem

    Expected output:

    text
    Will update '/config' with /etc/opensearch/opensearch-security/config.yml
    Will update '/roles' with /etc/opensearch/opensearch-security/roles.yml
    ...
    Done with success

    Step 8: Install OpenSearch Dashboards

    OpenSearch Dashboards is the fork of Kibana 7.10 — same visualisation engine, Discover tab, Dev Tools and saved objects, just rebranded and free of the ELv2 license.

    bash
    sudo apt install -y opensearch-dashboards

    Edit /etc/opensearch-dashboards/opensearch_dashboards.yml:

    yaml
    server.host: "127.0.0.1"
    server.port: 5601
    server.name: "dm-dashboards"

    opensearch.hosts: ["https://localhost:9200"] opensearch.ssl.verificationMode: certificate opensearch.ssl.certificateAuthorities: ["/etc/opensearch/certs/root-ca.pem"]

    opensearch.username: "kibanaserver" opensearch.password: "YourNewKibanaServerPassphrase!"

    opensearch.requestHeadersAllowlist: ["authorization", "securitytenant"] opensearch_security.multitenancy.enabled: true opensearch_security.multitenancy.tenants.preferred: ["Private", "Global"] opensearch_security.readonly_mode.roles: ["kibana_read_only"] opensearch_security.cookie.secure: true

    Replace the opensearch.password value with whatever you set for the kibanaserver user in Step 7a.

    Enable and start:

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

    Check the log:

    bash
    sudo journalctl -u opensearch-dashboards -n 30 --no-pager

    Dashboards is now listening on 127.0.0.1:5601. We deliberately bound it to loopback — the next step publishes it on the public internet through Nginx with a proper TLS cert.

    Step 9: Publish Dashboards Behind Nginx with Let's Encrypt

    Install Nginx and Certbot:

    bash
    sudo apt install -y nginx certbot python3-certbot-nginx

    Create the virtual host:

    bash
    sudo tee /etc/nginx/sites-available/opensearch-dashboards > /dev/null <<'EOF'
    server {
        listen 80;
        server_name search.yourdomain.com;
        return 301 https://$host$request_uri;
    }

    server { listen 443 ssl http2; server_name search.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/search.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/search.yourdomain.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5;

    add_header Strict-Transport-Security "max-age=63072000" always; add_header X-Content-Type-Options nosniff; add_header X-Frame-Options DENY;

    client_max_body_size 50m;

    location / { proxy_pass http://127.0.0.1:5601; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme;

    proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";

    proxy_buffering off; proxy_read_timeout 600s; proxy_send_timeout 600s; } } EOF

    sudo ln -s /etc/nginx/sites-available/opensearch-dashboards /etc/nginx/sites-enabled/ sudo nginx -t sudo systemctl reload nginx

    Issue the certificate:

    bash
    sudo certbot --nginx -d search.yourdomain.com --redirect --agree-tos -m [email protected] -n

    Certbot installs a systemd timer that renews automatically every 60 days. Open port 443 and 80 in UFW, and keep 9200 closed to the public internet:

    bash
    sudo ufw allow 22/tcp
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw deny 9200/tcp
    sudo ufw --force enable

    Browse to https://search.yourdomain.com and log in as admin with the password you set in Step 3.

    Step 10: Snapshots to S3 or MinIO

    OpenSearch snapshots are incremental, point-in-time backups of one or more indices. They go to a repository — typically an S3 bucket, a MinIO instance, or a mounted NFS share. The repository-s3 plugin ships preinstalled.

    10a. Store credentials in the keystore

    Never put S3 keys in opensearch.yml — they would be world-readable. Use the encrypted keystore:

    bash
    sudo /usr/share/opensearch/bin/opensearch-keystore add s3.client.default.access_key
    

    paste the access key when prompted

    sudo /usr/share/opensearch/bin/opensearch-keystore add s3.client.default.secret_key

    paste the secret key when prompted

    sudo systemctl restart opensearch

    10b. Register a MinIO repository

    Assuming MinIO is reachable at https://minio.internal:9000 with a bucket named opensearch-snapshots:

    bash
    curl -X PUT --cacert /etc/opensearch/certs/root-ca.pem \
      -u admin:'ReplaceWithAStrongPassphrase!2026' \
      -H "Content-Type: application/json" \
      https://localhost:9200/_snapshot/minio-backup \
      -d '{
        "type": "s3",
        "settings": {
          "bucket": "opensearch-snapshots",
          "endpoint": "minio.internal:9000",
          "protocol": "https",
          "path_style_access": true,
          "region": "us-east-1",
          "base_path": "dm-search"
        }
      }'

    For AWS S3, drop endpoint and path_style_access and set the correct region.

    10c. Take a snapshot

    bash
    curl -X PUT --cacert /etc/opensearch/certs/root-ca.pem \
      -u admin:'ReplaceWithAStrongPassphrase!2026' \
      "https://localhost:9200/_snapshot/minio-backup/snap-$(date +%Y%m%d-%H%M%S)?wait_for_completion=false" \
      -H "Content-Type: application/json" \
      -d '{"indices": "logs-,metrics-", "include_global_state": false}'

    10d. Schedule with Snapshot Management (SM)

    Rather than crontab, use the built-in SM plugin. In Dashboards, go to Management → Snapshot Management → Create policy and set:

    • Name: nightly-logs
    • Indices: logs-,metrics-
    • Repository: minio-backup
    • Schedule: 0 2 * (02:00 UTC daily)
    • Retention: 14 snapshots
    SM handles the create/delete lifecycle so you always have exactly N snapshots retained.

    Step 11: Index Templates and ISM Policies

    11a. A reusable index template for logs

    Templates pre-configure mappings and settings for any index matching a pattern:

    bash
    curl -X PUT --cacert /etc/opensearch/certs/root-ca.pem \
      -u admin:'ReplaceWithAStrongPassphrase!2026' \
      -H "Content-Type: application/json" \
      https://localhost:9200/_index_template/logs-template \
      -d '{
        "index_patterns": ["logs-*"],
        "template": {
          "settings": {
            "number_of_shards": 1,
            "number_of_replicas": 0,
            "refresh_interval": "5s",
            "plugins.index_state_management.policy_id": "logs-rollover-30d"
          },
          "mappings": {
            "properties": {
              "@timestamp": { "type": "date" },
              "level":      { "type": "keyword" },
              "service":    { "type": "keyword" },
              "host":       { "type": "keyword" },
              "message":    { "type": "text" },
              "trace_id":   { "type": "keyword" }
            }
          }
        }
      }'

    Any new index created as logs-app-2026.04.16 automatically inherits this schema.

    11b. An ISM policy with hot → delete lifecycle

    ISM (Index State Management) is OpenSearch's answer to Elasticsearch ILM. It transitions indices through states based on age, size or doc count.

    bash
    curl -X PUT --cacert /etc/opensearch/certs/root-ca.pem \
      -u admin:'ReplaceWithAStrongPassphrase!2026' \
      -H "Content-Type: application/json" \
      https://localhost:9200/_plugins/_ism/policies/logs-rollover-30d \
      -d '{
        "policy": {
          "description": "Hot 1d, warm 7d, delete 30d",
          "default_state": "hot",
          "states": [
            {
              "name": "hot",
              "actions": [
                { "rollover": { "min_index_age": "1d", "min_primary_shard_size": "20gb" } }
              ],
              "transitions": [{ "state_name": "warm", "conditions": { "min_index_age": "1d" } }]
            },
            {
              "name": "warm",
              "actions": [
                { "force_merge": { "max_num_segments": 1 } },
                { "replica_count": { "number_of_replicas": 0 } }
              ],
              "transitions": [{ "state_name": "delete", "conditions": { "min_index_age": "30d" } }]
            },
            {
              "name": "delete",
              "actions": [{ "delete": {} }]
            }
          ],
          "ism_template": [
            { "index_patterns": ["logs-*"], "priority": 100 }
          ]
        }
      }'

    Behaviour:

    • A new logs-* index starts in the hot state.
    • After one day or 20 GB per shard, ISM rolls over to a fresh index (logs-app-000002 etc.).
    • One day later it transitions to warm, force-merges to a single segment and drops replicas.
    • Thirty days after creation it is deleted.
    Disk usage becomes predictable and you never have to run a cron job to prune old indices.

    Performance Tuning

    • Refresh interval. Default is 1 second. For log indices that nobody queries in real time, set refresh_interval: 30s or even 60s in the template. Ingest throughput can double.
    • Bulk size. Logstash, Fluent Bit and Vector all batch documents. Tune the batch size to produce 5-15 MB bulk requests. Smaller batches waste round-trips; larger batches cause 429 rejections.
    • Shards per node. Aim for no more than 20 shards per GB of JVM heap. A 4 GB heap can handle ~80 shards comfortably; past that, cluster state grows and master operations slow down. Daily rollover + ISM delete keeps you well under that ceiling.
    • Circuit breakers. The default indices.breaker.total.limit is 95 percent of heap. If you hit CircuitBreakingException: [parent] Data too large, your queries or aggregations are more memory-hungry than the heap can support — shrink the time range, add filter clauses or move to composite aggregations.
    • Disable swappiness. Already done by turning off swap in Step 1; verify with free -h.
    • Separate hot from cold data. For larger deployments, mount a slower disk (HDD or object-backed) at /var/lib/opensearch/cold, mark nodes with node.attr.temp: cold, and route old indices there via the allocation ISM action.

    Troubleshooting

    ProblemCauseSolution
    bootstrap check failure: max virtual memory areas vm.max_map_count [65530] is too lowsysctl value not appliedRe-run sudo sysctl --system and verify with sysctl vm.max_map_count.
    Error: Password for the user admin is not setFresh 2.12+ install without OPENSEARCH_INITIAL_ADMIN_PASSWORDReinstall with the env var, or set it manually via internal_users.yml + securityadmin.sh.
    OpenSearch Security not initialized in DashboardsSecurity index missingRun securityadmin.sh with the admin certificate as in Step 7d.
    Unable to authenticate user kibanaserverPassword in opensearch_dashboards.yml doesn't match hash in internal_users.ymlRegenerate a bcrypt hash with hash.sh, update both files, push with securityadmin.sh, restart Dashboards.
    cluster_block_exception [FORBIDDEN/12/index read-only / allow delete (api)]Disk high watermark (85%) reachedFree disk or raise cluster.routing.allocation.disk.watermark.high temporarily, then run PUT /_all/_settings {"index.blocks.read_only_allow_delete": null}.
    SSLHandshakeException: PKIX path building failedopensearch-dashboards trusts system CAs, but cluster uses private CASet opensearch.ssl.certificateAuthorities to the path of root-ca.pem.
    Snapshots fail with AmazonS3Exception: The AWS Access Key Id you provided does not existKeystore values not reloadedsudo systemctl restart opensearch after adding keys.

    Useful log locations

    bash
    sudo journalctl -u opensearch -f
    sudo tail -f /var/log/opensearch/dm-search.log
    sudo journalctl -u opensearch-dashboards -f
    sudo tail -f /var/log/nginx/error.log

    FAQ

    Is OpenSearch really a drop-in replacement for Elasticsearch?

    OpenSearch was forked from Elasticsearch 7.10.2 and keeps the same REST API, Query DSL, aggregations, Painless scripting language and Lucene index format for most workloads. The official Elasticsearch clients (Java, Python elasticsearch-py, Node @elastic/elasticsearch) work against OpenSearch when you set the compatibility.override_main_response_version: true flag on the cluster and send the Accept: application/vnd.elasticsearch+json; compatible-with=7 header. Logstash, Filebeat, Fluent Bit, Vector and Grafana all have first-class OpenSearch outputs. However, Elasticsearch 8.x-only features — ES|QL, the semantic_text field type, the new Vector Search APIs, and the latest ML model lifecycle — are not in OpenSearch. If you're standing up a new cluster today and don't depend on those, OpenSearch is the safer licensing and cost choice.

    Do I need the security plugin on a single-node VPS?

    Yes. Since OpenSearch 2.12 the security plugin ships enabled by default, and the cluster refuses to start without TLS configured and an admin password set. Even on a single-node deployment, you should replace the demo certificates with a real CA (Step 6), rotate the admin, kibanaserver and logstash passwords (Step 7a), and keep port 9200 bound to localhost or a private network behind Nginx. Disabling security with plugins.security.disabled: true is technically possible but leaves your cluster with no authentication at all — a single exposed port means full cluster control for anyone who finds it on Shodan.

    How much JVM heap should I allocate to OpenSearch?

    Set -Xms and -Xmx to the same value, at roughly 50 percent of physical RAM, and never exceed 31 GB so the JVM retains compressed object pointers ("compressed OOPs"). On an 8 GB CloudCore Professional VPS that means 4 GB heap, leaving the other 4 GB for Lucene's memory-mapped segments, the OS page cache, Nginx, and OpenSearch Dashboards. Monitor heap pressure with GET _nodes/stats/jvm — if old-gen occupancy sits above 75 percent after a full GC, you need more RAM (larger VPS) rather than a larger heap.

    Can OpenSearch snapshots go to MinIO or any S3-compatible store?

    Yes. The repository-s3 plugin ships in the default distribution and talks to any S3-compatible endpoint: AWS S3, Backblaze B2, Cloudflare R2, Wasabi, or a self-hosted MinIO cluster. When registering the repository, set endpoint to your server address, protocol to https, path_style_access to true for MinIO (older S3-compatible stores require this), and the correct region. Store the access key and secret key in the OpenSearch keystore with opensearch-keystore add s3.client.default.access_key — never in opensearch.yml. For production, use a dedicated bucket with lifecycle rules that move older snapshot segments to cold storage (Glacier / MinIO ILM).

    What is an ISM policy and why do I need one?

    Index State Management is OpenSearch's lifecycle engine — the equivalent of Elasticsearch ILM. A policy defines states (typically hot, warm, cold, delete) with transitions triggered by age, size, or document count, and actions performed on entry (rollover, force_merge, replica_count, allocation, snapshot, delete). For logging workloads this is not optional: without ISM, indices grow without bound, shard count explodes past the master's comfort zone, and disk fills up in weeks. The policy in Step 11b rolls over daily, force-merges after one day, and deletes after 30 days — a sensible default that keeps a 8 GB VPS healthy indefinitely on typical application logs.

    OpenSearch vs. Elasticsearch vs. Meilisearch vs. Typesense — how do I choose?

    OpenSearch and Elasticsearch are distributed, multi-node, feature-complete search and analytics platforms. Use them when you need log analytics, observability, security analytics, multi-terabyte search corpora, aggregations, or vector search at scale. OpenSearch wins on license (Apache 2.0 vs. SSPL/ELv2) and on bundled features like RBAC, SSO and alerting.

    Meilisearch and Typesense are much smaller, single-node search engines optimised for instant search UIs on product catalogues, docs sites and app search boxes. They have beautiful SDKs, typo-tolerant prefix search out of the box, and sub-10 ms latency. They do not do log analytics, distributed ingest, or aggregations.

    Choose OpenSearch when you need the whole stack: logs, APM, security, full-text search, and vectors on the same cluster. Choose Meilisearch or Typesense when your only job is an autocomplete bar on a product catalogue with under 10 million documents.

    Next Steps

    Now that OpenSearch and Dashboards are running on your VPS, here are recommended directions:

    • Ship logs with Fluent Bit or Vector — Install Fluent Bit on your application servers and point its opensearch output at https://search.yourdomain.com:443. You will have structured logs flowing within minutes.
    • Set up alerting — The Alerting plugin is already installed. Create monitors in Dashboards that watch for error-rate spikes, failed logins, or slow queries, and wire them to Slack, PagerDuty or a generic webhook.
    • Compare with Elasticsearch — If you want to evaluate both before committing, follow our Install Elasticsearch on Ubuntu 24.04 guide and benchmark the two side-by-side.
    • Try Graylog for a purpose-built log frontend — Graylog sits on top of OpenSearch and adds pipelines, streams, and a UI optimised for log triage. See Install Graylog on Ubuntu 24.04.
    • Consider Loki for cheaper log retention — If you mostly grep logs and rarely run aggregations, Grafana Loki indexes only labels and stores raw logs in object storage at a fraction of the cost. See Install Loki on Ubuntu 24.04.
    • Read the official docs — The complete reference lives at opensearch.org/docs/ and covers every plugin, setting and API endpoint touched in this guide.

    Run OpenSearch on the right VPS.
    >
    OpenSearch is memory-hungry and benefits hugely from NVMe storage. Our CloudCore Professional plan gives you 6 vCPU, 12 GB RAM, and 100 GB NVMe SSD for EUR 19.99/month — enough to run a single-node cluster with 30 days of logs, Dashboards, and Nginx on the same host.
    >
    Need high availability? Provision three nodes in the same region, follow the multi-node discovery.seed_hosts configuration in Step 4, and you have a production OpenSearch cluster for under EUR 60/month — less than the entry tier of Elastic Cloud.
    >
    Deploy your OpenSearch VPS now.

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket