Set up Jaeger distributed tracing system with Elasticsearch storage backend for microservices monitoring. Configure collector, query service, and UI with SSL security and performance optimization.
Prerequisites
- Root or sudo access
- Elasticsearch 8.x installed and running
- At least 4GB RAM
- Network connectivity for downloading packages
What this solves
Jaeger provides distributed tracing for microservices architectures, allowing you to track requests across multiple services and identify performance bottlenecks. This tutorial shows how to install Jaeger with Elasticsearch as the storage backend, configure SSL security, and optimize performance for production workloads.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you have the latest versions available.
sudo apt update && sudo apt upgrade -y
Install required dependencies
Install essential packages needed for Jaeger installation and operation.
sudo apt install -y curl wget unzip systemd
Create Jaeger user and directories
Create a dedicated user for Jaeger services and set up the required directory structure with proper permissions.
sudo useradd --system --shell /bin/false --home-dir /var/lib/jaeger jaeger
sudo mkdir -p /var/lib/jaeger /var/log/jaeger /etc/jaeger
sudo chown jaeger:jaeger /var/lib/jaeger /var/log/jaeger
sudo chmod 755 /var/lib/jaeger /var/log/jaeger
sudo chmod 755 /etc/jaeger
Download Jaeger binaries
Download the latest Jaeger release and extract the binaries to the system PATH.
cd /tmp
JAEGER_VERSION="1.51.0"
wget 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
sudo cp jaeger-${JAEGER_VERSION}-linux-amd64/jaeger-* /usr/local/bin/
sudo chmod 755 /usr/local/bin/jaeger-*
sudo chown root:root /usr/local/bin/jaeger-*
Configure Jaeger collector
Create the configuration file for Jaeger collector with Elasticsearch backend settings.
es:
server-urls: http://localhost:9200
index-prefix: jaeger
create-index-templates: true
version: 8
num-shards: 5
num-replicas: 1
bulk:
size: 5000000
workers: 1
flush-interval: 200ms
tags-as-fields:
all: false
dot-replacement: "@"
include: "http.status_code,error"
archive:
enabled: true
index-prefix: jaeger-archive
collector:
grpc-server:
host-port: 0.0.0.0:14250
http-server:
host-port: 0.0.0.0:14268
zipkin:
host-port: 0.0.0.0:9411
otlp:
grpc:
host-port: 0.0.0.0:4317
http:
host-port: 0.0.0.0:4318
log-level: info
Create Jaeger collector systemd service
Set up the systemd service file for the Jaeger collector component.
[Unit]
Description=Jaeger Collector
Documentation=https://www.jaegertracing.io/
After=network.target elasticsearch.service
Wants=elasticsearch.service
[Service]
Type=simple
User=jaeger
Group=jaeger
ExecStart=/usr/local/bin/jaeger-collector --config-file=/etc/jaeger/collector.yaml
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=jaeger-collector
KillMode=mixed
KillSignal=SIGTERM
Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/jaeger /var/lib/jaeger
Resource limits
LimitNOFILE=1000000
LimitNPROC=1000000
[Install]
WantedBy=multi-user.target
Configure Jaeger query service
Create the configuration file for Jaeger query service that provides the web UI and API.
es:
server-urls: http://localhost:9200
index-prefix: jaeger
version: 8
tags-as-fields:
all: false
dot-replacement: "@"
include: "http.status_code,error"
archive:
enabled: true
index-prefix: jaeger-archive
query:
base-path: /
ui-config: /etc/jaeger/ui.json
static-files: /usr/local/share/jaeger/ui/
log-file: /var/log/jaeger/query.log
host-port: 0.0.0.0:16686
grpc-server:
host-port: 0.0.0.0:16685
max-clock-skew-adjustment: 0s
log-level: info
Create Jaeger UI configuration
Configure the Jaeger UI with custom settings and branding options.
{
"monitor": {
"menuEnabled": true
},
"dependencies": {
"menuEnabled": true
},
"archiveEnabled": true,
"tracking": {
"gaID": null,
"trackErrors": true
},
"search": {
"maxLookback": {
"label": "2 Days",
"value": "2d"
},
"maxLimit": 1500
},
"scripts": [],
"linkPatterns": []
}
Create Jaeger query systemd service
Set up the systemd service file for the Jaeger query component.
[Unit]
Description=Jaeger Query Service
Documentation=https://www.jaegertracing.io/
After=network.target elasticsearch.service jaeger-collector.service
Wants=elasticsearch.service
[Service]
Type=simple
User=jaeger
Group=jaeger
ExecStart=/usr/local/bin/jaeger-query --config-file=/etc/jaeger/query.yaml
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=jaeger-query
KillMode=mixed
KillSignal=SIGTERM
Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/jaeger /var/lib/jaeger
Resource limits
LimitNOFILE=65536
LimitNPROC=65536
[Install]
WantedBy=multi-user.target
Configure sampling strategies
Create sampling strategies configuration to control trace collection rates and reduce storage overhead.
{
"service_strategies": [
{
"service": "frontend",
"type": "probabilistic",
"param": 1.0
},
{
"service": "api-gateway",
"type": "probabilistic",
"param": 0.8
},
{
"service": "user-service",
"type": "probabilistic",
"param": 0.5
}
],
"default_strategy": {
"type": "probabilistic",
"param": 0.1
},
"operation_strategies": [
{
"service": "frontend",
"operation": "health-check",
"type": "probabilistic",
"param": 0.001
}
]
}
Generate SSL certificates
Create SSL certificates for secure communication between Jaeger components.
sudo mkdir -p /etc/jaeger/certs
cd /etc/jaeger/certs
Generate CA key and certificate
sudo openssl genrsa -out ca-key.pem 4096
sudo openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -out ca.pem -subj "/C=US/ST=CA/L=San Francisco/O=Example/CN=Jaeger CA"
Generate server key and certificate
sudo openssl genrsa -out server-key.pem 4096
sudo openssl req -subj "/CN=jaeger.example.com" -sha256 -new -key server-key.pem -out server.csr
sudo openssl x509 -req -days 365 -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -out server-cert.pem -CAcreateserial
Set proper permissions
sudo chown jaeger:jaeger /etc/jaeger/certs/*
sudo chmod 600 /etc/jaeger/certs/*-key.pem
sudo chmod 644 /etc/jaeger/certs/*.pem
sudo rm server.csr
Configure firewall rules
Open the necessary ports for Jaeger services to communicate properly.
sudo ufw allow 14250/tcp comment 'Jaeger gRPC'
sudo ufw allow 14268/tcp comment 'Jaeger HTTP'
sudo ufw allow 16686/tcp comment 'Jaeger UI'
sudo ufw allow 9411/tcp comment 'Zipkin'
sudo ufw allow 4317/tcp comment 'OTLP gRPC'
sudo ufw allow 4318/tcp comment 'OTLP HTTP'
Start and enable Jaeger services
Enable and start both Jaeger collector and query services.
sudo systemctl daemon-reload
sudo systemctl enable jaeger-collector jaeger-query
sudo systemctl start jaeger-collector
sudo systemctl start jaeger-query
Configure performance optimization
Create additional configuration for production performance tuning.
# Additional collector configuration for high throughput
collector:
queue-size: 10000
num-workers: 50
es-bulk-size: 5000000
es-bulk-workers: 5
es-bulk-flush-interval: 200ms
Memory settings
max-spans: 100000
max-connections-per-host: 30
Archive settings
archive:
max-span-age: 168h # 7 days
cleanup-interval: 1h
Configure SSL/TLS security
Update collector configuration for SSL
Enable SSL/TLS encryption for the Jaeger collector service.
es:
server-urls: https://localhost:9200
index-prefix: jaeger
create-index-templates: true
version: 8
tls:
enabled: true
ca: /etc/jaeger/certs/ca.pem
cert: /etc/jaeger/certs/server-cert.pem
key: /etc/jaeger/certs/server-key.pem
server-name: elasticsearch.example.com
skip-host-verify: false
collector:
grpc-server:
host-port: 0.0.0.0:14250
tls:
enabled: true
cert: /etc/jaeger/certs/server-cert.pem
key: /etc/jaeger/certs/server-key.pem
ca: /etc/jaeger/certs/ca.pem
http-server:
host-port: 0.0.0.0:14268
tls:
enabled: true
cert: /etc/jaeger/certs/server-cert.pem
key: /etc/jaeger/certs/server-key.pem
log-level: info
Update query service for SSL
Configure SSL/TLS for the Jaeger query service and web UI.
es:
server-urls: https://localhost:9200
index-prefix: jaeger
version: 8
tls:
enabled: true
ca: /etc/jaeger/certs/ca.pem
cert: /etc/jaeger/certs/server-cert.pem
key: /etc/jaeger/certs/server-key.pem
server-name: elasticsearch.example.com
skip-host-verify: false
query:
base-path: /
ui-config: /etc/jaeger/ui.json
host-port: 0.0.0.0:16686
tls:
enabled: true
cert: /etc/jaeger/certs/server-cert.pem
key: /etc/jaeger/certs/server-key.pem
ca: /etc/jaeger/certs/ca.pem
grpc-server:
host-port: 0.0.0.0:16685
tls:
enabled: true
cert: /etc/jaeger/certs/server-cert.pem
key: /etc/jaeger/certs/server-key.pem
log-level: info
Verify your setup
Check that all Jaeger services are running correctly and can communicate with Elasticsearch.
sudo systemctl status jaeger-collector
sudo systemctl status jaeger-query
Check service logs
sudo journalctl -u jaeger-collector -n 20
sudo journalctl -u jaeger-query -n 20
Verify Elasticsearch connectivity
curl -f http://localhost:9200/_cluster/health
curl -f http://localhost:9200/jaeger-*/_search?size=0
Test Jaeger endpoints
curl -f http://localhost:14268/api/sampling
curl -f http://localhost:16686/api/services
Check UI accessibility
curl -I http://localhost:16686
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Collector fails to start | Elasticsearch not accessible | Check ES health: curl localhost:9200/_cluster/health |
| No traces in UI | Wrong collector endpoint | Verify apps send to correct port (14268 HTTP, 14250 gRPC) |
| High memory usage | Too many spans in memory | Reduce queue-size and increase bulk workers |
| SSL certificate errors | Wrong certificate paths | Check file permissions: ls -la /etc/jaeger/certs/ |
| Permission denied errors | Incorrect file ownership | sudo chown -R jaeger:jaeger /var/lib/jaeger /var/log/jaeger |
| UI loads but no data | Query service can't reach ES | Check query service logs and ES connectivity |
Integration with monitoring stack
Jaeger integrates well with existing monitoring infrastructure. You can forward logs to Fluentd for centralized collection and use it alongside Grafana and Prometheus for comprehensive observability.
Next steps
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Default values
JAEGER_VERSION="1.51.0"
ELASTICSEARCH_URL="http://localhost:9200"
# Usage function
usage() {
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -v VERSION Jaeger version (default: $JAEGER_VERSION)"
echo " -e URL Elasticsearch URL (default: $ELASTICSEARCH_URL)"
echo " -h Show this help"
exit 1
}
# Parse arguments
while getopts "v:e:h" opt; do
case $opt in
v) JAEGER_VERSION="$OPTARG" ;;
e) ELASTICSEARCH_URL="$OPTARG" ;;
h) usage ;;
*) usage ;;
esac
done
# Logging functions
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Cleanup function
cleanup() {
log_error "Installation failed. Cleaning up..."
systemctl stop jaeger-collector jaeger-query 2>/dev/null || true
systemctl disable jaeger-collector jaeger-query 2>/dev/null || true
rm -f /etc/systemd/system/jaeger-*.service
rm -rf /etc/jaeger /var/lib/jaeger /var/log/jaeger
userdel jaeger 2>/dev/null || true
rm -f /usr/local/bin/jaeger-*
systemctl daemon-reload
}
trap cleanup ERR
# Check prerequisites
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root"
exit 1
fi
# Detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
;;
*)
log_error "Unsupported distro: $ID"
exit 1
;;
esac
else
log_error "Cannot detect distribution"
exit 1
fi
log_info "Detected distribution: $ID"
log_info "Installing Jaeger $JAEGER_VERSION with Elasticsearch backend"
# Step 1: Update system
echo "[1/8] Updating system packages..."
$PKG_UPDATE
# Step 2: Install dependencies
echo "[2/8] Installing required dependencies..."
$PKG_INSTALL curl wget unzip systemd
# Step 3: Create jaeger user and directories
echo "[3/8] Creating Jaeger user and directories..."
useradd --system --shell /bin/false --home-dir /var/lib/jaeger jaeger 2>/dev/null || true
mkdir -p /var/lib/jaeger /var/log/jaeger /etc/jaeger
chown jaeger:jaeger /var/lib/jaeger /var/log/jaeger
chmod 755 /var/lib/jaeger /var/log/jaeger /etc/jaeger
# Step 4: Download Jaeger binaries
echo "[4/8] Downloading Jaeger binaries..."
cd /tmp
wget -q "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"
cp "jaeger-${JAEGER_VERSION}-linux-amd64"/jaeger-* /usr/local/bin/
chmod 755 /usr/local/bin/jaeger-*
chown root:root /usr/local/bin/jaeger-*
# Step 5: Configure Jaeger collector
echo "[5/8] Configuring Jaeger collector..."
cat > /etc/jaeger/collector.yaml << EOF
es:
server-urls: $ELASTICSEARCH_URL
index-prefix: jaeger
create-index-templates: true
version: 8
num-shards: 5
num-replicas: 1
bulk:
size: 5000000
workers: 1
flush-interval: 200ms
tags-as-fields:
all: false
dot-replacement: "@"
include: "http.status_code,error"
archive:
enabled: true
index-prefix: jaeger-archive
collector:
grpc-server:
host-port: 0.0.0.0:14250
http-server:
host-port: 0.0.0.0:14268
zipkin:
host-port: 0.0.0.0:9411
otlp:
grpc:
host-port: 0.0.0.0:4317
http:
host-port: 0.0.0.0:4318
log-level: info
EOF
# Step 6: Create collector systemd service
echo "[6/8] Creating Jaeger collector service..."
cat > /etc/systemd/system/jaeger-collector.service << 'EOF'
[Unit]
Description=Jaeger Collector
Documentation=https://www.jaegertracing.io/
After=network.target elasticsearch.service
Wants=elasticsearch.service
[Service]
Type=simple
User=jaeger
Group=jaeger
ExecStart=/usr/local/bin/jaeger-collector --config-file=/etc/jaeger/collector.yaml
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=jaeger-collector
KillMode=mixed
KillSignal=SIGTERM
# Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/jaeger /var/lib/jaeger
# Resource limits
LimitNOFILE=1000000
LimitNPROC=1000000
[Install]
WantedBy=multi-user.target
EOF
# Step 7: Configure Jaeger query service
echo "[7/8] Configuring Jaeger query service..."
cat > /etc/jaeger/query.yaml << EOF
es:
server-urls: $ELASTICSEARCH_URL
index-prefix: jaeger
version: 8
tags-as-fields:
all: false
dot-replacement: "@"
include: "http.status_code,error"
archive:
enabled: true
index-prefix: jaeger-archive
query:
base-path: /
ui-config: /etc/jaeger/ui.json
log-file: /var/log/jaeger/query.log
host-port: 0.0.0.0:16686
grpc-server:
host-port: 0.0.0.0:16685
max-clock-skew-adjustment: 0s
log-level: info
EOF
# Create UI configuration
cat > /etc/jaeger/ui.json << 'EOF'
{
"monitor": {
"menuEnabled": true
},
"dependencies": {
"menuEnabled": true
},
"archiveEnabled": true,
"tracking": {
"gaID": "",
"trackErrors": true
}
}
EOF
# Create query systemd service
cat > /etc/systemd/system/jaeger-query.service << 'EOF'
[Unit]
Description=Jaeger Query
Documentation=https://www.jaegertracing.io/
After=network.target jaeger-collector.service
Wants=jaeger-collector.service
[Service]
Type=simple
User=jaeger
Group=jaeger
ExecStart=/usr/local/bin/jaeger-query --config-file=/etc/jaeger/query.yaml
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=jaeger-query
KillMode=mixed
KillSignal=SIGTERM
# Security settings
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/log/jaeger /var/lib/jaeger
[Install]
WantedBy=multi-user.target
EOF
# Set proper ownership and permissions
chown jaeger:jaeger /etc/jaeger/*.yaml /etc/jaeger/*.json
chmod 644 /etc/jaeger/*.yaml /etc/jaeger/*.json
# Step 8: Enable and start services
echo "[8/8] Enabling and starting services..."
systemctl daemon-reload
systemctl enable jaeger-collector jaeger-query
systemctl start jaeger-collector
sleep 3
systemctl start jaeger-query
# Configure firewall if available
if command -v ufw >/dev/null 2>&1; then
log_info "Configuring UFW firewall..."
ufw allow 14250/tcp comment "Jaeger gRPC"
ufw allow 14268/tcp comment "Jaeger HTTP"
ufw allow 16686/tcp comment "Jaeger UI"
ufw allow 9411/tcp comment "Zipkin"
ufw allow 4317/tcp comment "OTLP gRPC"
ufw allow 4318/tcp comment "OTLP HTTP"
elif command -v firewall-cmd >/dev/null 2>&1; then
log_info "Configuring firewalld..."
firewall-cmd --permanent --add-port=14250/tcp --add-port=14268/tcp --add-port=16686/tcp --add-port=9411/tcp --add-port=4317/tcp --add-port=4318/tcp
firewall-cmd --reload
fi
# Verification
echo
log_info "Verifying installation..."
sleep 5
if systemctl is-active --quiet jaeger-collector; then
log_info "✓ Jaeger collector is running"
else
log_error "✗ Jaeger collector failed to start"
exit 1
fi
if systemctl is-active --quiet jaeger-query; then
log_info "✓ Jaeger query service is running"
else
log_error "✗ Jaeger query service failed to start"
exit 1
fi
if curl -s -f http://localhost:16686/api/services >/dev/null 2>&1; then
log_info "✓ Jaeger API is responding"
else
log_warn "! Jaeger API not yet responding (may need more time)"
fi
echo
log_info "Jaeger installation completed successfully!"
log_info "Jaeger UI: http://$(hostname -I | awk '{print $1}'):16686"
log_info "Collector endpoints:"
log_info " - gRPC: :14250"
log_info " - HTTP: :14268"
log_info " - Zipkin: :9411"
log_info " - OTLP gRPC: :4317"
log_info " - OTLP HTTP: :4318"
log_info
log_info "Service management:"
log_info " systemctl {start|stop|restart|status} jaeger-collector"
log_info " systemctl {start|stop|restart|status} jaeger-query"
log_info
log_info "Logs:"
log_info " journalctl -u jaeger-collector -f"
log_info " journalctl -u jaeger-query -f"
Review the script before running. Execute with: bash install.sh