Data Analytics Azure

Elasticsearch 9 on Ubuntu 22.04 on Azure User Guide

| Product: Elasticsearch 9 on Ubuntu 22.04 on Azure

Overview

This image runs Elasticsearch 9 and Kibana 9 side by side on a single Ubuntu 22.04 LTS virtual machine. The Elasticsearch daemon binds to TCP 9200 over HTTPS, the transport layer binds to TCP 9300, and Kibana binds to TCP 5601. Authentication is enforced on the REST and transport layers by the Elastic xpack security subsystem using demo self signed certificates and an elastic superuser account whose password is generated on the first boot of every deployed virtual machine, so two virtual machines launched from the same gallery image never share credentials. Elasticsearch 9 ships with SIMD optimised vector search, logsdb as the default index mode for time series data, and reduced memory overhead for mixed read write workloads, which makes it a strong fit for log analytics, observability, and full text search at scale.

This image is a repackaged upstream distribution of the Elasticsearch 9 AGPL base, together with Kibana for visualisation. It does not include the Elastic commercial subscription features such as Machine Learning, Graph, cross cluster replication, or the commercial Security features that require an active Elastic subscription. If you need those, please procure a subscription directly from Elastic. The image is intended for teams that want a production grade search and analytics engine with a working Kibana UI on day one, without spending hours on repository wiring, JVM tuning, systemd plumbing, TLS certificate provisioning, or security bootstrap. It is not a multi node production cluster, the TLS certificates shipped are the upstream auto generated self signed certificates, and Section 13 documents the recommended path for replacing them with certificates issued by your own certificate authority before you put production traffic through the cluster.

The brand is lowercase cloudimg throughout this guide. All cloudimg URLs in this guide use the form https://www.cloudimg.co.uk.

Prerequisites

Before you deploy this image you need:

  • A Microsoft Azure subscription where you can create resource groups, virtual networks, and virtual machines
  • Azure role permissions equivalent to Contributor on the target resource group
  • An SSH public key for first login to the admin user account
  • A virtual network and subnet in the same region as the Azure Compute Gallery the image is published into, with an associated network security group
  • The Azure CLI (az version 2.50 or later) installed locally if you intend to use the CLI deployment path in Section 2
  • The cloudimg Elasticsearch 9 offer enabled on your tenant in Azure Marketplace

Step 1: Deploy the Virtual Machine from the Azure Portal

Navigate to Marketplace in the Azure Portal, search for Elasticsearch 9, and select the cloudimg publisher entry. Click Create to begin the wizard.

On the Basics tab choose your subscription, target resource group, and region. The region must match the region your Azure Compute Gallery exposes the image in. Set the virtual machine name. Choose a size of Standard_D4s_v3 or larger, which provides four virtual CPUs and sixteen gigabytes of memory. Elasticsearch is a JVM workload and benefits from a heap of around eight gigabytes for production, which must not exceed fifty percent of host memory. Kibana uses around seven hundred megabytes of resident memory. Choose SSH public key as the authentication type, set the username to a name of your choice, and paste your SSH public key.

On the Disks tab the recommended OS disk type is Premium SSD. Leave the OS disk size at the default. You can attach a separate Premium SSD data disk now if you intend to move the Elasticsearch data directory to it later, or you can do that after the cluster is running by following Section 12.

On the Networking tab select your existing virtual network and subnet. Attach a network security group that allows inbound TCP 22 from your management IP range, inbound TCP 5601 only from the virtual network CIDR or a jump host, and inbound TCP 9200 only from the application servers that need it. Do not expose 9200 or 5601 to the public internet. The demo self signed certificates ship with a node certificate that was generated on first boot and that has no external chain of trust, so any exposure beyond a private network requires the certificate replacement described in Section 13.

On the Management, Monitoring, and Advanced tabs the defaults are appropriate. Click Review + create, wait for validation to pass, then click Create. Deployment takes around three minutes to reach running state, and a further sixty to ninety seconds for Elasticsearch plus Kibana to come up inside the virtual machine.

Step 2: Deploy the Virtual Machine from the Azure CLI

If you prefer the command line, use the gallery image resource identifier as the source. The exact resource identifier is published on your Partner Center plan. A representative invocation:

RG="elasticsearch-prod"
LOCATION="eastus"
VM_NAME="elasticsearch-node-1"
ADMIN_USER="esuser"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/elasticsearch-9-ubuntu-22-04/versions/1.0.20260417"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"

az group create --name "$RG" --location "$LOCATION"

