Install Elasticsearch or OpenSearch
Install Elasticsearch 8 or OpenSearch 2 on Linux, tune JVM heap, configure security, set up single-node or cluster mode, and verify with live index operations.
Before you start
- ▸A server with at least 4 GB RAM (8 GB+ recommended for production)
- ▸sudo or root access
- ▸Outbound HTTPS access to download packages and GPG keys
- ▸Basic familiarity with systemd service management and curl
Elasticsearch and OpenSearch are both Lucene-based distributed search and analytics engines. OpenSearch is the AWS-maintained Apache 2.0 fork of Elasticsearch 7.10, created after Elastic moved to the SSPL license. For most new deployments, OpenSearch is the safer open-source choice; Elasticsearch remains the right pick if you need X-Pack features or Elastic Cloud integration. This guide covers installing either engine on a single node, sizing the JVM correctly, locking down security, and verifying the cluster with real index operations.
Choose Your Engine
The installation paths are similar but not identical. Decide before you start:
- Elasticsearch 8.x — security enabled by default, requires enrollment tokens for cluster formation.
- OpenSearch 2.x — security plugin ships enabled, uses its own demo certificates out of the box.
Both require a modern JVM, but they bundle their own JDK — you do not need to install Java separately.
System Prerequisites
Both engines are memory-hungry and require OS-level tuning. Do this on every node before installing anything.
Set vm.max_map_count
The default kernel limit on memory-mapped areas is too low for Lucene.
sudo sysctl -w vm.max_map_count=262144
Make it permanent:
echo 'vm.max_map_count=262144' | sudo tee /etc/sysctl.d/99-search.conf
sudo sysctl --system
Raise the open-file limit
sudo tee /etc/security/limits.d/search.conf <<'EOF'
elasticsearch - nofile 65535
opensearch - nofile 65535
EOF
Disable swap
Both engines will log warnings and perform poorly if memory is swapped out.
sudo swapoff -a
# Comment out swap lines in /etc/fstab to survive reboots
JVM Heap Sizing
This is the single most impactful tuning decision. Rules that apply to both engines:
- Set
-Xmsand-Xmxto the same value to avoid heap resizing at runtime. - Do not exceed 50 % of total RAM — the other half is needed by the OS page cache for Lucene file access.
- Do not go above 31 GB. Above this threshold the JVM can no longer use compressed ordinary object pointers (compressed oops), which wastes memory and hurts GC performance.
- Minimum practical heap for a test node: 1 GB. Minimum for production: 4–8 GB.
Edit the JVM options file after installation (paths shown below). Both engines use a jvm.options.d/ drop-in directory — prefer that over editing the main file.
# Elasticsearch
echo '-Xms4g
-Xmx4g' | sudo tee /etc/elasticsearch/jvm.options.d/heap.options
# OpenSearch
echo '-Xms4g
-Xmx4g' | sudo tee /etc/opensearch/jvm.options.d/heap.options
Install Elasticsearch 8.x
Debian / Ubuntu
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch \
| sudo gpg --dearmor -o /usr/share/keyrings/elasticsearch-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/elasticsearch-keyring.gpg] \
https://artifacts.elastic.co/packages/8.x/apt stable main" \
| sudo tee /etc/apt/sources.list.d/elasticsearch.list
sudo apt update && sudo apt install -y elasticsearch
Fedora / RHEL / Rocky
sudo rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch
sudo tee /etc/yum.repos.d/elasticsearch.repo <<'EOF'
[elasticsearch]
name=Elasticsearch repository for 8.x packages
baseurl=https://artifacts.elastic.co/packages/8.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md
EOF
sudo dnf install -y elasticsearch
Enable and start
sudo systemctl daemon-reload
sudo systemctl enable --now elasticsearch
On first start Elasticsearch 8.x prints an auto-generated elastic user password and an enrollment token to the journal. Capture them immediately:
sudo journalctl -u elasticsearch --no-pager | grep -E 'password|enrollment'
Install OpenSearch 2.x
Debian / Ubuntu
curl -o- https://artifacts.opensearch.org/publickeys/opensearch.pgp \
| sudo gpg --dearmor -o /usr/share/keyrings/opensearch-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/opensearch-keyring.gpg] \
https://artifacts.opensearch.org/packages/2.x/apt stable main" \
| sudo tee /etc/apt/sources.list.d/opensearch.list
sudo apt update && sudo apt install -y opensearch
Fedora / RHEL / Rocky
sudo rpm --import https://artifacts.opensearch.org/publickeys/opensearch.pgp
sudo tee /etc/yum.repos.d/opensearch.repo <<'EOF'
[opensearch-2.x]
name=OpenSearch 2.x repository
baseurl=https://artifacts.opensearch.org/packages/2.x/yum
gpgcheck=1
gpgkey=https://artifacts.opensearch.org/publickeys/opensearch.pgp
enabled=1
autorefresh=1
EOF
sudo dnf install -y opensearch
Enable and start
sudo systemctl daemon-reload
sudo systemctl enable --now opensearch
Security Configuration
Elasticsearch 8.x
Security is on by default with TLS on the transport and HTTP layers. For a single-node lab, the simplest path is to reset the built-in password after verifying the node is up:
sudo /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic -i
The self-signed CA certificate lives at /etc/elasticsearch/certs/http_ca.crt. Pass it to curl with --cacert or copy it to clients.
OpenSearch 2.x
The security plugin runs with auto-generated demo certificates on install. For any non-throwaway environment, replace them. At minimum, run the demo configuration script and then change default passwords:
sudo /usr/share/opensearch/plugins/opensearch-security/tools/install_demo_configuration.sh -y -i -s
sudo systemctl restart opensearch
Change the admin password via the security REST API or the securityadmin.sh tool once the node is healthy.
Single-Node vs. Cluster Configuration
For a single node, add these lines to the main config file to prevent the node from waiting for peers indefinitely:
# /etc/elasticsearch/elasticsearch.yml OR /etc/opensearch/opensearch.yml
discovery.type: single-node
cluster.name: my-cluster
node.name: node-1
network.host: 127.0.0.1 # change to 0.0.0.0 only when firewalled
For a multi-node cluster, set discovery.seed_hosts and cluster.initial_master_nodes (Elasticsearch) or cluster.initial_cluster_manager_nodes (OpenSearch 2.x, which renamed the role) to the IP addresses and names of your seed nodes. Keep an odd number of master-eligible nodes (3, 5) to avoid split-brain.
Firewall Rules
# ufw (Ubuntu/Debian)
sudo ufw allow from 10.0.0.0/24 to any port 9200
sudo ufw allow from 10.0.0.0/24 to any port 9300 # transport layer
# firewalld (Fedora/RHEL)
sudo firewall-cmd --add-rich-rule='rule family=ipv4 source address=10.0.0.0/24 port port=9200 protocol=tcp accept' --permanent
sudo firewall-cmd --add-rich-rule='rule family=ipv4 source address=10.0.0.0/24 port port=9300 protocol=tcp accept' --permanent
sudo firewall-cmd --reload
Never expose port 9200 directly to the internet without authentication and TLS.
Verify: Cluster Health and Basic Indexing
Check cluster health
# Elasticsearch (replace PASSWORD and path to CA cert as needed)
curl -u elastic:PASSWORD --cacert /etc/elasticsearch/certs/http_ca.crt \
https://localhost:9200/_cluster/health?pretty
# OpenSearch (demo certs, for testing only)
curl -ku admin:admin https://localhost:9200/_cluster/health?pretty
Expect "status": "green" on a single node with no indices, or "yellow" once you have indices with replicas that cannot be assigned.
Create an index and ingest a document
curl -ku admin:admin -X PUT https://localhost:9200/books \
-H 'Content-Type: application/json' \
-d '{"settings":{"number_of_shards":1,"number_of_replicas":0}}'
curl -ku admin:admin -X POST https://localhost:9200/books/_doc \
-H 'Content-Type: application/json' \
-d '{"title":"The Linux Command Line","author":"William Shotts","year":2019}'
Run a search
curl -ku admin:admin -X GET https://localhost:9200/books/_search?pretty \
-H 'Content-Type: application/json' \
-d '{"query":{"match":{"title":"linux"}}}'
Troubleshooting
- Node won't start, exits immediately — check
sudo journalctl -u elasticsearch -n 60. The most common causes arevm.max_map_counttoo low or heap set larger than available RAM. - Status stays red — run
/_cluster/allocation/explain?prettyto see why shards are unassigned. On a single node, setnumber_of_replicas: 0on all indices. - Authentication errors on OpenSearch — after changing config or certificates, the security index may need to be re-initialized with
securityadmin.sh. - High GC pressure / OutOfMemoryError — heap is either too small or above the 31 GB compressed-oops threshold. Reduce or cap it at 30 GB.
- Port 9200 connection refused — confirm
network.hostis set and the service is running:sudo systemctl status elasticsearch.
Frequently asked questions
- Do I need to install Java before installing Elasticsearch or OpenSearch?
- No. Both packages bundle a compatible JDK. Installing a system JVM separately is unnecessary and can cause conflicts if JAVA_HOME is set to the wrong version.
- Why does my single-node cluster show yellow instead of green health?
- Yellow means primary shards are assigned but replica shards are not. On a single node there is nowhere to place replicas. Set number_of_replicas to 0 on your indices and the status will turn green.
- What is the difference between Elasticsearch 8 and OpenSearch 2 security models?
- Elasticsearch 8 enables TLS and authentication by default and generates certificates automatically on first start. OpenSearch 2 ships the security plugin enabled with demo self-signed certificates that must be replaced for production use.
- How many master-eligible nodes should a production cluster have?
- Always an odd number — 3 is the standard minimum. This ensures a quorum can be reached and prevents split-brain if one node loses network connectivity.
- Can I run both Elasticsearch and OpenSearch on the same server?
- Technically yes, but they will conflict on ports 9200 and 9300. You would need to change the port bindings for one of them in its YAML config, and the JVM memory demands make co-hosting on anything under 16 GB RAM impractical.
Related guides
Configure Prometheus Alertmanager
Configure Prometheus Alertmanager with routing trees, receivers, inhibition rules, grouping, Go templates, and PagerDuty/Slack on-call integrations.
Build an Intranet Server on Linux
Set up a complete small-office intranet on one Linux box: Nginx web server, dnsmasq local DNS, Samba file sharing, and a Wiki.js team wiki.
Build an nftables Firewall Script
Build a complete nftables firewall from scratch: tables, chains, sets, default-deny input policy, service allowlisting, and persistent systemd configuration.
Caddy as a Reverse Proxy
Set up Caddy as a reverse proxy with automatic HTTPS, load balancing, WebSocket passthrough, reusable snippets, and header control — no certbot required.