The OpenTelemetry Collector is a vendor-neutral agent that receives, processes and exports telemetry: traces, metrics and logs. Instead of pointing every application at a specific backend, you send everything to a local Collector and decide in its configuration where the data goes. In this tutorial you will install the Collector Contrib distribution on Ubuntu 24.04 and build three pipelines: OTLP traces forwarded to Jaeger, host metrics exposed for Prometheus, and system logs read from disk.
Prerequisites
To follow this guide you need:
- A server running Ubuntu 24.04 LTS with at least 1 GB of RAM, for example a CubePath VPS.
- A non-root user with
sudoprivileges. - Optional: a Jaeger v2 instance reachable over a private network on port
4317, referred to asjaeger_host. Without it, the traces pipeline still works with the debug exporter.
How the Collector is organized
A Collector configuration has four kinds of components, and nothing runs until it is referenced in a pipeline:
| Component | Role | Examples used here |
|---|---|---|
| Receivers | Get data into the Collector | otlp, hostmetrics, filelog |
| Processors | Modify or protect data in flight | memory_limiter, resourcedetection, batch |
| Exporters | Send data to backends | otlp/jaeger, prometheus, debug |
| Extensions | Features outside pipelines | health_check |
The service.pipelines section wires them together per signal type (traces, metrics, logs). The same receiver or exporter can appear in several pipelines.
Step 1 - Installing otelcol-contrib
The OpenTelemetry project publishes two main distributions: otelcol (core components only) and otelcol-contrib, which includes community components such as hostmetrics, filelog, resourcedetection and the Prometheus exporter. This guide needs the contrib distribution, which is released as a .deb package on GitHub.
Check the latest version on the opentelemetry-collector-releases page and store it in a variable, without the leading v:
OTELCOL_VERSION=0.135.0
Download and install the package for your architecture (amd64 here, arm64 for ARM servers):
cd /tmp
wget "https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v${OTELCOL_VERSION}/otelcol-contrib_${OTELCOL_VERSION}_linux_amd64.deb"
sudo apt install "./otelcol-contrib_${OTELCOL_VERSION}_linux_amd64.deb"
The package creates the otelcol-contrib system user, the otelcol-contrib systemd service and a default configuration at /etc/otelcol-contrib/config.yaml. Check the version and the service:
otelcol-contrib --version
sudo systemctl status otelcol-contrib --no-pager
otelcol-contrib version 0.135.0
● otelcol-contrib.service - OpenTelemetry Collector Contrib
Loaded: loaded (/usr/lib/systemd/system/otelcol-contrib.service; enabled; preset: enabled)
Active: active (running) since Thu 2026-09-24 11:30:05 UTC; 12s ago
The service reads its command-line options from /etc/otelcol-contrib/otelcol-contrib.conf, which points to the configuration file:
cat /etc/otelcol-contrib/otelcol-contrib.conf
# Systemd environment file for the otelcol-contrib service
# Command-line options for the otelcol-contrib service.
# Run `/usr/bin/otelcol-contrib --help` to see all available options.
OTELCOL_OPTIONS="--config=/etc/otelcol-contrib/config.yaml"
Step 2 - Allowing the Collector to read system logs
The filelog receiver will read /var/log/syslog and /var/log/auth.log. On Ubuntu these files belong to the adm group, so add the Collector's user to it:
sudo usermod -aG adm otelcol-contrib
id otelcol-contrib
uid=998(otelcol-contrib) gid=998(otelcol-contrib) groups=998(otelcol-contrib),4(adm)
The new group takes effect when the service restarts in Step 4.
Step 3 - Writing the configuration
Keep the default file for reference and create a new one:
sudo mv /etc/otelcol-contrib/config.yaml /etc/otelcol-contrib/config.yaml.orig
sudo nano /etc/otelcol-contrib/config.yaml
extensions:
health_check:
endpoint: 127.0.0.1:13133
receivers:
otlp:
protocols:
grpc:
endpoint: 127.0.0.1:4317
http:
endpoint: 127.0.0.1:4318
hostmetrics:
collection_interval: 30s
scrapers:
cpu:
memory:
load:
disk:
filesystem:
network:
filelog:
include:
- /var/log/syslog
- /var/log/auth.log
start_at: end
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 80
spike_limit_percentage: 20
resourcedetection:
detectors: [system]
batch:
exporters:
debug:
verbosity: basic
prometheus:
endpoint: 127.0.0.1:8889
resource_to_telemetry_conversion:
enabled: true
otlp/jaeger:
endpoint: jaeger_host:4317
tls:
insecure: true
service:
extensions: [health_check]
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, resourcedetection, batch]
exporters: [otlp/jaeger, debug]
metrics:
receivers: [otlp, hostmetrics]
processors: [memory_limiter, resourcedetection, batch]
exporters: [prometheus]
logs:
receivers: [otlp, filelog]
processors: [memory_limiter, resourcedetection, batch]
exporters: [debug]
What each part does:
- Receivers:
otlpaccepts traces, metrics and logs from instrumented applications on the standard ports, bound to localhost so only local processes can send.hostmetricsreads CPU, memory, load, disk, filesystem and network statistics every 30 seconds.filelogtails the two log files, starting at the end so existing lines are not re-imported on the first run. - Processors:
memory_limitergoes first and refuses data when the Collector approaches 80% of available memory, instead of being killed by the OOM killer.resourcedetectionadds thehost.nameandos.typeattributes so you can tell servers apart in the backends.batchgoes last and groups data into batches before export. - Exporters:
otlp/jaegersends traces to Jaeger over OTLP gRPC. The/jaegersuffix is just a name that lets you define several exporters of the same type.tls.insecure: truedisables TLS, which is only acceptable on a private network.prometheusexposes all metrics in Prometheus format on127.0.0.1:8889/metricsfor a Prometheus server to scrape.debugprints a summary of each batch to the Collector's own log.
If you do not have a Jaeger instance, remove otlp/jaeger from the exporters section and from the traces pipeline; the debug exporter alone is enough to follow the rest of the guide.
Step 4 - Validating and applying the configuration
The Collector can check a configuration file without starting any pipeline. Run the validation as the service user, so it also catches permission problems:
sudo -u otelcol-contrib otelcol-contrib validate --config=/etc/otelcol-contrib/config.yaml
No output and an exit status of 0 mean the file is valid. A typo in a component name or an unused component referenced in a pipeline produces an explicit error, for example service::pipelines::traces: references exporter "otlp/jager" which is not configured.
Restart the service to load the new configuration and the new group membership:
sudo systemctl restart otelcol-contrib
sudo journalctl -u otelcol-contrib -n 20 --no-pager
... info [email protected]/service.go Starting otelcol-contrib... {"Version": "0.135.0", "NumCPU": 2}
... info extensions/extensions.go Starting extensions...
... info healthcheck/handler.go Health Check state change {"status": "ready"}
... info [email protected]/service.go Everything is ready. Begin running and processing data.
Query the health check extension:
curl -s http://127.0.0.1:13133/
{"status":"Server available","upSince":"2026-09-24T11:42:18.52Z","uptime":"35.1s"}
Step 5 - Verifying the metrics pipeline
After the first 30-second collection interval, the host metrics are available on the Prometheus exporter endpoint. List a few of them:
curl -s http://127.0.0.1:8889/metrics | grep -E '^system_(cpu_load|memory_usage)' | head -n 4
system_cpu_load_average_1m{host_name="otel-01",os_type="linux"} 0.12
system_cpu_load_average_5m{host_name="otel-01",os_type="linux"} 0.08
system_memory_usage_bytes{host_name="otel-01",os_type="linux",state="used"} 4.1234432e+08
system_memory_usage_bytes{host_name="otel-01",os_type="linux",state="free"} 1.293434880e+09
The host_name and os_type labels come from the resourcedetection processor; resource_to_telemetry_conversion in the exporter turns those resource attributes into metric labels. To collect these metrics, add a scrape job to your Prometheus server targeting your_server_ip:8889; in that case change the exporter endpoint to the server's private IP and open the port only to the Prometheus server.
Step 6 - Verifying the traces and logs pipelines
Send a test span to the OTLP HTTP receiver with curl. The payload is a minimal OTLP JSON document with one span; the trace and span IDs are hexadecimal strings of 32 and 16 characters:
curl -s -X POST http://127.0.0.1:4318/v1/traces \
-H 'Content-Type: application/json' \
-d '{"resourceSpans":[{"resource":{"attributes":[{"key":"service.name","value":{"stringValue":"curl-test"}}]},"scopeSpans":[{"spans":[{"traceId":"5b8efff798038103d269b633813fc60c","spanId":"eee19b7ec3c1b174","name":"test-span","kind":1,"startTimeUnixNano":"1790246000000000000","endTimeUnixNano":"1790246000500000000"}]}]}]}'
{"partialSuccess":{}}
An empty partialSuccess means the Collector accepted the span. The debug exporter logs a one-line summary of each batch:
sudo journalctl -u otelcol-contrib --since "1 minute ago" --no-pager | grep -E 'Traces|Logs'
... info Traces {"otelcol.component.id": "debug", "otelcol.signal": "traces", "resource spans": 1, "spans": 1}
If you configured the Jaeger exporter, the curl-test service now appears in the Jaeger UI with its test-span.
To test the logs pipeline, write a line to syslog with logger:
logger "otel collector filelog test"
sleep 5
sudo journalctl -u otelcol-contrib --since "1 minute ago" --no-pager | grep Logs
... info Logs {"otelcol.component.id": "debug", "otelcol.signal": "logs", "resource logs": 1, "log records": 1}
To see the full content of each record while debugging, set verbosity: detailed on the debug exporter and restart the service, then set it back to basic: detailed output is very verbose.
Step 7 - Pointing applications at the Collector
Applications instrumented with any OpenTelemetry SDK can now send to the local Collector instead of a specific backend. The standard environment variables are the same in every language:
export OTEL_SERVICE_NAME=my-service
export OTEL_EXPORTER_OTLP_ENDPOINT=http://127.0.0.1:4317
If you later move traces from Jaeger to another backend, or add a second destination, you only change the Collector's exporters and pipelines and restart it; the applications keep sending to 127.0.0.1:4317.
Troubleshooting
- The service fails to start with
address already in use: another process (for example a Jaeger container on the same host) already listens on4317,4318or8889. Find it withsudo ss -tlnp | grep -E '4317|4318|8889'and change one of the ports. fileloglogspermission denied: theotelcol-contribuser is not in theadmgroup, or the service was not restarted after adding it.- The Jaeger exporter logs
connection refusedor keeps retrying: check thatjaeger_host:4317is reachable from this server withnc -zv jaeger_host 4317. The Collector queues and retries data, so a short outage does not lose spans. - Data is dropped with
data refused due to high memory usage: thememory_limiteris protecting the process. Give the server more RAM or reduce the incoming volume, for example by sampling traces.
Conclusion
You installed the OpenTelemetry Collector Contrib on Ubuntu 24.04 and configured pipelines that accept OTLP from applications, collect host metrics and system logs, and export them to Jaeger, Prometheus and the debug log. Next, you can scrape the :8889 endpoint from Prometheus, replace the logs debug exporter with a real log backend such as Loki, and add the probabilistic_sampler processor to the traces pipeline to control volume on busy services.