az network vnet create \
  --resource-group "$RG" \
  --name elasticsearch-vnet \
  --address-prefix 10.40.0.0/16 \
  --subnet-name elasticsearch-subnet \
  --subnet-prefix 10.40.1.0/24

az network nsg create --resource-group "$RG" --name elasticsearch-nsg

az network nsg rule create --resource-group "$RG" --nsg-name elasticsearch-nsg \
  --name allow-ssh --priority 1000 \
  --source-address-prefixes "<your-management-cidr>" \
  --destination-port-ranges 22 --protocol Tcp --access Allow

az network nsg rule create --resource-group "$RG" --nsg-name elasticsearch-nsg \
  --name allow-rest --priority 1010 \
  --source-address-prefixes 10.40.0.0/16 \
  --destination-port-ranges 9200 --protocol Tcp --access Allow

az network nsg rule create --resource-group "$RG" --nsg-name elasticsearch-nsg \
  --name allow-kibana --priority 1020 \
  --source-address-prefixes 10.40.0.0/16 \
  --destination-port-ranges 5601 --protocol Tcp --access Allow

az vm create \
  --resource-group "$RG" \
  --name "$VM_NAME" \
  --image "$GALLERY_IMAGE_ID" \
  --size Standard_D4s_v3 \
  --admin-username "$ADMIN_USER" \
  --ssh-key-values "$SSH_KEY" \
  --vnet-name elasticsearch-vnet \
  --subnet elasticsearch-subnet \
  --nsg elasticsearch-nsg \
  --public-ip-address "" \
  --os-disk-size-gb 64

The final command produces a virtual machine with no public IP, which is the recommended posture for an Elasticsearch node. Connect through a bastion host, an Azure Bastion service, or a virtual private network peered into the same virtual network.

Step 3: Connect via SSH

Connect using the admin username you chose during deployment and the private key that matches the public key you pasted into the deployment form:

ssh -i ~/.ssh/id_rsa <admin-username>@<vm-private-ip>

All subsequent commands in this guide run on the virtual machine as the admin user, escalating with sudo where a command needs root. Do not log in directly as the elasticsearch or kibana service users; those accounts have no login shell and are intended for the daemons only.

Step 4: Retrieve the Admin Credentials

The first boot of the virtual machine runs elasticsearch-firstboot.service, which resets the elastic superuser password and the kibana_system service account password to fresh random values, wires the kibana_system credentials into kibana.yml, and writes the elastic user credentials to /stage/scripts/elasticsearch-credentials.log. Read it with sudo:

sudo cat /stage/scripts/elasticsearch-credentials.log

The file contains four lines:

username=elastic
password=<generated-password>
elasticsearch_url=https://<vm-private-ip>:9200
kibana_url=http://<vm-private-ip>:5601

Copy these values somewhere safe. The password is not recoverable from the image; rotating it is documented in Section 10. Store the file off the virtual machine if your environment requires that, and then tighten the file mode or remove the file. It is owned by root and mode 0600 by default.

Step 5: Server Components

The image ships the following components:

Component Version Purpose
Elasticsearch 9.x Distributed search and analytics engine. Installed at /usr/share/elasticsearch. Runs as elasticsearch.service under the elasticsearch system user.
Kibana 9.x Web UI for search, visualisation, and cluster management. Installed at /usr/share/kibana. Runs as kibana.service under the kibana system user.
Bundled JDK Elastic managed Java runtime shipped under /usr/share/elasticsearch/jdk. The system does not have a separate JDK package installed; Elasticsearch uses the bundled JDK by default.
xpack security Enabled by default Built in authentication, TLS on transport plus HTTP, role based access control, API keys. Demo self signed certificates are generated on first boot under /etc/elasticsearch/certs/.
First boot unit oneshot elasticsearch-firstboot.service resets the elastic and kibana_system passwords once, wires Kibana to Elasticsearch, and is gated by /var/lib/elasticsearch/.firstboot-done.
Base OS Ubuntu 22.04 LTS Long term support release, standard cloud init, Azure Linux Agent.

Step 6: Filesystem Layout

