Skip to main contentSkip to navigation
[email protected]
Client AreaSupport
Hosting Mammoth
HostingMammothYour Data, Our Responsibility
Home
Solutions
Hosting Services
Store
Pricing
About
Blog
API
Contact

Stay Ahead of the Curve

Get the latest insights on cybersecurity, AI innovations, and enterprise data solutions delivered to your inbox.

Hosting Mammoth
HostingMammothEnterprise Solutions

Enterprise-grade data solutions. Hosting, recovery, cybersecurity, and AI-powered services for businesses worldwide.

[email protected]
Sun - Fri, 9:00am - 5:00pm

Services

  • Cloud Hosting
  • Data Recovery
  • Cybersecurity
  • Legal Support
  • MSP Services
  • Web Development
  • AI Services
  • Free Server Migration

Hosting

  • VPS Hosting (NVMe SSD)
  • VDS Hosting (NVMe)
  • Storage VPS (High SSD)
  • GPU Servers
  • Managed Services
  • Cloud Firewall
  • Load Balancer
  • One-Click Apps
  • n8n Hosting
  • Object Storage
  • FAQ

Company

  • Store
  • Pricing
  • About Us
  • Locations
  • Blog
  • Testimonials
  • Contact
  • Affiliate Program
  • White-Label
  • Terms of Service
  • Privacy Policy
  • Browser Cookies
  • SLA

Support

  • Client Area
  • Submit Ticket
  • Knowledge Base
  • Server Status
  • API Documentation

© 2026 Hosting Mammoth. All rights reserved.

Knowledge Base
Getting StartedAccount ManagementVPS HostingGPU ServersStorage VPSCloud FirewallLoad BalancerServer ManagementBilling & PaymentsSupport & TicketsAffiliate ProgramReseller ProgramMarketplace & Appsn8n HostingManaged ServicesServer MigrationAPI & DevelopersSecurityTroubleshootingGlossaryInstall Guides
  1. Home
  2. /
  3. Support
  4. /
  5. Install Guides
  6. /
  7. How To Install Opentelemetry Collector Ubuntu
GUIDEInstall Guides

How to Install OpenTelemetry Collector on Ubuntu 24.04 VPS: Unified Telemetry Pipeline

