Op
Application Infrastructure Azure

OpenObserve on Ubuntu 24.04 on Azure User Guide

| Product: OpenObserve Observability on Ubuntu 24.04 LTS on Azure

Overview

OpenObserve is an open source, high performance observability platform that brings your logs, metrics and traces together in one place with a rich, built in web interface. It ships as a single self contained Go binary that stores data on local disk, so a single node runs the full search, dashboard and alerting experience with no external object store required and roughly a tenth of the storage footprint of Elasticsearch. The cloudimg image installs the pinned OpenObserve 0.91.1 binary and runs it as the openobserve systemd service, bound to loopback 127.0.0.1:5080 so it is never exposed directly, with an nginx reverse proxy on port 80 in front of it. OpenObserve has native user accounts: a root user is created uniquely on the first boot of every VM, and all ingest and REST APIs use HTTP Basic Auth with those per-VM credentials. Anonymous telemetry is disabled by default, so this VM observes your data privately out of the box. The local store (ingested logs, metrics and traces plus the metadata database) lives on a dedicated Azure data disk mounted at /var/lib/openobserve. Backed by 24/7 cloudimg support.

What is included:

  • OpenObserve 0.91.1 installed as a single Go binary and running as the openobserve systemd service
  • Logs, metrics and traces with a built in web UI on :80, fronted by nginx with OpenObserve bound to loopback only
  • Native per-VM root account, with a unique password generated on first boot; all APIs use HTTP Basic Auth
  • OpenObserve is bound to 127.0.0.1:5080 - the app is never exposed to the network directly
  • Anonymous telemetry disabled by default for a private, self hosted setup
  • A dedicated Azure data disk at /var/lib/openobserve holding the local store and metadata database
  • The nginx proxy forwards the WebSocket upgrade OpenObserve uses for live tail
  • openobserve.service + nginx.service as systemd units, enabled and active
  • An unauthenticated /healthz endpoint for Azure Load Balancer health probes
  • 24/7 cloudimg support

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet + subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a comfortable starting point for evaluation and light workloads; scale up the VM and the data disk as your ingest volume grows. NSG inbound: allow 22/tcp from your management network, 80/tcp for the web UI and ingest, and 443/tcp if you add TLS. OpenObserve serves plain HTTP on port 80; for production use, terminate TLS in front of it with your own domain and restrict access to trusted IP ranges (see Maintenance).

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for OpenObserve by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Review the dedicated data disk on the Disks tab, then Review + create -> Create.

Step 2 - Deploy from the Azure CLI

az vm create \
  --resource-group <your-rg> \
  --name openobserve \
  --image <marketplace-image-urn> \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_ed25519.pub \
  --vnet-name <your-vnet> --subnet <your-subnet> \
  --public-ip-sku Standard

az vm open-port --resource-group <your-rg> --name openobserve --port 80 --priority 1010

Step 3 - Connect to your VM

ssh azureuser@<vm-public-ip>

Step 4 - Confirm the services are running

systemctl is-active openobserve.service nginx.service

Both report active. OpenObserve stores and serves logs, metrics and traces on the loopback connector 127.0.0.1:5080; nginx fronts it on port 80. The local store lives on the dedicated Azure data disk mounted at /var/lib/openobserve.

The openobserve and nginx services active, OpenObserve listening on loopback 127.0.0.1:5080, nginx on port 80, and the dedicated data disk mounted at /var/lib/openobserve

Step 5 - Retrieve your root login

OpenObserve has native user accounts. A root user is created on the first boot of your VM and its email and unique password are written to a root-only file:

sudo cat /root/openobserve-credentials.txt

This file contains OPENOBSERVE_USERNAME (the root email, admin@openobserve.local), OPENOBSERVE_PASSWORD and the OPENOBSERVE_URL to open in a browser. The password is only read at first start from a separate 0640 env file (/etc/openobserve/root-user.env), so no plaintext password ships in the image. Store the password somewhere safe.

The OpenObserve version, the static configuration binding it to loopback with telemetry disabled, the per-VM credentials file with the generated root password, and the separate 0640 root-user env file that holds the secret

Step 6 - Confirm the health endpoint

nginx proxies an unauthenticated health endpoint for load balancers and probes:

curl -s -o /dev/null -w '%{http_code}\n' http://localhost/healthz

It returns 200. This endpoint never requires authentication, so it is safe for an Azure Load Balancer health probe.

Step 7 - Confirm authentication and the streams API

Because the root user is set on first boot, an unauthenticated API request returns HTTP 401, so nobody reaches your data without credentials. The following reads the per-VM password from the credentials file and proves the round-trip - unauthenticated is rejected, a wrong password is rejected, the correct password authenticates, and an authenticated call to the streams API returns JSON:

PW=$(sudo grep '^OPENOBSERVE_PASSWORD=' /root/openobserve-credentials.txt | cut -d= -f2-)
E=admin@openobserve.local
echo "unauth  : $(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/api/default/streams)"
echo "wrongpw : $(curl -s -o /dev/null -w '%{http_code}' -u $E:wrong-pw http://127.0.0.1/api/default/streams)"
echo "authed  : $(curl -s -o /dev/null -w '%{http_code}' -u $E:$PW http://127.0.0.1/api/default/streams)"
echo "streams : $(curl -s -u $E:$PW http://127.0.0.1/api/default/streams | python3 -c 'import sys,json;print(len(json.load(sys.stdin).get("list",[])),"stream(s)")')"