Path Purpose
/usr/share/elasticsearch Elasticsearch install root (ES_HOME)
/usr/share/kibana Kibana install root (KIBANA_HOME)
/etc/elasticsearch/elasticsearch.yml Cluster config
/etc/elasticsearch/jvm.options.d/ Heap and JVM overrides (cloudimg-heap.options sets 1g default)
/etc/elasticsearch/certs/ Auto generated HTTP CA, HTTP cert, transport cert, transport CA
/etc/kibana/kibana.yml Kibana config (elasticsearch.hosts, kibana_system credentials)
/var/lib/elasticsearch Index data (move to an attached disk for production, Section 12)
/var/log/elasticsearch/ Elasticsearch daemon log directory
/var/log/kibana/ Kibana daemon log directory
/etc/systemd/system/elasticsearch-firstboot.service First boot credential generator
/etc/systemd/system/kibana.service.d/10-wait-for-firstboot.conf systemd drop in that orders Kibana after firstboot
/opt/cloudimg/bin/setEnv.sh sources ES_HOME / KIBANA_HOME / PATH
/opt/cloudimg/bin/start_all.sh starts Elasticsearch then Kibana
/opt/cloudimg/bin/stop_all.sh stops Kibana then Elasticsearch
/stage/scripts/elasticsearch-credentials.log elastic user credentials plus URLs (file mode 0600)
/stage/scripts/BUILD_VERSIONS Captured package versions at image build time

Step 7: Start, Stop, and Check Status

The two main services are managed independently through systemd. Kibana depends on Elasticsearch being up and on the first boot unit having completed, so always start Elasticsearch first when bringing the stack up from a cold stop.

sudo systemctl start elasticsearch.service
sudo systemctl status elasticsearch.service
sudo systemctl start kibana.service
sudo systemctl status kibana.service

Or use the helper script:

sudo /opt/cloudimg/bin/start_all.sh

Stopping:

sudo /opt/cloudimg/bin/stop_all.sh

Both services are enabled at boot. If you prefer Kibana not to start automatically, sudo systemctl disable kibana.service.

Log output lives under /var/log/elasticsearch/ and /var/log/kibana/. Follow the Elasticsearch daemon log with:

sudo journalctl -u elasticsearch.service -f

Or for Kibana:

sudo journalctl -u kibana.service -f

Step 8: Connect with curl and a REST Client

Load the elastic password into a shell variable and query the cluster:

ELASTIC_PW=$(sudo awk -F= '/^password=/ {print $2}' /stage/scripts/elasticsearch-credentials.log)

curl -sku "elastic:${ELASTIC_PW}" https://localhost:9200
curl -sku "elastic:${ELASTIC_PW}" https://localhost:9200/_cluster/health?pretty
curl -sku "elastic:${ELASTIC_PW}" https://localhost:9200/_cat/nodes?v

The -k flag tells curl to skip TLS verification, which is necessary as long as the image ships with the demo self signed certificate. From a remote host, replace localhost with the private IP of the virtual machine, copy the elastic password into that host, and add -H 'Content-Type: application/json' when sending JSON bodies.

Expected output of the root endpoint includes the version, cluster name, and the Elasticsearch tagline:

{
  "name" : "cloudimg-node-1",
  "cluster_name" : "cloudimg-elasticsearch",
  "version" : {
    "number" : "9.x.x",
    ...
  },
  "tagline" : "You Know, for Search"
}

From the official Elasticsearch Python client the equivalent connection object is:

from elasticsearch import Elasticsearch

client = Elasticsearch(
    "https://<vm-private-ip>:9200",
    basic_auth=("elastic", "<generated-password>"),
    verify_certs=False,
    ssl_show_warn=False,
)

print(client.info())

For production code, set verify_certs=True and provide your own root CA bundle after replacing the demo certificates per Section 13. Prefer API keys over basic auth for programmatic access; see Section 11.

Step 9: Index a Document and Run a Query

Create an index, index a document, run a simple search, then clean up:

curl -sku "elastic:${ELASTIC_PW}" -X PUT \
  https://localhost:9200/products \
  -H 'Content-Type: application/json' \
  -d '{"settings":{"number_of_shards":1,"number_of_replicas":0}}'

curl -sku "elastic:${ELASTIC_PW}" -X POST \
  https://localhost:9200/products/_doc?refresh=true \
  -H 'Content-Type: application/json' \
  -d '{"name":"cloudimg Elasticsearch image","price":0,"tags":["azure","ubuntu"]}'

curl -sku "elastic:${ELASTIC_PW}" -X GET \
  https://localhost:9200/products/_search \
  -H 'Content-Type: application/json' \
  -d '{"query":{"match":{"name":"elasticsearch"}}}' | jq

curl -sku "elastic:${ELASTIC_PW}" -X DELETE https://localhost:9200/products

In the Kibana web UI open Dev Tools from the left navigation, paste the same requests (without curl wrapping and without the -sku), and run them with the play icon. The Dev Tools console uses the logged in session so you do not need to supply credentials per request. Kibana also ships with sample data sets accessible from the home page which are a fast way to populate an index and explore Discover, Dashboards, and Lens.

Step 10: Rotate the elastic Password

Generate a new password using the bundled tool:

