Elasticsearch is a distributed search and analytics engine built on Apache Lucene. Applications use it for full-text search, log analytics and filtering large datasets in near real time. In this tutorial you will install Elasticsearch 9 on Ubuntu 24.04 from Elastic's official APT repository, run it as a secured single-node cluster, size the JVM heap for your server, and create, index and search your first documents over the REST API.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS, for example a CubePath VPS.
- At least 4 GB of RAM and 2 vCPUs. Elasticsearch runs on less, but it will be slow and the JVM heap will be too small for real data.
- Free disk space for your indexes. Elasticsearch stops allocating data to a node once its disk is 85 percent full, so plan headroom.
- A non-root user with
sudoprivileges.
You do not need to install Java: the Elasticsearch package includes its own bundled JDK.
Step 1 - Adding the Elastic APT repository
Elastic publishes signed packages for each major version. First install the tools needed to fetch the signing key:
sudo apt update
sudo apt install curl gpg
Download the Elastic GPG key and store it in /etc/apt/keyrings:
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /etc/apt/keyrings/elasticsearch.gpg
Add the repository for the 9.x release line, pinned to that key:
echo "deb [signed-by=/etc/apt/keyrings/elasticsearch.gpg] https://artifacts.elastic.co/packages/9.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-9.x.list
Refresh the package index and confirm that the elasticsearch package now comes from Elastic:
sudo apt update
apt-cache policy elasticsearch
elasticsearch:
Installed: (none)
Candidate: 9.x.x
Version table:
9.x.x 500
500 https://artifacts.elastic.co/packages/9.x/apt stable/main amd64 Packages
Step 2 - Installing Elasticsearch
Install the package:
sudo apt install elasticsearch
During installation, Elasticsearch configures security automatically: it enables authentication, generates TLS certificates for the HTTP and transport layers, and creates a password for the built-in elastic superuser. The output includes a block like this:
--------------------------- Security autoconfiguration information ------------------------------
Authentication and authorization are enabled.
TLS for the transport and HTTP layers is enabled and configured.
The generated password for the elastic built-in superuser is : your_generated_password
...
Copy the password to a password manager now. If you miss it, you can generate a new one in Step 5.
The package does not start the service. You will configure it first.
Step 3 - Configuring the node
The main configuration file is /etc/elasticsearch/elasticsearch.yml. Open it:
sudo nano /etc/elasticsearch/elasticsearch.yml
Set a cluster name and a node name near the top of the file. Uncomment and edit these lines:
cluster.name: my-application
node.name: node-1
Leave network.host commented out, so the transport layer (node-to-node traffic) stays on the loopback interface.
Scroll to the end of the file. The installer appended a BEGIN SECURITY AUTO CONFIGURATION block with xpack.security.* settings, a cluster.initial_master_nodes line containing your hostname and this line:
http.host: 0.0.0.0
That setting makes the REST API on port 9200 listen on every interface, including the public one. When the application runs on the same server, change it so the API is only reachable locally:
http.host: 127.0.0.1
Leave the rest of the block as it is: it is what turns on TLS and authentication.
NoteDo not add
discovery.type: single-nodeto this file. It conflicts with thecluster.initial_master_nodessetting written by the installer and Elasticsearch will refuse to start.
Save and close the file.
Step 4 - Setting the JVM heap size
Elasticsearch sizes its heap automatically based on total RAM, but setting it explicitly makes memory use predictable. The usual rule is half of the server's RAM, never above about 31 GB, leaving the other half for the operating system's file cache, which Lucene relies on heavily.
Create a heap options file. On a 4 GB server, use 2 GB:
sudo nano /etc/elasticsearch/jvm.options.d/heap.options
-Xms2g
-Xmx2g
Always set -Xms and -Xmx to the same value so the heap never resizes at runtime. Do not edit the main jvm.options file; files in jvm.options.d/ survive package upgrades.
The Debian package already sets vm.max_map_count to the value Elasticsearch needs. Verify it:
sysctl vm.max_map_count
vm.max_map_count = 262144
Step 5 - Starting Elasticsearch
Reload systemd, then enable and start the service:
sudo systemctl daemon-reload
sudo systemctl enable --now elasticsearch
Startup takes 20 to 60 seconds. Check the status:
sudo systemctl status elasticsearch
● elasticsearch.service - Elasticsearch
Loaded: loaded (/usr/lib/systemd/system/elasticsearch.service; enabled; preset: enabled)
Active: active (running) since ...
If you did not save the generated password, reset it now. The tool prints a new random password:
sudo /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic
To avoid typing the password in each command, read it into a shell variable for this session:
read -rs ELASTIC_PASSWORD && export ELASTIC_PASSWORD
Now query the node over HTTPS. The --cacert option points to the CA certificate generated during installation, so curl can verify the connection:
sudo curl --cacert /etc/elasticsearch/certs/http_ca.crt -u "elastic:$ELASTIC_PASSWORD" https://localhost:9200
{
"name" : "node-1",
"cluster_name" : "my-application",
"cluster_uuid" : "...",
"version" : {
"number" : "9.x.x",
...
},
"tagline" : "You Know, for Search"
}
sudo is needed only because the certificate directory is readable by root and the elasticsearch group. To use the certificate as your regular user, copy it:
sudo cp /etc/elasticsearch/certs/http_ca.crt ~/http_ca.crt
sudo chown "$USER": ~/http_ca.crt
Check the cluster health:
curl --cacert ~/http_ca.crt -u "elastic:$ELASTIC_PASSWORD" "https://localhost:9200/_cluster/health?pretty"
{
"cluster_name" : "my-application",
"status" : "green",
"number_of_nodes" : 1,
...
}
A single node shows green until you create an index with replicas. Indexes with replicas show yellow, because a replica cannot live on the same node as its primary. Step 6 creates indexes with zero replicas to avoid that.
Step 6 - Indexing and searching documents
Create an index called products with one shard and no replicas, and define the field types:
curl --cacert ~/http_ca.crt -u "elastic:$ELASTIC_PASSWORD" -X PUT "https://localhost:9200/products" \
-H 'Content-Type: application/json' -d '
{
"settings": { "number_of_shards": 1, "number_of_replicas": 0 },
"mappings": {
"properties": {
"name": { "type": "text" },
"category": { "type": "keyword" },
"price": { "type": "float" },
"description": { "type": "text" }
}
}
}'
{"acknowledged":true,"shards_acknowledged":true,"index":"products"}
Index a few documents in one request with the bulk API. The refresh=true parameter makes them searchable immediately:
curl --cacert ~/http_ca.crt -u "elastic:$ELASTIC_PASSWORD" -X POST "https://localhost:9200/products/_bulk?refresh=true" \
-H 'Content-Type: application/x-ndjson' --data-binary '
{"index":{"_id":"1"}}
{"name":"Mechanical keyboard","category":"peripherals","price":89.9,"description":"Compact keyboard with brown switches"}
{"index":{"_id":"2"}}
{"name":"Wireless mouse","category":"peripherals","price":29.5,"description":"Ergonomic mouse with silent buttons"}
{"index":{"_id":"3"}}
{"name":"27 inch monitor","category":"displays","price":249.0,"description":"IPS monitor with adjustable stand"}
'
The response should contain "errors":false.
Run a full-text search for "keyboard" in the name and description fields, filtered by price:
curl --cacert ~/http_ca.crt -u "elastic:$ELASTIC_PASSWORD" "https://localhost:9200/products/_search?pretty" \
-H 'Content-Type: application/json' -d '
{
"query": {
"bool": {
"must": { "multi_match": { "query": "keyboard", "fields": ["name", "description"] } },
"filter": { "range": { "price": { "lte": 100 } } }
}
}
}'
"hits" : {
"total" : { "value" : 1, "relation" : "eq" },
"max_score" : ...,
"hits" : [
{
"_index" : "products",
"_id" : "1",
...
"_source" : { "name" : "Mechanical keyboard", ... }
Step 7 - Creating a user for your application
Do not use the elastic superuser in application code. Create an API key that can only read and write the products index:
curl --cacert ~/http_ca.crt -u "elastic:$ELASTIC_PASSWORD" -X POST "https://localhost:9200/_security/api_key?pretty" \
-H 'Content-Type: application/json' -d '
{
"name": "products-app",
"role_descriptors": {
"products_rw": {
"indices": [
{ "names": ["products"], "privileges": ["read", "write", "view_index_metadata"] }
]
}
}
}'
The response contains an encoded value. Store it in your application's secrets and send it in the Authorization header:
curl --cacert ~/http_ca.crt -H "Authorization: ApiKey your_encoded_api_key" "https://localhost:9200/products/_count"
{"count":3,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0}}
Step 8 - Allowing access from an application server (optional)
Skip this step if the application runs on the same server.
To let another server on your private network connect, make the REST API listen on localhost and on this server's private IP. Edit /etc/elasticsearch/elasticsearch.yml and change the http.host line from Step 3, replacing your_private_ip:
http.host: [127.0.0.1, your_private_ip]
Restart the service and allow port 9200 only from the application server, replacing app_server_ip:
sudo systemctl restart elasticsearch
sudo ufw allow from app_server_ip to any port 9200 proto tcp
Copy http_ca.crt to the application server so its client can verify the TLS certificate. Never open port 9200 to the whole internet.
Troubleshooting
- The service fails to start or times out: read the node log with
sudo journalctl -u elasticsearch -n 50 --no-pagerandsudo tail -n 100 /var/log/elasticsearch/my-application.log(the file is named aftercluster.name). A heap larger than available RAM is a common cause. curl: (60) SSL certificate problem: you are not passing the CA certificate. Add--cacert ~/http_ca.crt, and usehttps://, nothttp://.security_exceptionwith status 401: wrong password or an expired shell variable. Reset the password withelasticsearch-reset-passwordas shown in Step 5.- Cluster health is
yellow: some index has replicas that cannot be allocated on a single node. Set them to zero with aPUT /index_name/_settingsrequest containing{"index":{"number_of_replicas":0}}. - Writes fail with
cluster_block_exceptionandread_only_allow_delete: the disk passed the flood-stage watermark (95 percent). Free disk space; the block is removed automatically once usage drops.
Conclusion
You installed Elasticsearch 9 on Ubuntu 24.04 from the official repository, kept its built-in TLS and authentication, set a fixed JVM heap and indexed and searched documents with a dedicated API key. Next, you can install Kibana from the same repository to explore your data visually, use an official client library (Python, Node.js, PHP) instead of curl, or register a snapshot repository so you can back up your indexes.
