Configure Jaeger with NGINX reverse proxy and SSL termination

Intermediate 45 min Apr 21, 2026 125 views
Ubuntu 24.04 Debian 12 AlmaLinux 9 Rocky Linux 9

Set up Jaeger distributed tracing behind an NGINX reverse proxy with SSL termination and authentication. Learn to configure secure access, performance optimization, and production-ready monitoring for your microservices.

Prerequisites

  • Root or sudo access
  • Domain name with DNS configured
  • Docker and Docker Compose
  • Basic understanding of NGINX configuration

What this solves

Jaeger provides distributed tracing for microservices, but exposing it directly to the internet creates security risks. This tutorial shows you how to configure NGINX as a reverse proxy for Jaeger with SSL termination, authentication, and security headers for production use.

Step-by-step configuration

Update system packages

Start by updating your package manager to ensure you get the latest versions of all components.

sudo apt update && sudo apt upgrade -y
sudo dnf update -y

Install Docker and Docker Compose

Jaeger runs best in containers for easy management and updates. Install Docker and Docker Compose for container orchestration.

sudo apt install -y docker.io docker-compose-v2
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
sudo dnf install -y docker docker-compose
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
Note: Log out and back in for the docker group membership to take effect.

Install NGINX

Install NGINX to act as the reverse proxy for Jaeger. NGINX will handle SSL termination and authentication.

sudo apt install -y nginx apache2-utils
sudo dnf install -y nginx httpd-tools

Install Certbot for Let's Encrypt

Certbot automatically obtains and renews SSL certificates from Let's Encrypt for secure HTTPS connections.

sudo apt install -y certbot python3-certbot-nginx
sudo dnf install -y certbot python3-certbot-nginx

Create Jaeger Docker Compose configuration

Set up Jaeger with all-in-one deployment including the collector, query service, and UI components.

sudo mkdir -p /opt/jaeger
cd /opt/jaeger
version: '3.8'

services:
  jaeger:
    image: jaegertracing/all-in-one:1.52
    container_name: jaeger
    restart: unless-stopped
    ports:
      - "127.0.0.1:14268:14268"   # HTTP collector
      - "127.0.0.1:16686:16686"   # Web UI
      - "127.0.0.1:14250:14250"   # gRPC collector
      - "6831:6831/udp"           # UDP agent
      - "6832:6832/udp"           # UDP agent
    environment:
      - COLLECTOR_OTLP_ENABLED=true
      - COLLECTOR_ZIPKIN_HOST_PORT=:9411
    volumes:
      - jaeger_data:/badger
    command: [
      "--memory.max-traces=100000",
      "--query.base-path=/jaeger",
      "--admin.http.host-port=:16687"
    ]
    networks:
      - jaeger-net

volumes:
  jaeger_data:

networks:
  jaeger-net:
    driver: bridge

Start Jaeger container

Launch the Jaeger service and verify it's running correctly on the expected ports.

sudo docker compose up -d
sudo docker compose ps

Verify Jaeger is accessible locally:

curl -I http://localhost:16686/jaeger

Create basic authentication file

Set up HTTP basic authentication to secure access to Jaeger. Replace admin with your desired username.

sudo mkdir -p /etc/nginx/auth
sudo htpasswd -c /etc/nginx/auth/jaeger admin

Enter a strong password when prompted. The file will contain the hashed password for secure authentication.

Configure NGINX reverse proxy

Create an NGINX configuration that proxies requests to Jaeger with proper headers and authentication.

server {
    listen 80;
    server_name jaeger.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name jaeger.example.com;

    # SSL configuration will be added by Certbot

    # Security headers
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' ws: wss:;" always;

    # Enable gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript application/javascript application/xml+rss application/json;

    # Authentication
    auth_basic "Jaeger Access";
    auth_basic_user_file /etc/nginx/auth/jaeger;

    # Proxy settings
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host $host;
    proxy_set_header X-Forwarded-Port $server_port;

    # WebSocket support for real-time updates
    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";

    # Timeouts
    proxy_connect_timeout 60s;
    proxy_send_timeout 60s;
    proxy_read_timeout 60s;

    location /jaeger {
        proxy_pass http://127.0.0.1:16686/jaeger;
    }

    location / {
        return 301 /jaeger/;
    }

    # Health check endpoint (no auth required)
    location /health {
        auth_basic off;
        proxy_pass http://127.0.0.1:16687/;
        access_log off;
    }

    # Block access to sensitive paths
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }
}

Rate limiting

limit_req_zone $binary_remote_addr zone=jaeger_limit:10m rate=10r/m; limit_req zone=jaeger_limit burst=5 nodelay;

Enable the NGINX site

Activate the Jaeger configuration and test the NGINX configuration for syntax errors.

sudo ln -sf /etc/nginx/sites-available/jaeger /etc/nginx/sites-enabled/
sudo nginx -t
Note: Replace jaeger.example.com with your actual domain name before proceeding.

Obtain SSL certificate

Use Certbot to automatically obtain and configure SSL certificates from Let's Encrypt.

sudo certbot --nginx -d jaeger.example.com

Follow the prompts to enter your email and agree to the terms of service. Certbot will automatically modify your NGINX configuration to include SSL settings.