NEW_PW=$(sudo /usr/share/elasticsearch/bin/elasticsearch-reset-password \
  -u elastic -b -s)
echo "New elastic password: ${NEW_PW}"

The -b flag runs in batch mode (no prompt) and -s prints the plaintext password on stdout. The new password is written into the internal security index immediately; no restart is needed. Update /stage/scripts/elasticsearch-credentials.log so operators who SSH in later see the current value. If you also want to rotate the kibana_system service account password run the same command with -u kibana_system, then update /etc/kibana/kibana.yml and restart Kibana.

For interactive users, prefer creating separate accounts per analyst via Kibana Stack Management > Security > Users, and use the elastic superuser only for break glass access.

Step 11: Add Application Users, Roles, and API Keys

Elasticsearch 9 stores users, roles, and role mappings in the internal security index, not in YAML files. The supported workflow is through Kibana or the REST API.

Create a read only analyst via REST:

curl -sku "elastic:${ELASTIC_PW}" -X POST \
  https://localhost:9200/_security/user/analyst \
  -H 'Content-Type: application/json' \
  -d '{
    "password": "AnalystPass!234",
    "roles": ["viewer"],
    "full_name": "Read only analyst",
    "email": "analyst@example.com"
  }'

Create an API key scoped to a single index for an application:

curl -sku "elastic:${ELASTIC_PW}" -X POST \
  https://localhost:9200/_security/api_key \
  -H 'Content-Type: application/json' \
  -d '{
    "name": "app-logs-writer",
    "role_descriptors": {
      "logs_writer": {
        "cluster": ["monitor"],
        "indices": [{
          "names": ["logs-*"],
          "privileges": ["write", "create_index"]
        }]
      }
    }
  }'

The response contains an encoded field which is the base64 credential you pass in the Authorization: ApiKey <encoded> header. Rotate API keys by deleting and recreating; there is no in place update.

For finer grained roles, define a custom role with the _security/role/<name> endpoint that names specific indices and privilege groups, then either add it to a user or attach it to a role descriptor on an API key.

Step 12: Move Data to an Attached Premium Disk

The default /var/lib/elasticsearch lives on the OS disk. For production deployments, move it to a dedicated Premium SSD data disk so index growth does not compete with operating system writes and so you can size storage independently.

Attach a new Premium SSD disk through the portal or the Azure CLI, then on the virtual machine:

sudo systemctl stop kibana.service
sudo systemctl stop elasticsearch.service

DEVICE=/dev/disk/azure/scsi1/lun0
sudo mkfs.ext4 "${DEVICE}"
sudo mkdir -p /mnt/elasticsearch-data
echo "${DEVICE} /mnt/elasticsearch-data ext4 defaults,nofail 0 2" \
  | sudo tee -a /etc/fstab
sudo mount -a

sudo rsync -aHAX /var/lib/elasticsearch/ /mnt/elasticsearch-data/
sudo chown -R elasticsearch:elasticsearch /mnt/elasticsearch-data

sudo sed -i 's|^path.data: .*|path.data: /mnt/elasticsearch-data|' \
  /etc/elasticsearch/elasticsearch.yml

sudo systemctl start elasticsearch.service
sudo systemctl start kibana.service

Check the Elasticsearch log for a clean start and confirm index metadata is intact by listing indices via the REST API with GET /_cat/indices?v.

Step 13: Replace the Demo Certificates Before Production

The certificates shipped with this image are the upstream Elastic auto generated self signed certificates written to /etc/elasticsearch/certs/ on first start. They are suitable for evaluation and for private networks where traffic cannot be intercepted, but they are not production grade because the node certificate was issued by an on node CA with no external trust.

The replacement workflow is:

  1. Issue a node certificate and a root CA certificate from your own certificate authority, either internal or a commercial one. The node certificate must include the virtual machine's private IP and DNS name in its Subject Alternative Name extension.
  2. Use elasticsearch-certutil to generate a p12 bundle if you want to keep Elastic's keystore integrated workflow, or drop PEM files directly into /etc/elasticsearch/certs/.
  3. Update /etc/elasticsearch/elasticsearch.yml with the new filenames under xpack.security.http.ssl.* and xpack.security.transport.ssl.*.
  4. Restart Elasticsearch, then Kibana.
  5. Update Kibana with the new CA bundle in elasticsearch.ssl.certificateAuthorities so Kibana can verify the Elasticsearch HTTP endpoint without verificationMode: none.

Refer to the upstream Elastic documentation on the TLS configuration for Elasticsearch for the full list of keys and for the exact Subject Alternative Name requirements.

Step 14: Troubleshooting