25 min read

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?
  • Why Self-Host a Telemetry Pipeline?
  • Prerequisites
  • Step 1: Update System Packages
  • Step 2: Add the OpenTelemetry apt Repository
  • Step 3: Install otelcol-contrib
  • Step 4: Author config.yaml
  • Step 5: Validate and Start the Service
  • Step 6: Instrument Your Applications
  • Step 7: Harden for Production
  • Troubleshooting
  • FAQ
  • Next Steps
  • 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), and tail_sampling (keeps only interesting traces).
    • Exporters send data out. prometheusremotewrite pushes metrics to any Prometheus-compatible store (Prometheus itself, Mimir, Thanos, VictoriaMetrics). loki pushes logs to Grafana Loki. otlp sends traces to Grafana Tempo or any OTLP-compatible trace backend. Additional exporters cover Elasticsearch, Kafka, S3, Datadog, New Relic, Honeycomb, and Splunk.
    These components are wired into pipelines inside the 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, and redaction processors 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.
    The collector is not a replacement for your metrics database or log store. It is the pipe between your code and those stores, and owning the pipe is what makes the rest of your observability stack tractable.

    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:

    bash
    ssh root@your-server-ip

    Step 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.

    bash
    sudo apt update && sudo apt upgrade -y
    sudo apt install -y curl gpg apt-transport-https ca-certificates

    Expected output (abbreviated):

    text
    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:

    bash
    sudo reboot

    Step 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:

    bash
    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.gpg

    Register the apt source:

    bash
    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/null

    Refresh the package index:

    bash
    sudo apt update

    Expected output:

    text
    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... Done
    Alternative: direct .deb download. If you prefer not to trust an external apt source, download the latest .deb from the GitHub releases page and install with sudo dpkg -i otelcol-contrib_*.deb. Both methods yield the same binary and systemd unit.

    Step 3: Install otelcol-contrib

    Install the contrib distribution:

    bash
    sudo apt install -y otelcol-contrib

    Expected output:

    text
    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:

  • The binary at /usr/bin/otelcol-contrib
  • A systemd unit at /lib/systemd/system/otelcol-contrib.service
  • A default config at /etc/otelcol-contrib/config.yaml
  • An EnvironmentFile at /etc/otelcol-contrib/otelcol-contrib.conf
  • A system user otelcol-contrib under which the service runs
  • Verify the install:

    bash
    otelcol-contrib --version

    Expected output:

    text
    otelcol-contrib version 0.110.0

    The default config shipped by the package is a placeholder that will not do anything useful. Stop the service before we replace it:

    bash
    sudo systemctl stop otelcol-contrib

    Step 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.

    bash
    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_limiter processor is always first. If RAM pressure spikes, it refuses new data instead of letting the kernel OOM-kill the entire collector.
    • The batch processor sits last in the processing chain so that resource attributes and filters are applied to individual records before they are packed.
    • The resource processor adds deployment.environment, host.name, and collector.instance to every signal. In Grafana you can then split a dashboard by environment or host without touching your application code.
    • The prometheusremotewrite endpoint points at a local Prometheus. If you instead run Mimir, Thanos Receive, or VictoriaMetrics, swap the URL -- the protocol is the same.
    • The otlp/tempo exporter uses port 4319 because Tempo typically listens on 4317 for OTLP and we do not want to collide with the collector's own OTLP receiver. Adjust to match your Tempo deployment.
    Validate the configuration before starting the service:

    bash
    sudo otelcol-contrib validate --config=/etc/otelcol-contrib/config.yaml

    Expected output (no output on success):

    text

    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:

    bash
    sudo systemctl daemon-reload
    sudo systemctl enable otelcol-contrib
    sudo systemctl start otelcol-contrib

    Check status:

    bash
    sudo systemctl status otelcol-contrib

    Expected output:

    text
    ● 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.yaml

    Confirm the health check endpoint responds:

    bash
    curl http://127.0.0.1:13133

    Expected output:

    json
    {"status":"Server available","upSince":"2026-04-16T10:00:00Z","uptime":"5.123s"}

    Confirm the collector's own Prometheus metrics are being exposed:

    bash
    curl http://127.0.0.1:8888/metrics | head -20

    You 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:

    bash
    sudo journalctl -u otelcol-contrib -f

    At 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:

    bash
    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-http

    Create tracing.js:

    javascript
    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:

    bash
    node --require ./tracing.js ./server.js

    Python

    Install the SDK:

    bash
    pip install opentelemetry-distro opentelemetry-exporter-otlp
    opentelemetry-bootstrap -a install

    Run any Python application with automatic instrumentation:

    bash
    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.py

    No code changes required. The agent wraps Flask, Django, FastAPI, Celery, psycopg2, requests, and redis automatically.

    Go

    Install the modules:

    bash
    go get go.opentelemetry.io/otel \
      go.opentelemetry.io/otel/sdk \
      go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp

    Initialize the tracer provider:

    go
    package main

    import ( "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:

    bash
    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:

    yaml
    receivers:
      otlp:
        protocols:
          grpc:
            endpoint: 10.0.0.5:4317
          http:
            endpoint: 10.0.0.5:4318

    Then close the public ports with UFW:

    bash
    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 enable

    Authenticate OTLP with a Bearer Token

    For collectors exposed to the public internet, use the bearertokenauth extension:

    yaml
    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:

    bash
    echo 'OTLP_INGEST_TOKEN=$(openssl rand -hex 32)' | sudo tee -a /etc/otelcol-contrib/otelcol-contrib.conf
    sudo systemctl restart otelcol-contrib

    SDKs 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:

    yaml
    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:

    ini
    [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 events
    • otelcol_exporter_send_failed_* -- backend unreachable
    • otelcol_processor_dropped_* -- data discarded due to memory limits
    • otelcol_process_runtime_heap_alloc_bytes -- heap usage
    Set Prometheus alerts on any non-zero rate of refused or send_failed metrics. If the collector silently drops data, every other dashboard in your stack starts lying.

    Troubleshooting

    ProblemCauseSolution
    failed to load config: cannot unmarshalYAML indentation error in config.yamlRun otelcol-contrib validate --config=/etc/otelcol-contrib/config.yaml and fix the reported line.
    Receivers accept data but exporters show zero sentBackend URL wrong, TLS mismatch, or auth rejectionCheck journalctl -u otelcol-contrib for permanent error or context deadline exceeded. Test the backend with curl directly.
    Memory climbs until OOMmemory_limiter missing from pipelines, or limit_percentage too highPut memory_limiter first in every pipeline. Lower limit_percentage to 70 on 2 GB hosts.
    Prometheus remote write: out of order sampleMultiple collectors sending the same series with overlapping timestampsUse external_labels to distinguish collectors, or deduplicate upstream with Thanos.
    Traces never appear in TempoWrong Tempo port, or Tempo OTLP receiver disabledTempo 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 deniedCollector user cannot read the target log filesAdd the otelcol-contrib user to adm group: sudo usermod -aG adm otelcol-contrib && sudo systemctl restart otelcol-contrib.
    High CPU on the collectorNo batching, or synchronous exporters under loadConfirm batch is in every pipeline. Tune send_batch_size up to 16384 for high-throughput pipelines.
    SDK logs OTLP export failed: connection refusedCollector not listening on the expected interfaceConfirm endpoint: 0.0.0.0:4317 in config.yaml. Check sudo ss -lnpt</td><td>grep 4317.

    Useful Debugging Commands

    Stream collector logs:

    bash
    sudo journalctl -u otelcol-contrib -f

    Dump the effective config:

    bash
    sudo otelcol-contrib components --config=/etc/otelcol-contrib/config.yaml

    Inspect live pipelines via zPages:

    bash
    curl -s http://127.0.0.1:55679/debug/pipelinez

    Profile CPU hotspots:

    bash
    curl -s http://127.0.0.1:1777/debug/pprof/profile?seconds=30 > cpu.pprof
    go tool pprof cpu.pprof

    FAQ

    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:

    yaml
    exporters:
      jaeger:
        endpoint: jaeger-collector:14250
        tls:
          insecure: true
      # or
      zipkin:
        endpoint: http://zipkin:9411/api/v2/spans

    Update 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 prometheusremotewrite output 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 hostmetrics receiver in otelcol-contrib collects 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

    Was this article helpful?

    ← Back to Install GuidesBrowse all categories →

    Still have questions?

    Contact Support →Submit a Ticket