Applications Azure

Jaeger 2 Distributed Tracing on Ubuntu 24.04 on Azure User Guide

| Product: Jaeger 2.17.0 Distributed Tracing on Ubuntu 24.04 LTS on Azure

Overview

Jaeger is the CNCF graduated distributed tracing platform — instrument your services, ship spans over OTLP or Jaeger native protocols, search and visualise the resulting trace graph in the browser. Jaeger 2 is the major rebuild on top of the OpenTelemetry Collector codebase: a single Go binary named jaeger, an OTel-Collector-style YAML config, embedded Badger storage out of the box, and one process running every receiver, processor, exporter and the Web UI together.

The cloudimg image installs Jaeger 2.17.0 from the official GitHub release, runs as the dedicated jaeger system user, persists trace data to /var/lib/jaeger/badger, and wraps the Web UI plus admin health endpoint in nginx HTTP basic-auth on port 80 so the UI is never exposed without credentials. Trace ingest ports (OTLP gRPC 4317, OTLP HTTP 4318, Jaeger gRPC 14250, Jaeger Thrift HTTP 14268, Jaeger Thrift UDP 6831) stay direct because instrumented applications speak OTLP/Jaeger protocols, not HTTP basic-auth.

What is included:

  • Jaeger 2.17.0 (Apache 2.0) from the official GitHub release tarball at /opt/jaeger-2.17.0 with a /opt/jaeger symlink
  • SHA256-verified binary install (checksum compared against the upstream sha256sum.txt)
  • Dedicated jaeger system user, data directory at /var/lib/jaeger/badger (jaeger:jaeger 0750)
  • OTel-Collector-style YAML config at /etc/jaeger/config.yaml
  • Embedded Badger key-value storage with 48h TTL for live traces, 720h TTL for archive
  • All receivers enabled: OTLP gRPC :4317, OTLP HTTP :4318, Jaeger gRPC :14250, Jaeger Thrift HTTP :14268, Jaeger Thrift compact UDP :6831
  • Web UI on :16686, admin healthcheckv2 on :14269
  • nginx 1.24 reverse proxy with HTTP basic-auth fronting the UI and admin endpoint on port 80
  • Per-VM cloudimg user password rotated at first boot via openssl rand -hex 12
  • Per-VM credentials log at /etc/cloudimg-credentials.txt (root:root 0600)
  • jaeger.service, jaeger-2-firstboot.service, nginx.service — three systemd units
  • 24/7 cloudimg support

For production scale-out, swap the jaeger_storage extension's Badger backend for OpenSearch, Elasticsearch, or Cassandra — edit /etc/jaeger/config.yaml and restart jaeger.service.

Prerequisites

Active Azure subscription, SSH key, VNet + subnet. Standard_B2s (4 GB RAM) is comfortable for a single-node Jaeger handling tens of thousands of spans per minute. NSG inbound rules:

  • 22/tcp from your management CIDR — SSH
  • 80/tcp from any client CIDR that needs the Jaeger Web UI — nginx fronts the UI with HTTP basic-auth, so the UI is never exposed without credentials
  • 4317/tcp and 4318/tcp from your application subnet — OTLP gRPC and HTTP trace ingest (instrumented apps)
  • 14250/tcp, 14268/tcp, 6831/udp — Jaeger native trace ingest (only if you have apps using the older Jaeger client libraries)

Never expose 16686/tcp or 14269/tcp directly — those are the upstream UI and admin endpoints which have no auth. The cloudimg image runs them on 0.0.0.0 for nginx to proxy locally; bind them to loopback in production if you need stronger isolation.

Step 1-3: Deploy + SSH (standard pattern)

ssh azureuser@<vm-ip>

Step 4: Service Status + Versions

sudo systemctl is-active jaeger nginx
sudo test -f /var/lib/cloudimg/jaeger-2-firstboot.done && echo "firstboot: complete" || echo "firstboot: missing"
/opt/jaeger/jaeger --version 2>&1 | head -1

jaeger and nginx both report active. The first-boot oneshot writes a sentinel file when it finishes generating the per-VM nginx htpasswd password and credentials log, then exits cleanly. The Jaeger binary reports its git-version as v2.17.0.

jaeger and nginx active, firstboot sentinel complete, Jaeger 2.17.0 reported

Step 5: Read Per-VM Credentials

sudo cat /etc/cloudimg-credentials.txt