Elasticsearch refuses to start, max virtual memory areas vm.max_map_count [65530] is too low. The image ships /etc/sysctl.d/99-elasticsearch.conf which sets vm.max_map_count=262144. If a customer provisioning tool overrides it, re apply with sudo sysctl -w vm.max_map_count=262144 and make the override permanent by editing the file.

Elasticsearch refuses to start with a heap size error. The default heap is one gigabyte to fit evaluation sized VMs. For production, edit /etc/elasticsearch/jvm.options.d/cloudimg-heap.options and set -Xms and -Xmx to the same value, not more than fifty percent of host memory and not more than thirty one gigabytes (to preserve compressed object pointers). Restart Elasticsearch.

Kibana shows "Kibana server is not ready yet". This appears on every start until Elasticsearch is healthy and the security index is reachable. Wait sixty to ninety seconds, then check sudo journalctl -u kibana.service -n 200 for the first non zero exit condition. A common cause on a cold restart is that Kibana started before Elasticsearch finished bootstrap; the systemd drop in ordering we ship should prevent this, but if you ran the services out of order manually, stop Kibana, wait for Elasticsearch, start Kibana.

Kibana login fails with "Invalid credentials". Confirm the kibana_system password stored in /etc/kibana/kibana.yml matches the one in the cluster. If you rotated the kibana_system password with elasticsearch-reset-password -u kibana_system and forgot to update kibana.yml, Kibana will fail to authenticate on the back channel even though the elastic user works.

curl fails with "SSL certificate problem". Elasticsearch 9 serves HTTPS by default on port 9200. Use the -k flag to skip verification against the self signed cert, or set up the Elastic root CA in your client's trust store. The root CA is at /etc/elasticsearch/certs/http_ca.crt on the virtual machine.

Authentication errors from a remote client. Confirm the elastic password with sudo cat /stage/scripts/elasticsearch-credentials.log. Check that the client is sending Authorization: Basic <base64> with the elastic credentials or an API key in Authorization: ApiKey <encoded>. On repeated failures Elasticsearch 9 triggers rate limits on the auth endpoint; wait sixty seconds and retry.

Out of memory on a Standard_B2s. The default JVM heap plus Kibana plus the OS leaves no headroom for the page cache on a two vCPU four gigabyte virtual machine. Resize to Standard_D4s_v3 or larger, or stop Kibana on that VM.

Step 15: Security Recommendations

Run with an isolated network security group that exposes TCP 22 only to your management CIDR, TCP 5601 only to the virtual network CIDR or an explicit client allow list, and TCP 9200 only to application servers that need the REST API. Do not place a public IP on the virtual machine unless you have already replaced the demo certificates and moved the elastic account to a strong password.

Replace the demo certificates before any traffic other than evaluation traffic crosses the wire (Section 13). The auto generated certificates are on node self signed and have no external trust chain.

Rotate the elastic password immediately after your first Kibana login (Section 10). Create separate user accounts for each application and each analyst, and assign least privilege roles via Kibana Stack Management. Avoid using the elastic superuser as an application credential.

Prefer API keys over basic auth for any long lived programmatic access. API keys can be scoped to individual indices and privilege sets, and they can be rotated without touching end user passwords.

Keep patches current. Subscribe to the Elastic release notes and apply Elasticsearch minor patches within the distribution's patch windows. cloudimg publishes refreshed images at each minor release.

Enable the audit log by setting xpack.security.audit.enabled: true in elasticsearch.yml if your compliance framework requires it. The audit log writes JSON records under /var/log/elasticsearch/ and captures every privileged operation.

Step 16: Support and Licensing

cloudimg provides 24/7/365 expert technical support for this image, with a guaranteed 24 hour response on all requests and a one hour average on critical issues. Contact support@cloudimg.co.uk for assistance with deployment, upgrades, certificate replacement, or cluster migration.

Elasticsearch 9 is distributed by Elastic under the AGPL licence with the Server Side Public Licence and Elastic Licence v2 as alternatives; this image ships the AGPL licensed binaries. Kibana is distributed under the same trio. This image does not include any Elastic commercial subscription features such as Machine Learning, Graph, cross cluster replication, or the commercial Security features that require an active Elastic subscription; to use those, procure a subscription directly from Elastic. The image is a repackaged upstream distribution provided by cloudimg. Additional charges apply for build, maintenance, and 24/7 support. The image is not affiliated with or endorsed by Elastic NV.

Full product documentation, versioning notes, and upgrade guides are available at https://www.cloudimg.co.uk/guides/elasticsearch-9-on-ubuntu-22-04-azure.