Jaeger 2 Distributed Tracing on Ubuntu 24.04 on Azure User Guide
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.0with a/opt/jaegersymlink - SHA256-verified binary install (checksum compared against the upstream
sha256sum.txt) - Dedicated
jaegersystem 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
cloudimguser password rotated at first boot viaopenssl 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/tcpfrom your management CIDR — SSH80/tcpfrom any client CIDR that needs the Jaeger Web UI — nginx fronts the UI with HTTP basic-auth, so the UI is never exposed without credentials4317/tcpand4318/tcpfrom 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.

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.

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.

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.

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.yamland replace thejaeger_storageBadger backend with the appropriate config block, then restartjaeger.service. Badger is single-node only. - Restrict NSG: keep
80/tcpopen only to your CDN / load balancer or VPN CIDR; lock22/tcpto your management CIDR - TLS: terminate TLS at nginx by adding a
listen 443 ssl;block with your real cert andreturn 301 https://$host$request_uri;on:80 - Trace sampling: edit
/etc/jaeger/config.yamlto add a probabilistic sampler underprocessors: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.