It prints unauth : 401, wrongpw : 401, authed : 200, then the stream count. Every API, like the whole UI, is only reachable with the per-VM credentials because OpenObserve itself is bound to loopback and nginx is the only way in.

The HTTP Basic Auth round-trip returning 401 unauthenticated, 401 for a wrong password, 200 with the per-VM password, and the authenticated streams API returning the stream list as JSON

Step 8 - Sign in to the web UI

Browse to http://<vm-public-ip>/. OpenObserve shows its sign in page: enter the root email admin@openobserve.local and the password from Step 5, then select Login. OpenObserve opens on its home view, with Logs, Metrics, Traces, Dashboards, Alerts and Streams in the left menu.

The OpenObserve sign in page asking for the user email and password, branded with the OpenObserve logo

Step 9 - Search your logs

Open Logs from the left menu, pick a stream (for example app_logs) and a time range, then select Run query. OpenObserve shows an interactive histogram of matching events over time, a searchable field list on the left (level, service, status, duration and more), and the raw log records below - each expandable to its full JSON. Type a query in the search bar to filter by field or full text, and drag on the histogram to zoom into a window.

The OpenObserve logs explorer showing a stream selected, a histogram of events coloured by log level, the field list sidebar and the matching log records with timestamps

Step 10 - Ship your own data

OpenObserve ingests over a simple HTTP API and via OpenTelemetry (OTLP), Fluent Bit, Vector, Filebeat, Logstash and more. The Data sources page under Data gives you ready made snippets for each. The quickest test is a single log line over the JSON ingest API using the per-VM credentials:

PW=$(sudo grep '^OPENOBSERVE_PASSWORD=' /root/openobserve-credentials.txt | cut -d= -f2-)
E=admin@openobserve.local
curl -s -o /dev/null -w 'ingest -> %{http_code}\n' -u $E:$PW \
  -H 'Content-Type: application/json' \
  -d '[{"level":"info","service":"my-app","message":"hello from my application"}]' \
  http://127.0.0.1/api/default/my_app_logs/_json

It prints ingest -> 200, and a new my_app_logs stream appears in Logs immediately. Point your applications, OpenTelemetry collectors or log shippers at this host on port 80 and search everything from one place.

The OpenObserve Data sources page listing ingestion options - Curl, Filebeat, Fluent Bit, Fluentd, Vector, OTEL Collector, Logstash and Syslog-ng - with a ready made curl example for the default organisation

Step 11 - Manage your streams

Open Streams under Data. Each stream (a named collection of logs, metrics or traces) is listed with its type, event count and on-disk size, and you can filter by Logs, Metrics, Traces or Metadata. From here you configure retention, user defined schemas and field settings per stream.

The OpenObserve Streams management page listing the ingested log streams with their type, event count and storage size, and tabs to filter by Logs, Metrics, Traces and Metadata

Step 12 - Confirm the store lives on the dedicated disk

OpenObserve keeps its local store - the write ahead log, the compacted parquet data files and the metadata database - under /var/lib/openobserve on the dedicated Azure data disk, so your observability history survives OS changes and the disk can be resized independently:

findmnt /var/lib/openobserve

The mount is backed by a separate Azure data disk captured into the image and re-provisioned on every VM. The store grows under /var/lib/openobserve as you ingest data.

The OpenObserve data disk mounted at /var/lib/openobserve showing the local store directories on the dedicated Azure volume, telemetry disabled, and the ingested streams listed from the API

Maintenance

  • Password and users: the root password is set on first boot and read only at first start from /etc/openobserve/root-user.env. Create additional users and rotate credentials from IAM / Users in the web UI once you are signed in as root.
  • Restrict access: OpenObserve serves plain HTTP on port 80. For production, restrict the UI to trusted IP ranges in your Network Security Group, and front it with TLS (for example certbot with your own domain) terminating on :443.
  • Loopback binding: OpenObserve is bound to 127.0.0.1:5080 via ZO_HTTP_ADDR / ZO_HTTP_PORT in /etc/openobserve/openobserve.env, so nginx is the only path in. Keep it that way - do not change the bind address to a public interface.
  • Scale out: a single node stores everything on local disk. For larger deployments, point OpenObserve at object storage (S3 or Azure Blob) and an external metadata database, and run it in cluster mode - see the OpenObserve documentation.
  • Ingest: send data over the HTTP JSON API, OpenTelemetry (OTLP), Fluent Bit, Vector, Filebeat, Logstash and more; the Data sources page in the UI has copy-paste snippets for each.
  • Storage: all OpenObserve state lives under /var/lib/openobserve on the data disk; back up that volume to protect your observability history, and resize the disk as ingest grows.
  • Telemetry: anonymous telemetry is disabled by default via ZO_TELEMETRY=false in /etc/openobserve/openobserve.env for a fully self hosted, private setup.
  • Licensing (AGPL-3.0): OpenObserve is licensed under the GNU Affero General Public License v3.0. The image ships the unmodified upstream binary. Under the AGPL, if you modify OpenObserve and offer the modified version to users over a network, you must also offer them the corresponding source of your modifications; running the unmodified binary carries no such obligation.
  • Security patches: unattended-upgrades remains enabled so the OS continues to receive security updates automatically.

Support

cloudimg provides 24/7 expert support for this image. Contact support@cloudimg.co.uk.