Pick up JAEGER_PASSWORD (the cloudimg user's password — used for the nginx HTTP basic-auth prompt when you browse to the Web UI) and the trace-ingest endpoints (OTLP_GRPC, OTLP_HTTP, JAEGER_GRPC, JAEGER_THRIFT_HTTP, JAEGER_THRIFT_UDP). The file is root:root 0600. JAEGER_URL is the canonical browser entry point and JAEGER_ADMIN_HEALTH_URL points at the healthcheckv2 endpoint behind basic-auth.

Step 6: Admin Health Check

curl -s http://127.0.0.1:14269/status | head -c 400
PASS=$(sudo grep '^JAEGER_PASSWORD=' /etc/cloudimg-credentials.txt | cut -d= -f2-)
curl -s -u cloudimg:$PASS http://127.0.0.1/admin/status | head -c 400

The first request hits Jaeger's healthcheckv2 extension directly on the loopback admin port and returns a JSON status document confirming all components are healthy. The second goes through nginx with HTTP basic-auth and returns the same document — proving the full reverse-proxy chain is wired correctly.

Step 7: Send a Trace Over OTLP HTTP

curl -sf -X POST -H 'Content-Type: application/json' \
    --data '{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"hello-cloudimg"}}]},"scopeSpans":[{"scope":{"name":"manual"},"spans":[{"traceId":"5b8aa5a2d2c872e8321cf37308d69df2","spanId":"051581bf3cb55c13","name":"manual-span","startTimeUnixNano":"1746000000000000000","endTimeUnixNano":"1746000001000000000","kind":1}]}]}]}' \
    http://127.0.0.1:4318/v1/traces && echo "trace ingested"

Sends a minimal valid OTLP JSON trace with service.name=hello-cloudimg to the OTLP HTTP receiver. The receiver routes it through the batch processor and writes it to the Badger backend via the jaeger_storage_exporter. The trace is now searchable in the Web UI.

Step 8: Browse the Jaeger Web UI

Browse to http://<vm-ip>/ and authenticate as cloudimg / <JAEGER_PASSWORD> at the nginx HTTP basic-auth prompt. The Search page is the default landing — pick a service from the dropdown, click "Find Traces", and the trace you POSTed in Step 7 appears.

Jaeger Web UI Search page after basic-auth login — service dropdown, lookback / min duration / max duration filters, Find Traces button

Step 9: System Architecture

The Search → System Architecture menu renders a force-directed graph of every service that has emitted spans, with edges showing parent-child call relationships derived from the trace context. With a single hello-cloudimg trace ingested it shows one node; in production with thousands of spans/minute you see your full service mesh.

Jaeger Web UI System Architecture page — force-directed graph of services and call relationships

Step 10: Trace Detail

Clicking through a trace in the Search results opens the trace detail view — a Gantt-style flame graph of every span in the trace, with timing data, tags, logs, and process info. This is where you debug latency bottlenecks: hover over the longest span to see its duration, expand its tags to see HTTP status codes / database queries / custom attributes.

Jaeger Web UI trace detail — Gantt-style timeline of every span with timing, tags, and process info

Step 11: Components

Component Path
jaeger binary /opt/jaeger/jaeger (symlink to /opt/jaeger-2.17.0/jaeger)
Config /etc/jaeger/config.yaml
Badger storage /var/lib/jaeger/badger/ (live), /var/lib/jaeger/badger-archive/ (archive)
systemd unit /etc/systemd/system/jaeger.service
Firstboot script /usr/local/sbin/jaeger-2-firstboot.sh
Firstboot service /etc/systemd/system/jaeger-2-firstboot.service
nginx site config /etc/nginx/sites-enabled/jaeger-cloudimg
nginx htpasswd /etc/nginx/.htpasswd-jaeger (root:www-data 0640)
Credentials /etc/cloudimg-credentials.txt (root:root 0600)
Logs journalctl -u jaeger, /var/log/cloudimg-firstboot.log

Step 12: Production Hardening

  • Switch Badger to OpenSearch / Elasticsearch / Cassandra: edit /etc/jaeger/config.yaml and replace the jaeger_storage Badger backend with the appropriate config block, then restart jaeger.service. Badger is single-node only.
  • Restrict NSG: keep 80/tcp open only to your CDN / load balancer or VPN CIDR; lock 22/tcp to your management CIDR
  • TLS: terminate TLS at nginx by adding a listen 443 ssl; block with your real cert and return 301 https://$host$request_uri; on :80
  • Trace sampling: edit /etc/jaeger/config.yaml to add a probabilistic sampler under processors: if your application throughput is high (e.g. 1% sampling for production)
  • Monitor Jaeger metrics: the config exposes Prometheus metrics on :8888 — scrape from your Prometheus install for visibility into receiver throughput, exporter latency, dropped spans
  • Patch monthly: apt-get update && apt-get upgrade && reboot; subscribe to https://github.com/jaegertracing/jaeger/releases for Jaeger releases

Licensing

Jaeger is Apache 2.0 — free to use commercially. cloudimg provides commercial support separately. support@cloudimg.co.uk.