ELK 9
Videos
Overview: ELK Stack 9 on Ubuntu 24.04 on Azure: overview (watch on YouTube)
Launch and log in: ELK Stack 9 on Ubuntu 24.04 on Azure: launch and log in (watch on YouTube)
Deep Dive: ELK Stack 9 on Ubuntu 24.04 on Azure: deep dive (watch on YouTube)
Overview
The ELK Stack is three Elastic products that work together for log analytics and search. Elasticsearch stores and searches the data. Logstash receives and processes events, here from Beats agents such as Filebeat, and writes them to Elasticsearch. Kibana is the web interface for searching, exploring and building dashboards.
This SecureAnyCloud image runs all three on one virtual machine as a single-node cluster on Ubuntu 24.04 LTS. It installs them from Elastic's official package repository (artifacts.elastic.co/packages/9.x/apt). Image version 1.0.20260502 carries Elasticsearch, Logstash and Kibana 9.3.4.
Every command and output in this guide was run on a virtual machine deployed from the public Marketplace image (image version 1.0.20260502), as the azureuser account a customer gets.
What you get on top of the upstream packages:
- A password for each VM. Elasticsearch security is on. At first boot,
elk-firstboot.servicesets theelasticsuperuser and the built-inkibana_systemuser to strong passwords generated on that VM. It writes theelasticpassword to a root-only file, and the password used while the image was built is refused afterwards. - Elasticsearch on localhost only. The REST API (9200) and the transport port (9300) listen on
127.0.0.1, and every request needs a login. - Kibana asks for a login. Kibana listens on port 5601 and redirects every page to its sign-in screen. Keep 5601 closed in your network security group and reach Kibana through an SSH tunnel (Step 9).
- A ready Beats pipeline. Logstash listens for Beats agents on port 5044 and writes each agent's events to a daily index in the local Elasticsearch (
filebeat-2026.11.02, for example). - Memory sizing at first boot. The first-boot service sets the Elasticsearch and Logstash Java heap sizes from the memory of the VM you chose.
- Pinned to 9.x. The three packages are held with
apt-mark hold, so routine upgrades never move them. You upgrade them within 9.x when you choose (Step 13).
What's included
- Elasticsearch, Logstash and Kibana 9.3.4 (image version 1.0.20260502), from Elastic's 9.x APT repository, which stays configured on the VM
- The default Beats input pipeline,
/etc/logstash/conf.d/01-beats-input.conf - The SecureAnyCloud first-boot service elk-firstboot.service (per-VM passwords and heap sizing)
- Ubuntu 24.04 LTS with unattended security upgrades turned on
- Azure Linux Agent (
waagent) and cloud-init for Azure provisioning - 24/7 SecureAnyCloud support
Connecting to your VM
Use these connection details:
- SSH user: the administrator you choose at deployment (for example azureuser)
- Kibana: http://localhost:5601 through an SSH tunnel (Step 9), user elastic
- Elasticsearch REST API: http://127.0.0.1:9200 on the VM (localhost only)
- Logstash Beats input: TCP 5044
- Credentials file: /stage/scripts/elk-credentials.log (root only)
- Open ports needed: 22 (SSH). Open 5044 only to the servers that ship logs (Step 10).
Prerequisites
- An Azure subscription and an SSH public key
- For the command line route, the Azure CLI on your workstation, signed in with
az login - A network security group that allows TCP 22 from your own address only
Choose a virtual machine size that suits your log volume and retention. Elasticsearch, Logstash and Kibana all run on the same VM, and the first-boot service sizes the Java heaps from the VM's memory (Step 12).
Deploy from the Azure portal
On the Marketplace listing, choose Get it now. The Azure portal then walks you through your subscription, resource group, region and size, your SSH key and the inbound ports.
Allow SSH (22) only for inbound ports, then select Review and create.
Deploy from the Azure CLI
The image carries a Marketplace purchase plan, so accept its terms once per subscription and then create the virtual machine. Replace <your-vm-size> with the size you chose:
az vm image terms accept --urn sac1647283583153:elk-9-ubuntu-24-04:default:latest
az vm create \
--resource-group myResourceGroup \
--name my-elk-vm \
--image sac1647283583153:elk-9-ubuntu-24-04:default:latest \
--size <your-vm-size> \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--nsg my-ssh-only-nsg \
--public-ip-sku StandardConnect via SSH
On your own workstation, find the public IP and connect:
az vm show --resource-group myResourceGroup --name my-elk-vm --show-details --query publicIps -o tsv ssh azureuser@<vm-ip>First boot takes a few minutes after the VM is running. The first-boot service sizes the heaps, restarts Elasticsearch, sets the per-VM passwords, then restarts Kibana and Logstash. If you connect straight away, wait for it to finish:
sudo cloud-init status --waitIt prints status: done once first boot has finished.
Check the services
systemctl is-active elasticsearch.service kibana.service logstash.service elk-firstboot.service
sudo ss -Htln '( sport = :9200 or sport = :5601 or sport = :5044 )' | awk '{print $4}'All four services report active. Elasticsearch listens on 127.0.0.1:9200 only (Java prints it as [::ffff:127.0.0.1]:9200). Kibana listens on 0.0.0.0:5601 and the Logstash Beats input on *:5044.
Your network security group decides who can reach 5601 and 5044. With the SSH-only group from the prerequisites, nobody outside the VM can.
Read the per-VM credentials
The elastic password is unique to this VM and is written at first boot to a root-only file (mode 0600):
sudo stat -c '%a %U:%G %n' /stage/scripts/elk-credentials.log
sudo grep -E '^(ELASTIC_USER|ELASTICSEARCH_URL|LOGSTASH_BEATS_PORT)=' /stage/scripts/elk-credentials.logIt prints 600 root:root /stage/scripts/elk-credentials.log, then ELASTIC_USER=elastic, ELASTICSEARCH_URL=http://127.0.0.1:9200 and LOGSTASH_BEATS_PORT=5044. The file's KIBANA_URL line holds the VM's private address; from your workstation, use the SSH tunnel in Step 9 instead.
When you need the password, read it into a variable instead of printing it on screen. The commands in this guide use $ES_PW, so set it in each new shell:
export ES_PW="$(sudo sed -n 's/^ELASTIC_PASSWORD=//p' /stage/scripts/elk-credentials.log)"Store the password safely, because elastic is the superuser.
The authentication gate
Elasticsearch refuses a request with no login and one with a wrong login. It accepts the VM's own:
curl -s -o /dev/null -w 'no login -> HTTP %{http_code}\n' http://127.0.0.1:9200/
curl -s -o /dev/null -w 'wrong login -> HTTP %{http_code}\n' -u 'elastic:not-the-real-one' http://127.0.0.1:9200/
curl -s -o /dev/null -w 'VM login -> HTTP %{http_code}\n' -u "elastic:$ES_PW" http://127.0.0.1:9200/The first two return HTTP 401 and the last HTTP 200. Kibana does the same for its pages: curl -s -o /dev/null -w '%{http_code} %{redirect_url}\n' http://127.0.0.1:5601/ prints 302 http://127.0.0.1:5601/login?next=%2F.
Cluster health and version
curl -s -u "elastic:$ES_PW" 'http://127.0.0.1:9200/_cluster/health?filter_path=cluster_name,status,number_of_nodes&pretty'
curl -s -u "elastic:$ES_PW" 'http://127.0.0.1:9200/?filter_path=name,cluster_name,version.number&pretty'The cluster sac-elk reports "status" : "green" with one node, elk-node-1, and the version (9.3.4 on image version 1.0.20260502).
Index and search a document
A single-node cluster cannot place replica shards. Create indices with no replicas so that they stay green:
curl -s -u "elastic:$ES_PW" -X PUT http://127.0.0.1:9200/app-logs \
-H 'Content-Type: application/json' -d '{"settings":{"number_of_replicas":0}}'
curl -s -u "elastic:$ES_PW" -X POST 'http://127.0.0.1:9200/app-logs/_doc?refresh=true' \
-H 'Content-Type: application/json' \
-d '{"@timestamp":"2026-11-02T09:00:00Z","service":"checkout","level":"error","message":"payment gateway timeout after 30s"}'
curl -s -u "elastic:$ES_PW" 'http://127.0.0.1:9200/app-logs/_search?q=message:timeout&filter_path=hits.total.value,hits.hits._source.message&pretty'
curl -s -u "elastic:$ES_PW" 'http://127.0.0.1:9200/_cat/indices/app-logs?v&h=health,index,docs.count'The search finds the document ("value" : 1, payment gateway timeout after 30s) and the index is green with one document. An index created without that setting, such as the daily filebeat-* indices Logstash creates, shows yellow on one node. Its data is complete, and only the replica has nowhere to go.
Open Kibana through an SSH tunnel
Keep port 5601 closed in the network security group and carry Kibana over SSH. On your workstation, open the tunnel:
ssh -N -L 5601:127.0.0.1:5601 azureuser@<vm-ip>Leave the tunnel running and open http://localhost:5601 in your browser. The traffic travels inside the encrypted SSH connection.
Sign in with the user elastic and the password from Step 5. Kibana opens, with Discover, Dashboards and its other applications in the main menu.
Ship logs with Filebeat through Logstash
Logstash's default pipeline takes events from Beats agents on port 5044 and writes them to Elasticsearch as <beat>-<date> indices.
This example runs the whole path on the VM itself, with Filebeat reading one log file. Elastic's 9.x repository is already configured, so install Filebeat and give it a minimal configuration:
sudo apt-get -q update
sudo apt-get install -y filebeat
sudo tee /etc/filebeat/filebeat.yml >/dev/null <<'EOF' filebeat.inputs: - type: filestream id: demo-app paths: - /var/log/demo-app.log output.logstash: hosts: ["127.0.0.1:5044"] EOF
sudo filebeat test config -c /etc/filebeat/filebeat.yml
sudo filebeat test output -c /etc/filebeat/filebeat.yml
sudo systemctl enable --now filebeatfilebeat test config prints Config OK, and filebeat test output ends with talk to server... OK. Write a few lines to the log and count what arrives:
for i in 1 2 3; do echo "$(date -Is) checkout ERROR payment gateway timeout order=$((1040+i))" | sudo tee -a /var/log/demo-app.log >/dev/null; done sleep 15 curl -s -u "elastic:$ES_PW" 'http://127.0.0.1:9200/_cat/indices/filebeat-*?v&h=health,index,docs.count'The three lines arrive in today's filebeat-* index (for example filebeat-2026.11.02 with docs.count 3).
To ship logs from your other servers, install Filebeat on each one from the same Elastic 9.x repository. Set output.logstash.hosts to this VM's private address and port 5044. Then add a network security group rule that allows TCP 5044 from those servers' private addresses only.
The Beats input on this image accepts plain TCP with no client authentication, so never open 5044 to the internet (see Security notes).
Explore the logs in Kibana
Create a Kibana data view for the Filebeat indices, either in Kibana (Stack Management, Data views) or with Kibana's API:
curl -s -u "elastic:$ES_PW" -X POST http://127.0.0.1:5601/api/data_views/data_view \
-H 'kbn-xsrf: true' -H 'Content-Type: application/json' \
-d '{"data_view":{"title":"filebeat-*","name":"Filebeat logs","timeFieldName":"@timestamp"}}'Open Discover in Kibana and choose the Filebeat logs data view. Search with KQL, for example message : timeout.
Logstash pipelines and memory
The default pipeline is /etc/logstash/conf.d/01-beats-input.conf. Logstash loads every .conf file in /etc/logstash/conf.d/, so add your own filters and outputs there. Restart Logstash after a change:
sudo systemctl restart logstash.serviceAfter a restart, Logstash takes a minute or two before it listens on 5044 again. sudo journalctl -u logstash -f shows Pipelines running when it is ready.
Its Elasticsearch output signs in as elastic, with the password taken from the root-only environment file /etc/default/logstash (mode 0600). A pipeline file never holds the password itself.
The first-boot service sets the Java heap sizes from the VM's memory and records what it chose:
sudo grep 'Adjusting heaps' /var/log/elk-firstboot.logElasticsearch takes about 40 percent of memory, and Logstash about 15 percent, up to 1 GB. To change them later, edit /etc/elasticsearch/jvm.options.d/sac-heap.options (keep -Xms and -Xmx equal) or the -Xms/-Xmx lines in /etc/logstash/jvm.options, then restart that service. If you resize the VM, adjust the heaps yourself, because first boot runs only once.
Keep it updated
The image was patched when it was built. Ubuntu security updates install automatically (unattended-upgrades is active), but other updates do not. Update a new VM before you put it into service, and regularly afterwards:
sudo apt-get -q update
sudo apt-get -y upgradeElasticsearch, Logstash and Kibana are held, so this leaves them alone (apt-mark showhold lists all three). Elastic's 9.x repository carries newer 9.x releases than the image.
To upgrade the stack within 9.x, release the hold and upgrade all three together while keeping SecureAnyCloud's configuration files. Then hold them again and restart:
sudo apt-mark unhold elasticsearch logstash kibana
sudo apt-get install -y -o Dpkg::Options::=--force-confold elasticsearch logstash kibana
sudo apt-mark hold elasticsearch logstash kibana
sudo systemctl daemon-reload
sudo systemctl restart elasticsearch.service kibana.service logstash.serviceWe ran exactly this upgrade from 9.3.4 to 9.5.5. The elastic password, the indices and the Beats pipeline carried over, and events flowed again once Logstash had restarted.
--force-confold keeps the existing kibana.yml and logstash.yml. The package's new defaults are saved beside them as .dpkg-dist files.
Read Elastic's release notes before you upgrade and take a snapshot of the OS disk first. Reboot if a new kernel was installed.
Security notes
- Network security group. SSH (22) is the only port you need to open. Reach Kibana through the SSH tunnel (Step 9), and allow 5044 only from your log shippers' private addresses.
- No TLS on this image. Elasticsearch and Kibana speak plain HTTP. This is safe while Elasticsearch stays on localhost and Kibana is reached through the SSH tunnel. Before you expose Kibana or Elasticsearch to a network, or accept Beats traffic from outside a private network, configure TLS by following Elastic's guide to securing the Elastic Stack.
- Users. Create users with only the roles they need instead of sharing
elastic(Kibana, Stack Management, Users and Roles). If you change theelasticpassword, also updateELASTIC_PASSWORDin/etc/default/logstashand restart Logstash, because its output signs in aselastic. The credentials file is not rewritten, so update or remove it yourself. - Credentials file. Once you have stored the password somewhere safe, such as Azure Key Vault, you can remove the file:
sudo shred -u /stage/scripts/elk-credentials.log.
When you have finished with the examples, remove them:
curl -s -u "elastic:$ES_PW" -X DELETE http://127.0.0.1:9200/app-logs
sudo systemctl disable --now filebeatServer components
Use this list to find the main configuration, data and log locations:
- Elasticsearch configuration: /etc/elasticsearch/elasticsearch.yml
- Elasticsearch heap: /etc/elasticsearch/jvm.options.d/sac-heap.options
- Elasticsearch data and logs: /var/lib/elasticsearch/, /var/log/elasticsearch/
- Logstash pipelines: /etc/logstash/conf.d/
- Logstash environment (output login): /etc/default/logstash (mode 0600)
- Logstash heap: /etc/logstash/jvm.options
- Kibana configuration: /etc/kibana/kibana.yml
- Credentials file: /stage/scripts/elk-credentials.log (mode 0600)
- First-boot service and log: elk-firstboot.service, /var/log/elk-firstboot.log
Support and licensing
The Elasticsearch, Logstash and Kibana packages on this image are Elastic's default distributions, which Elastic licenses under the Elastic License 2.0. This image uses their free features only, and no Elastic subscription is included.
SecureAnyCloud provides round-the-clock technical support for this image by email, with a guaranteed 24-hour response time: sales@secureanycloud.com.
Elasticsearch, Logstash and Kibana are trademarks of Elasticsearch B.V. Ubuntu is a registered trademark of Canonical Ltd. SecureAnyCloud is not affiliated with or endorsed by either of them.
Deploy on Azure
Launch ELK Stack 9 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com