Jaeger is an open-source, end-to-end distributed tracing system originally developed by Uber Technologies and now a CNCF graduated project. It lets you follow a single user request as it propagates across dozens of microservices, measure the latency contribution of each span, and pinpoint the exact service or database call responsible for a slowdown. On RHEL 8 you can run the all-in-one binary for development and proof-of-concept work, then graduate to a production Docker Compose stack backed by Elasticsearch for persistence. This tutorial covers both paths and shows how to instrument a Go or Python application to send spans directly to Jaeger.

Prerequisites

  • RHEL 8 server (or AlmaLinux 8 / Rocky Linux 8) with sudo access
  • At least 1 GB RAM for the all-in-one binary; 4 GB recommended for the Elasticsearch-backed stack
  • Docker and Docker Compose installed for the production path
  • Go 1.21+ or Python 3.9+ for the instrumentation examples
  • Ports 14250 (Jaeger gRPC), 14268 (HTTP collector), and 16686 (UI) open in firewalld

Step 1 — Run Jaeger All-in-One for Development

The jaeger-all-in-one binary bundles the collector, query service, and UI into a single process with an in-memory storage backend. It is ideal for local development and demos but loses all trace data on restart.

JAEGER_VERSION="1.57.0"

curl -LO "https://github.com/jaegertracing/jaeger/releases/download/v${JAEGER_VERSION}/jaeger-${JAEGER_VERSION}-linux-amd64.tar.gz"
tar xzf jaeger-${JAEGER_VERSION}-linux-amd64.tar.gz
cd jaeger-${JAEGER_VERSION}-linux-amd64

./jaeger-all-in-one 
  --collector.otlp.grpc.host-port=":14250" 
  --collector.otlp.http.host-port=":14268" 
  --query.http-server.host-port=":16686"

Open http://localhost:16686 to confirm the Jaeger UI is running. You should see the search interface with no services listed yet.

Step 2 — Create a systemd Unit for Jaeger All-in-One

To run Jaeger all-in-one as a managed service rather than in a foreground terminal, install the binary system-wide and create a systemd unit.

sudo mv jaeger-all-in-one /usr/local/bin/jaeger-all-in-one
sudo chmod +x /usr/local/bin/jaeger-all-in-one

sudo tee /etc/systemd/system/jaeger.service > /dev/null <<'EOF'
[Unit]
Description=Jaeger All-in-One Distributed Tracing
After=network.target

[Service]
Type=simple
ExecStart=/usr/local/bin/jaeger-all-in-one 
  --collector.otlp.grpc.host-port=":14250" 
  --collector.otlp.http.host-port=":14268" 
  --query.http-server.host-port=":16686"
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
EOF

sudo systemctl daemon-reload
sudo systemctl enable --now jaeger
sudo systemctl status jaeger

Step 3 — Production Setup: Docker Compose with Elasticsearch

For persistent storage, deploy the Jaeger Collector and Query services in Docker Compose backed by a single-node Elasticsearch cluster. Save the following as /opt/jaeger/docker-compose.yml.

mkdir -p /opt/jaeger && cd /opt/jaeger

cat > docker-compose.yml <<'EOF'
version: "3.8"
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
    environment:
      - discovery.type=single-node
      - xpack.security.enabled=false
      - ES_JAVA_OPTS=-Xms512m -Xmx512m
    volumes:
      - es-data:/usr/share/elasticsearch/data
    ports:
      - "9200:9200"

  jaeger-collector:
    image: jaegertracing/jaeger-collector:1.57
    environment:
      - SPAN_STORAGE_TYPE=elasticsearch
      - ES_SERVER_URLS=http://elasticsearch:9200
    ports:
      - "14250:14250"
      - "14268:14268"
    depends_on:
      - elasticsearch

  jaeger-query:
    image: jaegertracing/jaeger-query:1.57
    environment:
      - SPAN_STORAGE_TYPE=elasticsearch
      - ES_SERVER_URLS=http://elasticsearch:9200
    ports:
      - "16686:16686"
    depends_on:
      - elasticsearch

volumes:
  es-data:
EOF

docker compose up -d
docker compose ps

Step 4 — Instrument a Python Application

Install the OpenTelemetry Python SDK with the OTLP exporter and send spans to the Jaeger collector endpoint. The example creates a manual span to demonstrate the basic API.

pip install opentelemetry-sdk 
            opentelemetry-exporter-otlp-proto-grpc 
            opentelemetry-instrumentation

cat > trace_example.py <<'EOF'
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter

provider = TracerProvider()
exporter = OTLPSpanExporter(endpoint="http://localhost:14250", insecure=True)
provider.add_span_processor(BatchSpanProcessor(exporter))
trace.set_tracer_provider(provider)

tracer = trace.get_tracer("demo-service")
with tracer.start_as_current_span("process-order") as span:
    span.set_attribute("order.id", "ORD-12345")
    span.set_attribute("order.amount", 99.99)
    print("Span sent to Jaeger")
EOF

python trace_example.py

Step 5 — Search Traces in the Jaeger UI

Open the Jaeger UI at http://<your-server-ip>:16686. Use the search panel to filter by service, operation, tag values, or minimum duration to narrow down the spans of interest.

# Firewall: open the Jaeger UI port
sudo firewall-cmd --permanent --add-port=16686/tcp
sudo firewall-cmd --reload

# Verify the collector is accepting spans
curl -s http://localhost:14269/metrics | grep jaeger_collector_spans_received_total

# Check query service health
curl -s http://localhost:16687/

In the UI’s Search tab, select demo-service from the Service dropdown (it appears after the first span is received), set Lookback to Last 1 Hour, and click Find Traces. Click any result to expand the full span timeline with attribute key-value pairs displayed in the right panel.

Conclusion

You have run Jaeger all-in-one on RHEL 8 for development, created a systemd service for persistent operation, deployed a Docker Compose production stack with Elasticsearch storage, and sent spans from a Python application using the OpenTelemetry OTLP exporter. Jaeger’s UI makes it straightforward to identify latency outliers, trace request flows across service boundaries, and correlate trace IDs with log lines in your centralised log system. For high-throughput environments, consider scaling the collector horizontally behind a load balancer and increasing the number of Elasticsearch shards.

Next steps: Correlating Jaeger Traces with Prometheus Metrics Using Grafana, Setting Up Adaptive Sampling in Jaeger, and Deploying Jaeger on Kubernetes with the Jaeger Operator.