How to Install OpenTelemetry Collector on Ubuntu 24.04 VPS: Unified Telemetry Pipeline
Modern applications emit three kinds of signals: metrics (numbers over time), logs (discrete events), and traces (causal chains of requests across services). Historically, each signal had its own agent, its own protocol, and its own backend. OpenTelemetry changes that. By running a single OpenTelemetry Collector on your Ubuntu 24.04 VPS, you terminate OTLP from every SDK in your stack, normalize the data, and fan it out to Prometheus, Loki, Tempo, or any other backend you already operate.
This guide walks through installing otelcol-contrib from the official apt repository, authoring a production-ready config.yaml with OTLP, Prometheus scrape, and filelog receivers, wiring in the batch, memory_limiter, and resource processors, and exporting to Prometheus (via remote write), Loki, and Tempo. By the end you will have a unified telemetry pipeline, your applications instrumented with the OpenTelemetry SDK, and a systemd service that restarts cleanly on reboot.
Skip the manual setup? Our CloudCore Starter VPS gives you a clean Ubuntu 24.04 environment ready for the OpenTelemetry Collector in under 60 seconds.
Table of Contents
What is the OpenTelemetry Collector?
The OpenTelemetry Collector is a vendor-neutral proxy that receives, processes, and exports telemetry data. It replaces a patchwork of per-backend agents (Fluent Bit, Promtail, Jaeger Agent, Statsd, Filebeat, OpenCensus, Datadog Agent, New Relic Infrastructure Agent) with a single binary that speaks OTLP on the input side and any backend protocol you care about on the output side.
The collector has three core concepts:
- Receivers ingest data. The most important is the OTLP receiver (gRPC on port 4317, HTTP on port 4318), which is the native OpenTelemetry protocol. The contrib distribution also ships receivers for Prometheus scrape, filelog, syslog, hostmetrics, Kafka, Jaeger, Zipkin, Statsd, and dozens more.
- Processors transform data in flight. Common examples are
batch(groups small payloads into efficient batches),memory_limiter(applies back-pressure when RAM pressure rises),resource(attaches host/service attributes),attributes(rewrites or redacts fields),filter(drops noisy data), andtail_sampling(keeps only interesting traces). - Exporters send data out.
prometheusremotewritepushes metrics to any Prometheus-compatible store (Prometheus itself, Mimir, Thanos, VictoriaMetrics).lokipushes logs to Grafana Loki.otlpsends traces to Grafana Tempo or any OTLP-compatible trace backend. Additional exporters cover Elasticsearch, Kafka, S3, Datadog, New Relic, Honeycomb, and Splunk.
service block. A pipeline is a typed chain: a traces pipeline receives traces, sends them through processors, and hands them to trace exporters. Each signal (traces, metrics, logs) gets its own pipeline, which is what makes the collector a true unified telemetry plane.Two distributions are published. otelcol is the core distribution with a minimal set of components. otelcol-contrib is the contrib distribution with everything maintained by the community. For a real production pipeline that includes Loki and Tempo, you want contrib.
Why Self-Host a Telemetry Pipeline?
Hosted observability platforms (Datadog, New Relic, Honeycomb, Grafana Cloud, Dynatrace) are convenient, but they meter ingest aggressively. Cardinality you pay for at a SaaS vendor is cardinality you control for free on your own VPS. Running the OpenTelemetry Collector yourself buys you:
- Protocol independence -- Your applications emit OTLP. If you later move from Prometheus to Mimir, from Loki to Elasticsearch, or from Tempo to Jaeger, you change a few lines in
config.yaml. The SDK code never changes. This is the single biggest reason to standardize on OpenTelemetry. - Flat cost -- A CloudCore Starter VPS ingests millions of spans per day for a fixed monthly fee. SaaS vendors charge per GB or per host, and costs scale non-linearly with cardinality.
- Data residency and compliance -- Telemetry routinely contains PII, request bodies, error messages with user data, and SQL fragments. Keeping it on a known EU-hosted VPS keeps you inside your GDPR processing agreements.
- Unified schema -- One resource attribute schema across metrics, logs, and traces means a single
service.name="checkout"filter works across all three views in Grafana. You stop juggling mismatched labels between Loki and Prometheus. - Transformation control -- The
attributes,filter, andredactionprocessors let you strip sensitive fields or drop high-cardinality labels before data leaves your VPS. SaaS vendors offer some of this, but you only see what you already sent. - Zero-downtime vendor swaps -- Run two exporters in parallel during a migration. Send traces to Tempo and Jaeger simultaneously for a week, then cut over when you trust the new backend.
Prerequisites
Before you begin, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access
- SSH access to your server
- At least 2 GB of RAM (4 GB recommended if you will run Prometheus, Loki, or Tempo on the same host)
- At least 10 GB of free disk space
- Prometheus, Loki, and Tempo reachable from the VPS -- on localhost, a private network, or the public internet
Recommended Plan: CloudCore Starter>
The collector itself is lightweight -- 150 to 400 MB of RAM at moderate throughput. For a dedicated collector host, the CloudCore Starter plan is more than enough:>
- 2 vCPU cores
- 4 GB RAM
- 50 GB NVMe SSD
- Unmetered bandwidth>
If you plan to co-locate Prometheus, Loki, and Tempo on the same VPS, step up to CloudCore Professional for extra headroom.
Connect to your server via SSH:
ssh root@your-server-ipStep 1: Update System Packages
Start with a clean, patched system. This matters because the apt repository we add in the next step validates packages against gpg and apt-transport-https, which must be present and current.
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl gpg apt-transport-https ca-certificatesExpected output (abbreviated):
Reading package lists... Done
Building dependency tree... Done
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.If the kernel was updated, reboot before continuing:
sudo rebootStep 2: Add the OpenTelemetry apt Repository
The OpenTelemetry project publishes signed .deb packages for every release of both otelcol and otelcol-contrib. We register the repository and import its signing key.
Import the signing key:
curl -fsSL https://raw.githubusercontent.com/open-telemetry/opentelemetry-collector-releases/main/otelcol-contrib/opentelemetry.gpg.key \
| sudo gpg --dearmor -o /usr/share/keyrings/opentelemetry-archive-keyring.gpgRegister the apt source:
echo "deb [signed-by=/usr/share/keyrings/opentelemetry-archive-keyring.gpg] https://apt.opentelemetry.io/ stable main" \
| sudo tee /etc/apt/sources.list.d/opentelemetry.list > /dev/nullRefresh the package index:
sudo apt updateExpected output:
Hit:1 http://archive.ubuntu.com/ubuntu noble InRelease
Get:2 https://apt.opentelemetry.io stable InRelease
Get:3 https://apt.opentelemetry.io stable/main amd64 Packages
Reading package lists... DoneAlternative: direct .deb download. If you prefer not to trust an external apt source, download the latest.debfrom the GitHub releases page and install withsudo dpkg -i otelcol-contrib_*.deb. Both methods yield the same binary and systemd unit.
Step 3: Install otelcol-contrib
Install the contrib distribution:
sudo apt install -y otelcol-contribExpected output:
The following NEW packages will be installed:
otelcol-contrib
...
Created symlink /etc/systemd/system/multi-user.target.wants/otelcol-contrib.service -> /lib/systemd/system/otelcol-contrib.service.The package installs:
/usr/bin/otelcol-contrib/lib/systemd/system/otelcol-contrib.service/etc/otelcol-contrib/config.yaml/etc/otelcol-contrib/otelcol-contrib.confotelcol-contrib under which the service runsVerify the install:
otelcol-contrib --versionExpected output:
otelcol-contrib version 0.110.0The default config shipped by the package is a placeholder that will not do anything useful. Stop the service before we replace it:
sudo systemctl stop otelcol-contribStep 4: Author config.yaml
Replace /etc/otelcol-contrib/config.yaml with a production-ready configuration. This single file defines every receiver, processor, exporter, and pipeline the collector will run.
sudo tee /etc/otelcol-contrib/config.yaml > /dev/null <<'EOF'============================================================================
OpenTelemetry Collector Configuration
Unified pipeline for metrics, logs, and traces
============================================================================
receivers:
# Native OTLP ingest from instrumented applications. # gRPC on 4317, HTTP on 4318. Bind to 0.0.0.0 so apps on other hosts # can push; keep this behind a firewall or private network. otlp: protocols: grpc: endpoint: 0.0.0.0:4317 http: endpoint: 0.0.0.0:4318
# Prometheus scrape receiver. The collector acts as a Prometheus # scraper for any /metrics endpoint you list here, then forwards # the samples through the metrics pipeline. prometheus: config: scrape_configs: - job_name: otel-collector-self scrape_interval: 30s static_configs: - targets: ['127.0.0.1:8888'] - job_name: node-exporter scrape_interval: 30s static_configs: - targets: ['127.0.0.1:9100']
# Filelog receiver tails log files and emits them as OTLP logs. # Perfect for legacy apps that cannot be instrumented with an SDK. filelog: include: - /var/log/syslog - /var/log/nginx/access.log - /var/log/nginx/error.log include_file_name: true include_file_path: true start_at: end operators: - type: regex_parser regex: '^(?P<timestamp>\S+\s+\S+\s+\S+)\s+(?P<host>\S+)\s+(?P<message>.*)$' timestamp: parse_from: attributes.timestamp layout: 'Jan _2 15:04:05'
processors:
# Enforce a hard RAM ceiling. When the collector approaches the # limit it back-pressures receivers and drops data rather than # OOM-killing the host. Always put this first in every pipeline. memory_limiter: check_interval: 1s limit_percentage: 80 spike_limit_percentage: 25
# Batch small payloads into efficient exports. Reduces request # count by 10-100x at the cost of a small added latency. batch: send_batch_size: 8192 send_batch_max_size: 10000 timeout: 5s
# Attach resource attributes that identify this collector. # Anything here shows up as labels on every metric, log, and trace. resource: attributes: - key: deployment.environment value: production action: upsert - key: host.name from_attribute: host.name action: insert - key: collector.instance value: otel-edge-01 action: upsert
exporters:
# Metrics go to Prometheus via remote_write. Works with Prometheus, # Grafana Mimir, Thanos Receive, VictoriaMetrics, and Cortex. prometheusremotewrite: endpoint: http://127.0.0.1:9090/api/v1/write tls: insecure: true resource_to_telemetry_conversion: enabled: true external_labels: source: otel-collector
# Logs go to Grafana Loki. The exporter maps OTLP log records # into Loki streams using resource attributes as labels. loki: endpoint: http://127.0.0.1:3100/loki/api/v1/push default_labels_enabled: exporter: true job: true
# Traces go to Grafana Tempo over OTLP/gRPC. Tempo speaks native # OTLP so there is no conversion step. otlp/tempo: endpoint: 127.0.0.1:4319 tls: insecure: true
# Debug exporter -- logs a sample of data to stdout. Enable only # while wiring up a new pipeline, then remove from pipelines. debug: verbosity: basic sampling_initial: 2 sampling_thereafter: 500
extensions:
# Health check endpoint used by systemd and load balancers. health_check: endpoint: 0.0.0.0:13133
# pprof endpoint for CPU/heap profiling. Localhost only. pprof: endpoint: 127.0.0.1:1777
# zPages for live inspection of pipelines. zpages: endpoint: 127.0.0.1:55679
service:
extensions: [health_check, pprof, zpages]
# The collector's own telemetry. Scraped by the prometheus # receiver above and exported alongside app metrics. telemetry: metrics: address: 127.0.0.1:8888 logs: level: info encoding: json
pipelines:
metrics: receivers: [otlp, prometheus] processors: [memory_limiter, resource, batch] exporters: [prometheusremotewrite]
logs: receivers: [otlp, filelog] processors: [memory_limiter, resource, batch] exporters: [loki]
traces: receivers: [otlp] processors: [memory_limiter, resource, batch] exporters: [otlp/tempo] EOF
A few notes on this configuration:
- The
memory_limiterprocessor is always first. If RAM pressure spikes, it refuses new data instead of letting the kernel OOM-kill the entire collector. - The
batchprocessor sits last in the processing chain so that resource attributes and filters are applied to individual records before they are packed. - The
resourceprocessor addsdeployment.environment,host.name, andcollector.instanceto every signal. In Grafana you can then split a dashboard by environment or host without touching your application code. - The
prometheusremotewriteendpoint points at a local Prometheus. If you instead run Mimir, Thanos Receive, or VictoriaMetrics, swap the URL -- the protocol is the same. - The
otlp/tempoexporter uses port 4319 because Tempo typically listens on4317for OTLP and we do not want to collide with the collector's own OTLP receiver. Adjust to match your Tempo deployment.
sudo otelcol-contrib validate --config=/etc/otelcol-contrib/config.yamlExpected output (no output on success):
If there is a syntax error the collector will print the offending line number. Fix and re-run until validation is clean.
Step 5: Validate and Start the Service
Enable and start the systemd service:
sudo systemctl daemon-reload
sudo systemctl enable otelcol-contrib
sudo systemctl start otelcol-contribCheck status:
sudo systemctl status otelcol-contribExpected output:
● otelcol-contrib.service - OpenTelemetry Collector Contrib
Loaded: loaded (/lib/systemd/system/otelcol-contrib.service; enabled; preset: enabled)
Active: active (running) since Thu 2026-04-16 10:00:00 UTC; 5s ago
Main PID: 4321 (otelcol-contrib)
Tasks: 9 (limit: 4580)
Memory: 145.2M
CPU: 0.823s
CGroup: /system.slice/otelcol-contrib.service
└─4321 /usr/bin/otelcol-contrib --config=/etc/otelcol-contrib/config.yamlConfirm the health check endpoint responds:
curl http://127.0.0.1:13133Expected output:
{"status":"Server available","upSince":"2026-04-16T10:00:00Z","uptime":"5.123s"}Confirm the collector's own Prometheus metrics are being exposed:
curl http://127.0.0.1:8888/metrics | head -20You should see metrics like otelcol_receiver_accepted_spans, otelcol_processor_batch_batch_send_size, and otelcol_exporter_sent_metric_points. These are the counters you will monitor in Grafana to confirm the pipeline is flowing.
Stream logs to watch data arrive:
sudo journalctl -u otelcol-contrib -fAt this point the collector is running, pipelines are live, and the OTLP endpoints on 4317/4318 are ready to accept data. The next step is instrumenting your applications.
Step 6: Instrument Your Applications
The whole point of OpenTelemetry is that your applications emit OTLP once and the collector handles everything downstream. Here are minimal SDK examples for the three most common languages. All of them assume the collector is reachable at http://localhost:4318 (OTLP/HTTP).
Node.js
Install the SDK packages:
npm install @opentelemetry/api \
@opentelemetry/sdk-node \
@opentelemetry/auto-instrumentations-node \
@opentelemetry/exporter-trace-otlp-http \
@opentelemetry/exporter-metrics-otlp-http \
@opentelemetry/exporter-logs-otlp-httpCreate tracing.js:
const { NodeSDK } = require('@opentelemetry/sdk-node'); const { getNodeAutoInstrumentations } = require('@opentelemetry/auto-instrumentations-node'); const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-http'); const { OTLPMetricExporter } = require('@opentelemetry/exporter-metrics-otlp-http'); const { PeriodicExportingMetricReader } = require('@opentelemetry/sdk-metrics'); const { Resource } = require('@opentelemetry/resources'); const { SemanticResourceAttributes } = require('@opentelemetry/semantic-conventions');const sdk = new NodeSDK({ resource: new Resource({ [SemanticResourceAttributes.SERVICE_NAME]: 'checkout-api', [SemanticResourceAttributes.SERVICE_VERSION]: '1.4.2', [SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: 'production', }), traceExporter: new OTLPTraceExporter({ url: 'http://localhost:4318/v1/traces', }), metricReader: new PeriodicExportingMetricReader({ exporter: new OTLPMetricExporter({ url: 'http://localhost:4318/v1/metrics', }), exportIntervalMillis: 15000, }), instrumentations: [getNodeAutoInstrumentations()], });
sdk.start();
Load it before your application code:
node --require ./tracing.js ./server.jsPython
Install the SDK:
pip install opentelemetry-distro opentelemetry-exporter-otlp
opentelemetry-bootstrap -a installRun any Python application with automatic instrumentation:
OTEL_SERVICE_NAME=checkout-api \
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4318 \
OTEL_RESOURCE_ATTRIBUTES="deployment.environment=production,service.version=1.4.2" \
opentelemetry-instrument python app.pyNo code changes required. The agent wraps Flask, Django, FastAPI, Celery, psycopg2, requests, and redis automatically.
Go
Install the modules:
go get go.opentelemetry.io/otel \
go.opentelemetry.io/otel/sdk \
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttpInitialize the tracer provider:
package mainimport ( "context" "log"
"go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp" "go.opentelemetry.io/otel/sdk/resource" sdktrace "go.opentelemetry.io/otel/sdk/trace" semconv "go.opentelemetry.io/otel/semconv/v1.21.0" )
func initTracer(ctx context.Context) (*sdktrace.TracerProvider, error) { exporter, err := otlptracehttp.New(ctx, otlptracehttp.WithEndpoint("localhost:4318"), otlptracehttp.WithInsecure(), ) if err != nil { return nil, err }
res, _ := resource.New(ctx, resource.WithAttributes( semconv.ServiceName("checkout-api"), semconv.ServiceVersion("1.4.2"), attribute.String("deployment.environment", "production"), ), )
tp := sdktrace.NewTracerProvider( sdktrace.WithBatcher(exporter), sdktrace.WithResource(res), ) otel.SetTracerProvider(tp) return tp, nil }
func main() { ctx := context.Background() tp, err := initTracer(ctx) if err != nil { log.Fatal(err) } defer tp.Shutdown(ctx)
tracer := otel.Tracer("checkout-api") _, span := tracer.Start(ctx, "process-order") defer span.End()
// ... your business logic ... }
Verifying the Data Flow
Once an instrumented service starts emitting, check the collector counters:
curl -s http://127.0.0.1:8888/metrics | grep -E '(accepted|sent)_(spans|metric|log)'You should see otelcol_receiver_accepted_spans_total climb and otelcol_exporter_sent_spans_total track it closely. If the receiver counter climbs but the exporter counter stays flat, your backend (Tempo, Loki, Prometheus) is unreachable -- check journalctl -u otelcol-contrib for export errors.
Step 7: Harden for Production
The default configuration is safe for a single VPS. For anything beyond a lab, apply these hardening steps.
Restrict OTLP to Private Networks
Edit config.yaml and bind OTLP to your private interface instead of 0.0.0.0:
receivers:
otlp:
protocols:
grpc:
endpoint: 10.0.0.5:4317
http:
endpoint: 10.0.0.5:4318Then close the public ports with UFW:
sudo ufw deny 4317
sudo ufw deny 4318
sudo ufw allow from 10.0.0.0/8 to any port 4317
sudo ufw allow from 10.0.0.0/8 to any port 4318
sudo ufw enableAuthenticate OTLP with a Bearer Token
For collectors exposed to the public internet, use the bearertokenauth extension:
extensions: bearertokenauth: scheme: Bearer token: ${env:OTLP_INGEST_TOKEN}receivers: otlp: protocols: grpc: endpoint: 0.0.0.0:4317 auth: authenticator: bearertokenauth http: endpoint: 0.0.0.0:4318 auth: authenticator: bearertokenauth
service: extensions: [health_check, pprof, zpages, bearertokenauth]
Pass the token via the EnvironmentFile:
echo 'OTLP_INGEST_TOKEN=$(openssl rand -hex 32)' | sudo tee -a /etc/otelcol-contrib/otelcol-contrib.conf
sudo systemctl restart otelcol-contribSDKs set the token with OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer <token>".
Tail Sampling for Traces
Full trace retention is expensive. Add the tail_sampling processor to keep only interesting traces:
processors:
tail_sampling:
decision_wait: 10s
num_traces: 50000
policies:
- name: errors-always
type: status_code
status_code: { status_codes: [ERROR] }
- name: slow-requests
type: latency
latency: { threshold_ms: 500 }
- name: baseline-sample
type: probabilistic
probabilistic: { sampling_percentage: 5 }Insert it into the traces pipeline between resource and batch. You now keep every error and every slow request, plus a 5% baseline of healthy traffic.
Rotate Credentials with systemd LoadCredential
Store backend tokens outside the config file using systemd's credential system. Create /etc/otelcol-contrib/override.conf:
[Service]
LoadCredential=loki_token:/etc/otelcol-contrib/secrets/loki_token
Environment="LOKI_TOKEN_FILE=%d/loki_token"Reference the file in the exporter block with ${file:/run/credentials/otelcol-contrib.service/loki_token}.
Monitor the Collector Itself
A silent collector is worse than a loud one. The collector's own metrics at 127.0.0.1:8888 include:
otelcol_receiver_refused_*-- back-pressure eventsotelcol_exporter_send_failed_*-- backend unreachableotelcol_processor_dropped_*-- data discarded due to memory limitsotelcol_process_runtime_heap_alloc_bytes-- heap usage
refused or send_failed metrics. If the collector silently drops data, every other dashboard in your stack starts lying.Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
failed to load config: cannot unmarshal | YAML indentation error in config.yaml | Run otelcol-contrib validate --config=/etc/otelcol-contrib/config.yaml and fix the reported line. |
| Receivers accept data but exporters show zero sent | Backend URL wrong, TLS mismatch, or auth rejection | Check journalctl -u otelcol-contrib for permanent error or context deadline exceeded. Test the backend with curl directly. |
| Memory climbs until OOM | memory_limiter missing from pipelines, or limit_percentage too high | Put memory_limiter first in every pipeline. Lower limit_percentage to 70 on 2 GB hosts. |
Prometheus remote write: out of order sample | Multiple collectors sending the same series with overlapping timestamps | Use external_labels to distinguish collectors, or deduplicate upstream with Thanos. |
| Traces never appear in Tempo | Wrong Tempo port, or Tempo OTLP receiver disabled | Tempo accepts OTLP on 4317 by default. Your collector exporter must target a different port if they share a host. Check the Tempo config. |
filelog receiver: permission denied | Collector user cannot read the target log files | Add the otelcol-contrib user to adm group: sudo usermod -aG adm otelcol-contrib && sudo systemctl restart otelcol-contrib. |
| High CPU on the collector | No batching, or synchronous exporters under load | Confirm batch is in every pipeline. Tune send_batch_size up to 16384 for high-throughput pipelines. |
SDK logs OTLP export failed: connection refused | Collector not listening on the expected interface | Confirm endpoint: 0.0.0.0:4317 in config.yaml. Check sudo ss -lnpt</td><td>grep 4317. |
Useful Debugging Commands
Stream collector logs:
sudo journalctl -u otelcol-contrib -fDump the effective config:
sudo otelcol-contrib components --config=/etc/otelcol-contrib/config.yamlInspect live pipelines via zPages:
curl -s http://127.0.0.1:55679/debug/pipelinezProfile CPU hotspots:
curl -s http://127.0.0.1:1777/debug/pprof/profile?seconds=30 > cpu.pprof
go tool pprof cpu.pprofFAQ
What is the difference between otelcol and otelcol-contrib?
otelcol is the core distribution maintained by the OpenTelemetry Collector project. It contains only a minimal set of components that the core maintainers ship and support directly: OTLP receivers and exporters, a batch processor, a few utility extensions. otelcol-contrib bundles everything contributed by the wider community -- Loki, Tempo, Jaeger, Zipkin, Prometheus Remote Write, Elasticsearch, Kafka, S3, Datadog, New Relic, Splunk, and dozens more. For any real-world unified pipeline across logs, metrics, and traces, otelcol-contrib is what you want. The binary is larger (around 200 MB unpacked versus 50 MB for core) but the runtime memory footprint is roughly the same because only the components you enable in config.yaml are instantiated.
Do I need a separate agent and gateway collector?
For a single-VPS setup a single collector instance acts as both agent and gateway. When you scale to multiple application servers, the standard pattern is a two-tier deployment. An agent collector runs on each application host, often as a small in-process exporter or a sidecar. It handles local log tailing, hostmetrics, and the short-lived connection from the app SDK. Agents forward OTLP to a central gateway collector that handles batching, tail sampling, buffering during backend outages, and final export. The gateway is also where you apply cost controls like drop rules and sampling decisions. This architecture is why OTLP has native support for both push and pull semantics -- agents push to gateways, and gateways push to backends.
How much RAM does the OpenTelemetry Collector use?
A typical collector configured with OTLP, Prometheus scrape, and filelog receivers plus Prometheus Remote Write, Loki, and OTLP exporters uses 150 to 400 MB of RAM at moderate throughput (a few thousand spans per second, tens of thousands of metric points per minute, low-MB/s log ingest). The memory_limiter processor enforces a hard cap based on limit_percentage, which back-pressures receivers or drops data rather than allowing the host to OOM. A CloudCore Starter with 4 GB RAM handles the collector plus a modest Prometheus or Loki co-located on the same host. If you plan to run all three backends (Prometheus, Loki, Tempo) on one machine, step up to 8 GB or split them across hosts.
Can I send traces to Jaeger or Zipkin instead of Tempo?
Yes. otelcol-contrib ships jaeger and zipkin exporters alongside otlp. Swap the otlp/tempo exporter block in config.yaml for:
exporters:
jaeger:
endpoint: jaeger-collector:14250
tls:
insecure: true
# or
zipkin:
endpoint: http://zipkin:9411/api/v2/spansUpdate the traces pipeline to reference the new exporter name. Nothing else changes -- receivers, processors, and instrumentation code stay identical because OTLP is vendor-neutral. This is exactly the kind of swap that self-hosting a telemetry pipeline makes trivial. You can even dual-write: list both otlp/tempo and jaeger in the traces pipeline during a migration and cut over when you are confident in the new backend.
Is OpenTelemetry a drop-in replacement for Prometheus?
No. OpenTelemetry is a telemetry collection and routing framework. It does not store time-series data, run PromQL queries, evaluate alerts, or render dashboards. Prometheus (or Mimir, Thanos, VictoriaMetrics, Cortex) remains the storage and query engine for metrics. The collector sits in front of Prometheus as an ingestion layer: it scrapes /metrics endpoints via the prometheus receiver, receives OTLP metrics from SDKs, and forwards the combined stream to Prometheus via prometheusremotewrite. You keep PromQL, alerts, and Grafana dashboards exactly as before. What you gain is a unified instrumentation API that emits metrics, logs, and traces through one SDK with one resource attribute schema, so your service.name="checkout" label works identically across all three views.
How do I handle collector high availability?
Run two or more collectors behind a TCP load balancer (HAProxy, Nginx stream, or a cloud LB). SDKs support multiple OTLP endpoints with automatic failover. For metrics, use the external_labels field in prometheusremotewrite so each collector adds a unique label and Prometheus can deduplicate. For traces, deploy a gateway tier with the loadbalancing exporter so spans from the same trace always land on the same gateway, which is required for correct tail sampling. Loki and Tempo both handle idempotent writes from multiple collectors correctly by default.
Next Steps
With the OpenTelemetry Collector running and your applications instrumented, build out the rest of the stack:
- Install Prometheus on Ubuntu -- Stand up the metrics backend that receives the
prometheusremotewriteoutput from this collector and serves PromQL queries to Grafana. - Install Loki on Ubuntu -- Deploy Grafana Loki to receive logs from the collector's Loki exporter. Query logs alongside metrics with the same label schema.
- Install Tempo on Ubuntu -- Add Grafana Tempo as the OTLP trace backend. Jump from a slow HTTP endpoint in a Grafana dashboard straight into the full distributed trace.
- Add Grafana -- Connect Prometheus, Loki, and Tempo as datasources in a single Grafana instance. The OpenTelemetry resource attributes on every signal make cross-linking trivial.
- Enable tail sampling -- Retain every error and every slow trace at full fidelity while downsampling healthy traffic to 5%. See the hardening section above.
- Add hostmetrics -- The
hostmetricsreceiver inotelcol-contribcollects CPU, memory, disk, network, and process metrics without any external agent. Replace node-exporter with a native collector receiver. - Explore the full collector documentation at opentelemetry.io/docs/collector/ for advanced topics like filter processors, transform processors, and the OpAMP management protocol.
Skip the Manual Install -- Launch a Ready Observability VPS>
The CloudCore Starter plan gives you a clean Ubuntu 24.04 environment with the resources needed to run the OpenTelemetry Collector alongside Prometheus, Loki, and Tempo. Deploy in 60 seconds and start shipping unified telemetry the same afternoon.>
- 2 vCPU cores, 4 GB RAM, 50 GB NVMe
- Unmetered bandwidth
- Full root access
- Ubuntu 24.04 LTS image>
Launch Your CloudCore Starter VPS