
You don’t need to install a full monitoring stack on every box. In 2026, the clean pattern is a single local agent that collects host metrics and logs, adds a few useful labels, and forwards everything to the backend you choose. This post walks through a Linux VPS monitoring agent setup using the OpenTelemetry Collector as that agent, with a configuration you can roll out and maintain without surprises.
Assume you’re starting with one Ubuntu server (and you’ll add more later). It runs Nginx plus a couple of systemd services. You want: (1) CPU/memory/disk/network metrics, (2) journald and Nginx access/error logs, (3) consistent host identity labels, and (4) a rollback that takes minutes.
On a VPS, this works best when you control the whole path—systemd units, firewall policy, and log locations. A HostMyCode VPS gives you full root access for exactly that, which makes an agent-based approach straightforward.
Why OpenTelemetry Collector works as a VPS “monitoring agent”
Think of the OpenTelemetry Collector as an on-host router for telemetry. It scrapes host metrics, reads journald, tails files, adds attributes (environment, role, service namespace), batches efficiently, and exports to one or more destinations.
- Portable: send to Prometheus remote_write, OTLP, Loki-compatible endpoints, or a vendor’s OTLP gateway.
- Resource-aware: batching and memory limiting keep the agent stable on small instances.
- Auditable: one config file, one systemd unit, predictable ports and paths.
If you already run Prometheus + Grafana, you might prefer our deeper guide on low-noise Prometheus and Grafana monitoring. Here we stay focused on the per-host agent and how to keep it tidy.
Prerequisites (what you need before you touch config)
- A VPS running Ubuntu 24.04 LTS or newer (systemd + journald). The steps translate to Debian 12/13 with minor path changes.
- Root or sudo access.
- An upstream telemetry destination:
- Option A: an OTLP endpoint (HTTP or gRPC) you control, e.g., a central Collector.
- Option B: Prometheus remote_write + a log backend (Grafana Loki, or any compatible receiver).
- Nginx installed (for the log examples). If you don’t use Nginx, swap in your app’s log path.
Security note: treat telemetry endpoints like production APIs. Use TLS, restrict outbound destinations, and don’t ship secrets from logs. If you’re still tightening baseline hardening, start with our Linux VPS hardening checklist.
Architecture: one agent, two pipelines (metrics + logs)
On the VPS, the Collector will:
- Scrape host metrics via
hostmetrics. - Read system logs from journald.
- Tail Nginx log files directly (access + error).
- Add consistent attributes:
service.namespace,deployment.environment, and a statichost.role. - Export:
- Metrics to an OTLP endpoint (or remote_write if you prefer).
- Logs to an OTLP endpoint (or Loki-compatible endpoint).
Everything stays explicit: no auto-discovery guesses, no mystery listeners, no “what file is it tailing?” moments.
Step-by-step: Linux VPS monitoring agent setup with OpenTelemetry Collector
Follow this as a checklist. You’ll finish with a running agent, basic verification, and an easy rollback path.
1) Create a dedicated system user and directories
sudo useradd --system --no-create-home --shell /usr/sbin/nologin otelcol
sudo mkdir -p /etc/otelcol /var/lib/otelcol /var/log/otelcol
sudo chown -R otelcol:otelcol /var/lib/otelcol /var/log/otelcol
sudo chmod 0750 /var/lib/otelcol /var/log/otelcol
Expected output: no output on success.
2) Install the OpenTelemetry Collector Contrib build
In 2026, the otelcol-contrib build is usually the practical choice for VPS agents. It includes the journald and filelog receivers, plus the exporters you’ll typically need.
Install using the official package repository for your distro, or pin a vetted version from your internal artifact store. If you want a quick check on your server’s patching posture before adding new services, see automated patching with safe reboots.
If you’re using an upstream apt repo, the package name is commonly otelcol-contrib. After install, verify the binary:
otelcol-contrib --version
Expected output (example):
otelcol-contrib version 0.123.0
If your version differs, that’s fine. The key is staying on a current 2026 release line and keeping it updated.
3) Decide your export target and credentials
This post assumes you’re exporting to a central endpoint at:
https://otel-gw.ops.example.net:4318(OTLP/HTTP)
You’ll also use an API token as an HTTP header. If you already run mTLS internally, use that instead. If you’re building internal mTLS, this Step CA guide is relevant: private mTLS with Step CA.
4) Write the Collector config (metrics + journald + Nginx logs)
Create /etc/otelcol/agent.yaml:
sudo tee /etc/otelcol/agent.yaml >/dev/null <<'YAML'
receivers:
hostmetrics:
collection_interval: 15s
scrapers:
cpu:
memory:
disk:
filesystem:
network:
load:
processes:
journald:
directory: /var/log/journal
units:
- nginx.service
- ssh.service
priority: info
filelog/nginx_access:
include:
- /var/log/nginx/access.log
start_at: end
operators:
- type: regex_parser
regex: '^(?P<remote_addr>\S+) \S+ \S+ \[(?P<time_local>[^\]]+)\] "(?P<method>\S+) (?P<path>\S+) (?P<proto>\S+)" (?P<status>\d+) (?P<bytes>\d+) "(?P<referer>[^"]*)" "(?P<ua>[^"]*)"'
- type: add
field: attributes.log.source
value: nginx_access
filelog/nginx_error:
include:
- /var/log/nginx/error.log
start_at: end
operators:
- type: add
field: attributes.log.source
value: nginx_error
processors:
memory_limiter:
check_interval: 1s
limit_mib: 256
spike_limit_mib: 64
batch:
send_batch_size: 8192
timeout: 5s
attributes/static_tags:
actions:
- key: deployment.environment
action: upsert
value: prod
- key: service.namespace
action: upsert
value: edge-web
- key: host.role
action: upsert
value: reverse-proxy
exporters:
otlphttp:
endpoint: https://otel-gw.ops.example.net:4318
headers:
Authorization: "Bearer OTEL_DEMO_TOKEN_REPLACE_ME"
compression: gzip
timeout: 10s
service:
telemetry:
logs:
level: info
pipelines:
metrics:
receivers: [hostmetrics]
processors: [memory_limiter, attributes/static_tags, batch]
exporters: [otlphttp]
logs:
receivers: [journald, filelog/nginx_access, filelog/nginx_error]
processors: [memory_limiter, attributes/static_tags, batch]
exporters: [otlphttp]
YAML
sudo chmod 0640 /etc/otelcol/agent.yaml
sudo chown root:otelcol /etc/otelcol/agent.yaml
What this config does: it deliberately limits journald to two units. That keeps the firehose under control. Add your own services later (for example, api.service or worker.service) once you’ve confirmed volume and value.
5) Store the token safely (systemd drop-in) rather than in the YAML
Don’t bake tokens into config files you’ll copy around. Put them in a systemd environment file and lock down permissions.
sudo install -m 0640 -o root -g otelcol /dev/null /etc/otelcol/agent.env
sudo bash -c 'cat > /etc/otelcol/agent.env <<EOF
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer OTEL_DEMO_TOKEN_REPLACE_ME
EOF'
Now update the exporter in agent.yaml to read headers from env instead of hardcoding. Replace the headers: section with:
headers: {}
And add an extensions block? You can, but for OTLP headers most teams keep it simple and rotate via env files because it’s easy to automate.
For clarity, we’ll keep the YAML header here and rotate it by editing the env file and reloading the service in the next step. (Many teams standardize on env rotation because it’s trivial to script.)
6) Create a systemd unit for the agent
Create /etc/systemd/system/otelcol-agent.service:
sudo tee /etc/systemd/system/otelcol-agent.service >/dev/null <<'UNIT'
[Unit]
Description=OpenTelemetry Collector (VPS Agent)
After=network-online.target
Wants=network-online.target
[Service]
User=otelcol
Group=otelcol
EnvironmentFile=/etc/otelcol/agent.env
ExecStart=/usr/bin/otelcol-contrib --config=/etc/otelcol/agent.yaml
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/otelcol /var/log/otelcol
# Allow reading system logs and nginx logs
SupplementaryGroups=systemd-journal adm
[Install]
WantedBy=multi-user.target
UNIT
Reload systemd and start the agent:
sudo systemctl daemon-reload
sudo systemctl enable --now otelcol-agent
sudo systemctl status otelcol-agent --no-pager
Expected output (example):
● otelcol-agent.service - OpenTelemetry Collector (VPS Agent)
Loaded: loaded (/etc/systemd/system/otelcol-agent.service; enabled)
Active: active (running)
7) Verify the agent is actually collecting data (local checks)
Start by checking for permissions issues:
sudo journalctl -u otelcol-agent -n 80 --no-pager
What you want to see: clean startup logs, pipeline initialization, and no “permission denied” errors for journald or Nginx log files.
Next, force a request through Nginx and make sure the agent stays healthy:
curl -I http://127.0.0.1/ 2>/dev/null | head -n 1
Expected output (example):
HTTP/1.1 200 OK
That request should append a line to /var/log/nginx/access.log, which the Collector should be tailing.
8) Verify exports (upstream checks)
The exact verification depends on your destination, but you can confirm the basics without clicking through dashboards.
- From the agent logs: look for steady exports, not endless retries or backoff loops.
- From your gateway/collector: confirm you see the host with
service.namespace=edge-web.
On the VPS, this grep quickly surfaces repeated failures:
sudo journalctl -u otelcol-agent --since "10 min ago" --no-pager | egrep -i "error|failed|retry" | tail -n 30
If you see TLS errors, double-check the endpoint and CA chain. If you see 401 or 403, rotate the token in /etc/otelcol/agent.env and restart:
sudo systemctl restart otelcol-agent
9) Put the agent behind sensible network policy (outbound allowlist)
Telemetry is outbound traffic. If your VPS egress policy is “allow all,” tighten it. With nftables, a common approach is an output allowlist to your OTLP endpoint’s IP(s) and port 4318.
If you’re planning a firewall migration, don’t wing it during a maintenance window. Use a predictable plan like our iptables to nftables cutover guide and schedule it.
Common pitfalls (and how to spot them fast)
- Journald permission denied: your service user isn’t in
systemd-journal. Fix withSupplementaryGroups=systemd-journaland restart. - Nginx logs not readable: on Ubuntu, Nginx logs are often group
adm. Addadmto supplementary groups (as shown) or adjust ACLs. - High CPU on small VPS: bump
collection_intervalto 30s and keep batching on. If you don’t need per-process stats, drop theprocessesscraper. - Log storms: don’t tail everything. Limit journald units. Skip debug logs unless you’re actively investigating.
- Missing host identity: keep a stable attribute like
host.role, and consider injecting a unique node name via systemdEnvironment=HOST_ID=...and an attributes processor.
Rollback plan (safe, quick, and boring)
A monitoring agent should be easy to remove under pressure. This rollback keeps the server stable and predictable.
-
Stop and disable the service:
sudo systemctl disable --now otelcol-agent -
Remove the unit and reload systemd:
sudo rm -f /etc/systemd/system/otelcol-agent.service sudo systemctl daemon-reload -
Remove config and env files (optional):
sudo rm -rf /etc/otelcol sudo rm -rf /var/lib/otelcol /var/log/otelcol -
Uninstall the package: use your distro’s package manager.
If the agent is causing resource pressure, stopping it is usually enough. You can uninstall later, after things calm down.
Operational tips for 2026: keep signal high and costs predictable
Once the agent is stable, most improvements come from being selective about what you ship.
- Ship fewer logs: start with error logs and security-relevant units, then add more only if they earn their keep.
- Tag consistently: treat tags like an API contract. If
deployment.environmentandservice.namespacearen’t consistent, your dashboards will break in quiet, annoying ways. - Batch aggressively: tiny batches waste CPU and network overhead. The included batch settings are a sensible baseline.
- Plan for backups anyway: telemetry won’t save you from accidental deletes. For backup/restore basics, see restic + S3 backup strategy.
Where HostMyCode fits (and what to choose)
This agent pattern works best when you control systemd, networking, and disk paths. That’s why it maps cleanly to VPS hosting.
- If you want full control and you’re comfortable operating Linux: start with a HostMyCode VPS and keep the setup lean.
- If you’d rather have the OS maintenance and baseline hardening handled for you: use managed VPS hosting and focus on your applications and dashboards.
Next steps (practical upgrades after the first server)
- Add alerts with clear ownership: alert on disk fill rate, memory pressure, and Nginx 5xx spikes. Avoid “CPU > 80%” alerts unless you’ve tied them to a real symptom.
- Expand journald units: add your app services one at a time, then tune volume.
- Introduce trace collection: if you run an API, start with 10–20% sampling and increase during incident windows.
- Harden outbound access: allowlist the telemetry gateway and rotate tokens. If you use mTLS, automate cert rotation.
If you’re standardizing monitoring across a few servers, start with a VPS where you control systemd, firewall rules, and log paths end-to-end. A HostMyCode VPS is a clean base for the OpenTelemetry agent approach. If you want help keeping the OS layer consistent while you focus on telemetry and reliability, managed VPS hosting is a good fit.
FAQ
Do I need Prometheus on every VPS for this setup?
No. With an agent-based model, the VPS runs only the Collector. Your backend can be centralized (Prometheus, Grafana Cloud, VictoriaMetrics, an OTLP gateway, etc.).
Is journald better than tailing files?
For systemd services, journald is usually cleaner because you can filter by unit and priority. For Nginx, file tailing is still common because access/error logs are already structured around files.
What’s the minimum VPS size for an OTel agent?
For a single web server, a small VPS is typically fine if you keep intervals sane (15–30s) and avoid high-volume debug logs. If you see sustained CPU burn, reduce scrapers and increase batching.
How do I test the config before restarting the service?
Run the Collector in the foreground with the same config and watch for parse errors:
sudo -u otelcol /usr/bin/otelcol-contrib --config=/etc/otelcol/agent.yaml
Can I run multiple exporters (e.g., OTLP + Loki) at the same time?
Yes. Add another exporter and include it in the relevant pipeline. Start with one destination, verify it’s stable, then split exports if you have a concrete reason.
Summary
A good monitoring agent fades into the background. With OpenTelemetry Collector, you can collect host metrics and the logs you actually care about on a Linux VPS, tag them consistently, and export them to a backend you control—without running a full monitoring stack on every server. If you’re building this on fresh infrastructure, a HostMyCode VPS gives you the cleanest path to a predictable, repeatable setup.