Setting up TLS (Transport Layer Security) from Sensor to Satellite
Secure the hop between the Levo eBPF Sensor and the Levo Satellite so sensors export traffic only over HTTPS/TLS. This guide covers Docker Compose Satellite deployments, where HAProxy presents the TLS listener that sensors dial.
- In scope: TLS between Sensor → Satellite (certificate on the Satellite listener; trust + HTTPS URL on each sensor host).
- Out of scope: TLS between your applications and their clients, and mutual TLS (mTLS). Contact Levo Support if you need mTLS.
What you will configure
| Side | Responsibility |
|---|---|
| Satellite host | Present a TLS endpoint (default port 8443) with a certificate whose SAN matches the hostname sensors will dial. |
| Each sensor host | Trust that certificate (or its issuing CA), point LEVO_SATELLITE_URL at https://…, and keep TLS verification enabled. |
Recommended layout on the Satellite host:
/opt/levo/haproxy/
site.crt # public certificate (or full chain)
site.key # private key (mode 600)
combined.pem # site.crt (+ intermediates) + site.key for HAProxy
haproxy.cfg.template
haproxy-ecs.cfg.template
Replace placeholder hostnames (satellite.example.com) and paths with values from your environment.
Prerequisites
- Docker Compose v2 on the Satellite host
- A DNS name (preferred) or stable IP for the Satellite that every sensor host can reach
- Root or sudo on the Satellite host and on each sensor host
- The
levo-ebpf-sensorpackage already installed on sensor hosts
1. Create the Satellite server certificate
Run these steps on a trusted machine (typically the Satellite host). The Sensor validates the server certificate against Subject Alternative Name (SAN) entries — not the Common Name alone. Every hostname or IP used in LEVO_SATELLITE_URL must appear in the SAN list.
1.1 Working directory
sudo mkdir -p /opt/levo/haproxy
cd /opt/levo/haproxy
1.2 Self-signed certificate (lab or closed networks)
openssl req -x509 -nodes -newkey rsa:4096 -sha256 -days 825 \
-keyout site.key -out site.crt \
-subj "/C=US/O=Example Corp/OU=Platform Security/CN=satellite.example.com" \
-addext "subjectAltName=DNS:satellite.example.com,DNS:localhost,IP:127.0.0.1"
Adjust subjectAltName to include every DNS name and IP sensors will use. Prefer ≤ 825 days for public-trust style rotation policies even on private PKI.
1.3 Enterprise PKI (recommended for production)
If you already operate an internal CA:
openssl req -new -newkey rsa:4096 -nodes -keyout site.key -out site.csr \
-subj "/CN=satellite.example.com" \
-addext "subjectAltName=DNS:satellite.example.com"
Have your CA sign site.csr to produce site.crt (include any required intermediate certificates). Sensors should trust the CA root (or the chain you distribute), not only the leaf.
1.4 PEM file for HAProxy
HAProxy loads certificate and key from a single file referenced by crt on the bind line:
# Self-signed / single leaf:
cat site.crt site.key > combined.pem
# With intermediates (leaf, then intermediates, then key):
# cat site.crt intermediate.crt site.key > combined.pem
sudo chmod 600 combined.pem site.key
sudo chmod 644 site.crt
2. Enable TLS on the Satellite listener (HAProxy)
In Docker Compose deployments, levoai-haproxy fronts Satellite and Collector. Sensors connect to the public listener (default 8443). HAProxy completes the TLS handshake there and forwards cleartext traffic to internal backends over the container network.
2.1 Required HAProxy settings
Ensure both haproxy.cfg.template and haproxy-ecs.cfg.template under /opt/levo/haproxy/ include:
- A sensor-facing frontend that accepts TLS ClientHellos on your chosen port (default
8443). - An internal TLS bind that loads
/levo/site-cert.pem(yourcombined.pem). - Path-based routing to
levoai-satelliteandlevoai-collectorunchanged from the stock Satellite compose stack.
Reference HAProxy configuration (Docker Compose)
Paste into both haproxy.cfg.template and haproxy-ecs.cfg.template (keep them identical for Compose):
global
log stdout format raw local0
maxconn 1024
defaults
log global
timeout client 60s
timeout connect 60s
timeout server 60s
frontend tcp-in
bind :8443
mode tcp
tcp-request inspect-delay 5s
tcp-request content accept if { req_ssl_hello_type 1 }
use_backend bk_tls if { req_ssl_hello_type 1 }
default_backend bk_plain_h2
backend bk_tls
mode tcp
server loopback 127.0.0.1:8444 send-proxy
backend bk_plain_h2
mode tcp
server loopback 127.0.0.1:8445 send-proxy
frontend https-in
bind :8444 accept-proxy ssl crt /levo/site-cert.pem alpn h2,http/1.1
mode http
monitor-uri /healthz
http-request set-var(req.authn_enabled) bool(${LEVOAI_SATELLITE_AUTHN_ENABLED})
acl is_health_check path_beg /healthz
acl prefix_paths path_beg /v1/ /1.0/ /tracer-config /sensor-config /sensor-btf /sensor-health
acl has_paths path -i -m sub opentelemetry.proto.collector
acl valid_org_id hdr(x-levo-organization-id) -i ${LEVOAI_ORG_ID}
http-request deny if { var(req.authn_enabled) -m bool } !valid_org_id !is_health_check prefix_paths
http-request deny if { var(req.authn_enabled) -m bool } !valid_org_id !is_health_check has_paths
default_backend levoai-collector-4317
use_backend levoai-collector-4318 if { path_beg /v1/traces } || { path_beg /v1/metrics } || { path_beg /v1/logs }
use_backend levoai-collector-4320 if { path_beg /v1/cloudfront-event }
use_backend levoai-collector-4322 if { path_beg /v1/edgeworker-event }
use_backend levoai-collector-4323 if { path_beg /v1/f5-ltm-logs }
use_backend levoai-satellite-9999 if { path_beg /1.0/ebpf/traces } || { path_beg /1.0/suricata } || { path_beg /1.0/har } || { path_beg /1.0/flush } || { path_beg /tracer-config } || { path_beg /sensor-config } || { path_beg /sensor-btf } || { path_beg /sensor-health }
frontend grpc-in
bind :8445 accept-proxy alpn h2 proto h2
mode http
default_backend levoai-collector-4317
use_backend levoai-satellite-9999 if { path_beg /1.0/ebpf/traces } || { path_beg /1.0/suricata } || { path_beg /1.0/har } || { path_beg /1.0/flush } || { path_beg /tracer-config } || { path_beg /sensor-config } || { path_beg /sensor-btf } || { path_beg /sensor-health }
frontend health-in
bind :8080 alpn h2,http/1.1
mode http
monitor-uri /healthz
default_backend levoai-collector-4317
backend levoai-collector-4317
mode http
server levoai-collector levoai-collector:4317 proto h2
backend levoai-collector-4318
mode http
server levoai-collector levoai-collector:4318
backend levoai-collector-4320
mode http
server levoai-collector levoai-collector:4320
backend levoai-collector-4322
mode http
server levoai-collector levoai-collector:4322
backend levoai-collector-4323
mode http
server levoai-collector levoai-collector:4323
backend levoai-satellite-9999
mode http
server levoai-satellite levoai-satellite:9999
Notes on HAProxy keywords: ssl, req_ssl_hello_type, and crt are HAProxy configuration tokens. They refer to the TLS listener even though the keyword spelling is historical.
2.2 Optional: change the sensor-facing port
Update the tcp-in bind (for example to 9443) in both templates. Leave internal loopback ports 8444 and 8445 unchanged — do not publish them on the host.
2.3 Mount the certificate and publish the listener
In docker-compose.yml, mount the templates and PEM, and publish only the sensor-facing port:
levoai-haproxy:
image: "levoai/haproxy:${LEVOAI_HAPROXY_VERSION:-latest}"
container_name: levoai-haproxy
restart: on-failure:5
cpu_shares: 512
security_opt:
- no-new-privileges:true
- apparmor:docker-default
depends_on:
- levoai-collector
- levoai-satellite
ports:
- "8443:8443" # match the tcp-in bind (e.g. "9443:9443")
volumes:
- /opt/levo/haproxy/haproxy.cfg.template:/usr/local/etc/haproxy/haproxy.cfg.template:ro
- /opt/levo/haproxy/haproxy-ecs.cfg.template:/usr/local/etc/haproxy/haproxy-ecs.cfg.template:ro
- /opt/levo/haproxy/combined.pem:/levo/site-cert.pem:ro
environment:
no_proxy: localhost,127.0.0.1,.local,*.local,levoai-rabbitmq,levoai-satellite,levoai-tagger,levoai-collector,levoai-ion
LEVOAI_SATELLITE_AUTHN_ENABLED: ${LEVOAI_SATELLITE_AUTHN_ENABLED:-false}
LEVOAI_CONF_OVERRIDES: >
{
"onprem-api": {
"org-id": "${LEVOAI_ORG_ID:-}"
}
}
logging:
options:
max-size: "100m"
max-file: "2"
Keep these three values aligned: HAProxy tcp-in bind, Compose ports: mapping, and LEVO_SATELLITE_URL on every sensor.
2.4 Apply and verify the Satellite certificate
cd /path/to/your/satellite-compose # directory that contains docker-compose.yml
docker compose up -d levoai-haproxy
docker compose logs --tail=50 levoai-haproxy
openssl s_client -connect satellite.example.com:8443 \
-servername satellite.example.com </dev/null \
| openssl x509 -noout -subject -issuer -dates -ext subjectAltName
Confirm the presented certificate matches the SAN list and validity window you expect.
3. Trust the certificate on each sensor host
Copy the trust material from the Satellite host to every sensor:
- Self-signed: distribute
site.crt(the leaf). - Enterprise CA: distribute the CA root (or the chain your security team standardizes on).
# On each sensor host
sudo mkdir -p /etc/levo/sensor
sudo scp user@satellite-host:/opt/levo/haproxy/site.crt \
/etc/levo/sensor/levo-satellite-ca.crt
sudo chmod 644 /etc/levo/sensor/levo-satellite-ca.crt
3.1 Install into the OS trust store
| Distribution | Install |
|---|---|
| Ubuntu / Debian | sudo cp … /usr/local/share/ca-certificates/levo-satellite-ca.crt && sudo update-ca-certificates |
| RHEL / CentOS / Rocky / Alma / Fedora / Amazon Linux | sudo cp … /etc/pki/ca-trust/source/anchors/levo-satellite-ca.crt && sudo update-ca-trust extract |
| SUSE / openSUSE | sudo cp … /etc/pki/trust/anchors/levo-satellite-ca.crt && sudo update-ca-certificates |
| Alpine | sudo cp … /usr/local/share/ca-certificates/levo-satellite-ca.crt && sudo update-ca-certificates |
| Arch | sudo trust anchor --store /etc/levo/sensor/levo-satellite-ca.crt |
3.2 Connectivity check (without disabling verification)
curl -v https://satellite.example.com:8443/healthz
This must succeed without -k / --insecure.
| Symptom | Likely cause |
|---|---|
certificate signed by unknown authority | Cert not in the OS trust store, or update-ca-certificates / update-ca-trust was not run. |
x509: certificate is valid for X, not Y | Hostname in LEVO_SATELLITE_URL is missing from the certificate SAN — reissue the cert. |
certificate has expired | Reissue, rebuild combined.pem, restart levoai-haproxy. |
4. Point the Sensor at the TLS Satellite URL
4.1 Sensor config (/etc/levo/sensor/config.yaml)
Keep verification enabled. Point the sensor at the same CA file you installed:
# TLS Settings: connectivity with Satellite
ignore-ssl-verify: false # product key name; leave false so TLS verification stays on
# tls-client-cert-path: "" # mTLS only
# tls-client-key-path: "" # mTLS only
tls-ca-cert-path: /etc/levo/sensor/levo-satellite-ca.crt
OS trust (section 3) makes host tooling (curl, diagnostics) work. tls-ca-cert-path pins the sensor process to the Satellite CA for reproducible deployments.
4.2 Runtime defaults (/etc/default/levo-ebpf-sensor)
sudo tee /etc/default/levo-ebpf-sensor >/dev/null <<'EOF'
# Levo eBPF Sensor runtime configuration
# Must use https:// and a hostname/IP present in the server certificate SAN.
LEVO_SATELLITE_URL=https://satellite.example.com:8443
LEVO_ORG_ID=<your-organization-id>
LEVO_WORKSPACE_ID=<your-workspace-id>
LEVO_ENV=prod
LEVO_CONFIG_FILE=/etc/levo/sensor/config.yaml
EOF
sudo chmod 644 /etc/default/levo-ebpf-sensor
LEVO_ORG_ID and LEVO_WORKSPACE_ID must match the Satellite compose environment (LEVOAI_ORG_ID). Find them in the Levo console under Settings → Organization / Workspace.
5. Restart and verify
sudo systemctl daemon-reload
sudo systemctl restart levo-ebpf-sensor
sudo systemctl status levo-ebpf-sensor --no-pager
sudo journalctl -u levo-ebpf-sensor -f --no-pager
A healthy start resolves the Satellite URL, completes the TLS handshake, and begins exporting traces. Investigate any of:
tls: failed to verify certificatex509: certificate signed by unknown authorityconnection refused
Checklist
-
site.crt,site.key, andcombined.pemexist under/opt/levo/haproxy/with correct permissions - Certificate SAN covers every hostname/IP used in
LEVO_SATELLITE_URL - HAProxy bind port, Compose
ports:mapping, and sensor URL port all match -
levoai-haproxyis healthy and/healthzreturns 200 over HTTPS - Every sensor host has the CA/leaf at
/etc/levo/sensor/levo-satellite-ca.crtand in the OS trust store -
tls-ca-cert-pathis set andignore-ssl-verifyremainsfalse - Sensor service restarted; logs show a successful TLS handshake with the Satellite