Configure automatic certificate renewal

Set up automatic renewal for Let's Encrypt certificates to prevent expiration issues.

sudo systemctl enable --now certbot.timer
sudo certbot renew --dry-run

Optimize NGINX configuration

Update the main NGINX configuration for better performance with reverse proxying.

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
}

http {
    # Basic Settings
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    client_max_body_size 100M;
    
    # Hide nginx version
    server_tokens off;
    
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    
    # Logging Settings
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for" '
                    '$request_time $upstream_response_time';
    
    access_log /var/log/nginx/access.log main;
    error_log /var/log/nginx/error.log warn;
    
    # Gzip Settings
    gzip on;
    gzip_vary on;
    gzip_comp_level 6;
    gzip_min_length 1000;
    gzip_types
        application/atom+xml
        application/geo+json
        application/javascript
        application/x-javascript
        application/json
        application/ld+json
        application/manifest+json
        application/rdf+xml
        application/rss+xml
        application/xhtml+xml
        application/xml
        font/eot
        font/otf
        font/ttf
        image/svg+xml
        text/css
        text/javascript
        text/plain
        text/xml;
    
    # Buffer settings
    proxy_buffering on;
    proxy_buffer_size 128k;
    proxy_buffers 4 256k;
    proxy_busy_buffers_size 256k;
    
    # Virtual Host Configs
    include /etc/nginx/conf.d/*.conf;
    include /etc/nginx/sites-enabled/*;
}

Start and enable services

Start NGINX and ensure both NGINX and Docker start automatically on system boot.

sudo systemctl enable --now nginx
sudo systemctl status nginx
sudo systemctl status docker

Configure firewall rules

Open the necessary firewall ports for HTTP, HTTPS, and Jaeger's UDP ports for trace collection.

sudo ufw allow 'Nginx Full'
sudo ufw allow 6831/udp
sudo ufw allow 6832/udp
sudo ufw reload
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --add-port=6831/udp
sudo firewall-cmd --permanent --add-port=6832/udp
sudo firewall-cmd --reload

Verify your setup

Test that Jaeger is accessible through NGINX with proper SSL and authentication:

# Test SSL certificate
curl -I https://jaeger.example.com/health

Test authentication (should return 401)

curl -I https://jaeger.example.com/jaeger

Test with authentication

curl -u admin:your_password -I https://jaeger.example.com/jaeger

Check Docker container status

sudo docker compose -f /opt/jaeger/docker-compose.yml ps

Verify NGINX configuration

sudo nginx -t

Check SSL certificate expiry

sudo certbot certificates

You should be able to access Jaeger at https://jaeger.example.com/jaeger using the username and password you created.

Configure application instrumentation

Configure trace collection endpoints

Your applications need to send traces to Jaeger. Configure them to use these endpoints:

  • HTTP Collector: http://your-server-ip:14268/api/traces
  • gRPC Collector: your-server-ip:14250
  • UDP Agent (Compact): your-server-ip:6831
  • UDP Agent (Binary): your-server-ip:6832

Example application configuration

Here's how to configure a Node.js application to send traces to Jaeger:

const { NodeSDK } = require('@opentelemetry/sdk-node');
const { JaegerExporter } = require('@opentelemetry/exporter-jaeger');

const jaegerExporter = new JaegerExporter({
  endpoint: 'http://your-server-ip:14268/api/traces',
});

const sdk = new NodeSDK({
  traceExporter: jaegerExporter,
  serviceName: 'my-application',
});

sdk.start();

Monitor and maintain your setup

Set up log monitoring

Monitor NGINX and Jaeger logs for issues and performance metrics.

# Monitor NGINX access logs
sudo tail -f /var/log/nginx/access.log

Monitor NGINX error logs

sudo tail -f /var/log/nginx/error.log

Monitor Jaeger container logs

sudo docker compose -f /opt/jaeger/docker-compose.yml logs -f

Configure log rotation

Set up log rotation to prevent disk space issues with growing log files.

/var/log/nginx/access.log /var/log/nginx/error.log {
    daily
    rotate 30
    compress
    delaycompress
    missingok
    notifempty
    create 0644 www-data www-data
    postrotate
        systemctl reload nginx
    endscript
}

Common issues

SymptomCauseFix
502 Bad Gateway errorJaeger container not runningsudo docker compose -f /opt/jaeger/docker-compose.yml up -d
SSL certificate errorDomain DNS not pointing to serverUpdate DNS A record to point to server IP
Authentication prompt not showingauth_basic directive missingCheck NGINX configuration for auth_basic lines
Traces not appearingApplication misconfiguredVerify collector endpoint URL and network connectivity
High memory usageToo many traces storedAdjust --memory.max-traces parameter in docker-compose.yml
Certificate renewal failsNGINX configuration conflictssudo certbot renew --nginx --force-renewal

Next steps

Running this in production?

Want this handled for you? Setting up Jaeger with NGINX once is straightforward. Keeping it patched, monitored, backed up and performant across environments is the harder part. See how we run infrastructure like this for European teams needing reliable observability stacks.

Automated install script

Run this to automate the entire setup

Need help?

Don't want to manage this yourself?

We handle managed devops services for businesses that depend on uptime. From initial setup to ongoing operations.