From 233120ffe9f78055e702241875e77429fea006fc Mon Sep 17 00:00:00 2001 From: JeremyFunk Date: Mon, 19 Oct 2026 19:57:57 +0200 Subject: [PATCH 1/6] feat(landing): remote server monitoring guide --- apps/landing/src/pages/guides/index.astro | 7 + .../guides/remote-server-monitoring.astro | 669 ++++++++++++++++++ apps/landing/src/pages/llms.txt.ts | 1 + 3 files changed, 677 insertions(+) create mode 100644 apps/landing/src/pages/guides/remote-server-monitoring.astro diff --git a/apps/landing/src/pages/guides/index.astro b/apps/landing/src/pages/guides/index.astro index 7ca330c958..2f84661f12 100644 --- a/apps/landing/src/pages/guides/index.astro +++ b/apps/landing/src/pages/guides/index.astro @@ -56,6 +56,13 @@ const coreGuides = [ description: "The four metric types with examples, the handful worth watching, and how to collect and chart them.", read: "7 min", }, + { + href: "/guides/remote-server-monitoring", + label: "Infrastructure", + title: "Remote server monitoring with OpenTelemetry", + description: "Agent vs agentless collection, the signals to watch, a Collector systemd setup with host metrics, logs and HTTP checks, and alerts for servers that go quiet.", + read: "11 min", + }, ]; const investigationGuides = [ diff --git a/apps/landing/src/pages/guides/remote-server-monitoring.astro b/apps/landing/src/pages/guides/remote-server-monitoring.astro new file mode 100644 index 0000000000..69b0f3dacf --- /dev/null +++ b/apps/landing/src/pages/guides/remote-server-monitoring.astro @@ -0,0 +1,669 @@ +--- +import Layout from "../../layouts/Layout.astro"; +import Nav from "../../components/Nav.astro"; +import Footer from "../../components/Footer.astro"; +import BottomCTA from "../../components/BottomCTA.astro"; +import Breadcrumb from "../../components/Breadcrumb.astro"; +import FAQ from "../../components/FAQ.astro"; +import SeoHead from "../../components/SeoHead.astro"; +import TableOfContents from "../../components/TableOfContents.astro"; +import { CONTRIBUTORS } from "../../lib/changelog-meta"; + +const pageTitle = "Remote server monitoring with OpenTelemetry: agents, checks and alerts"; +const pageDescription = + "Remote server monitoring for cloud VMs, colo and branch servers: agent vs agentless, what to watch, secure push, and an OpenTelemetry Collector setup."; +const published = "2026-10-19"; +const updatedLabel = "October 19, 2026"; +const author = CONTRIBUTORS.jeremyfunk; +const authorUrl = `https://github.com/${author.github}`; + +const methods = [ + { + method: "Agent on the server", + how: "A process on the server reads /proc, log files and the journal, and sends the data out. The OpenTelemetry Collector, Prometheus node_exporter and vendor agents work this way.", + fits: "Linux and Windows servers you control", + cost: "Software to install, configure and upgrade on every server. Uses some CPU and memory of its own.", + }, + { + method: "SNMP polling", + how: "A central poller queries the SNMP daemon on each device over UDP 161 and looks values up in MIBs.", + fits: "Switches, firewalls, UPS units and appliances that cannot run an agent", + cost: "Coarse metrics and no logs. SNMP v1 and v2c send the community string in plain text, so use v3.", + }, + { + method: "SSH polling", + how: "A central host logs in on a schedule, runs commands such as df or uptime, and parses the output.", + fits: "A handful of servers, or one metric no agent collects", + cost: "The poller holds credentials for every server, and each server accepts inbound SSH from it. Output parsing breaks across distributions.", + }, + { + method: "WMI or WinRM polling", + how: "A central host queries Windows performance counters remotely.", + fits: "Windows servers in a domain", + cost: "Inbound RPC (135 plus a dynamic port range) or WinRM (5985 and 5986), and an account with remote query rights.", + }, + { + method: "HTTP and ICMP checks", + how: "A prober outside the server sends a request or a ping and records whether it answered and how fast.", + fits: "Reachability, public endpoints, TLS certificates", + cost: "Sees up or down and latency. Nothing about disk, memory or the process that is about to fail.", + }, +]; + +const signals = [ + { signal: "Heartbeat", source: "The agent reporting at all", metric: "Any host metric arriving", alert: "No data from the host for 5 minutes" }, + { signal: "Reachability", source: "A check from another machine", metric: "httpcheck.status", alert: "No 2xx response for 2 minutes" }, + { signal: "CPU", source: "/proc/stat", metric: "system.cpu.utilization, system.cpu.load_average.15m", alert: "Above 90% for 15 minutes, or load above the core count" }, + { signal: "Memory", source: "/proc/meminfo", metric: "system.memory.utilization", alert: "Above 90% used, or the OOM killer in the logs" }, + { signal: "Disk space", source: "statfs on each mount", metric: "system.filesystem.utilization", alert: "Any mountpoint above 85 to 90%" }, + { signal: "Network", source: "/proc/net/dev", metric: "system.network.io, system.network.errors", alert: "Errors or drops rising, or traffic at link capacity" }, + { signal: "Processes", source: "/proc", metric: "system.processes.count", alert: "Blocked processes piling up" }, + { signal: "Logs", source: "/var/log, the journal", metric: "filelog receiver", alert: "Disk I/O errors, OOM kills, repeated SSH auth failures" }, + { signal: "Certificates", source: "The TLS handshake", metric: "httpcheck.tls.cert_remaining", alert: "Fewer than 14 days left" }, +]; + +const sshCommands = `uptime # load averages for 1, 5 and 15 minutes, time since boot +top -o %MEM # live CPU and memory per process (or htop) +vmstat 5 5 # run queue, swap in and out, CPU iowait, every 5 seconds +iostat -xz 5 3 # per-device utilization and wait times (sysstat package) +free -h # memory and swap in use +df -h && df -i # space and inodes per filesystem +ss -tulpn # listening sockets and the process behind each +ss -s # socket counts by state +journalctl -p err -b # errors since the last boot +journalctl -u nginx --since "1 hour ago"`; + +const sshLoop = `for h in web-01 web-02 db-01; do + ssh "$h" 'echo "== $(hostname)"; uptime; df -h / | tail -1' +done`; + +const installCommands = `curl -LO https://github.com/open-telemetry/opentelemetry-collector-releases/releases/download/v0.163.1/otelcol-contrib_0.163.1_linux_amd64.deb +sudo dpkg -i otelcol-contrib_0.163.1_linux_amd64.deb`; + +const serverConfig = `receivers: + hostmetrics: + collection_interval: 30s + scrapers: + cpu: + metrics: + system.cpu.utilization: + enabled: true + load: {} + memory: + metrics: + system.memory.utilization: + enabled: true + filesystem: + metrics: + system.filesystem.utilization: + enabled: true + network: {} + processes: {} + filelog: + include: + - /var/log/syslog + - /var/log/auth.log + start_at: end + +processors: + resourcedetection: + detectors: [env, system] + resource/logs: + attributes: + - key: service.name + value: system-logs + action: upsert + batch: {} + +exporters: + otlphttp/maple: + endpoint: https://ingest.maple.dev + compression: gzip + headers: + x-maple-ingest-key: \${env:MAPLE_INGEST_KEY} + +service: + pipelines: + metrics: + receivers: [hostmetrics] + processors: [resourcedetection, batch] + exporters: [otlphttp/maple] + logs: + receivers: [filelog] + processors: [resourcedetection, resource/logs, batch] + exporters: [otlphttp/maple]`; + +const envFile = `OTELCOL_OPTIONS="--config=/etc/otelcol-contrib/config.yaml" +MAPLE_INGEST_KEY=maple_sk_... +OTEL_RESOURCE_ATTRIBUTES=deployment.environment.name=production`; + +const serviceCommands = `sudo chmod 600 /etc/otelcol-contrib/otelcol-contrib.conf +sudo usermod -aG adm otelcol-contrib +sudo systemctl enable otelcol-contrib +sudo systemctl restart otelcol-contrib +journalctl -u otelcol-contrib -f`; + +const checkerConfig = `receivers: + http_check: + collection_interval: 60s + metrics: + httpcheck.tls.cert_remaining: + enabled: true + targets: + - method: GET + endpoints: + - https://web-01.example.com/health + - https://web-02.example.com/health + - https://api.example.com/health + +processors: + resource/uptime: + attributes: + - key: service.name + value: uptime-checks + action: upsert + batch: {} + +exporters: + otlphttp/maple: + endpoint: https://ingest.maple.dev + compression: gzip + headers: + x-maple-ingest-key: \${env:MAPLE_INGEST_KEY} + +service: + pipelines: + metrics/uptime: + receivers: [http_check] + processors: [resource/uptime, batch] + exporters: [otlphttp/maple]`; + +const envTemplate = `OTELCOL_OPTIONS="--config=/etc/otelcol-contrib/config.yaml" +MAPLE_INGEST_KEY={{ maple_ingest_key }} +OTEL_RESOURCE_ATTRIBUTES=host.name={{ inventory_hostname }},deployment.environment.name={{ env_name }}`; + +const heartbeatSql = `SELECT $__timeGroup(TimeUnix) AS bucket, count() AS value +FROM metrics_gauge +WHERE $__orgFilter AND $__timeFilter(TimeUnix) + AND MetricName = 'system.memory.utilization' + AND ResourceAttributes['host.name'] = 'web-01' +GROUP BY bucket`; + +const diskSql = `SELECT $__timeGroup(TimeUnix) AS bucket, + ResourceAttributes['host.name'] AS group, + max(Value) AS value +FROM metrics_gauge +WHERE $__orgFilter AND $__timeFilter(TimeUnix) + AND MetricName = 'system.filesystem.utilization' + AND Attributes['type'] NOT IN ('squashfs', 'tmpfs') +GROUP BY bucket, group`; + +const toc = [ + ["definition", "Definition"], + ["methods", "Agent vs agentless"], + ["push-pull", "Push or pull"], + ["signals", "Signals to watch"], + ["security", "Security"], + ["ssh", "Checks over SSH"], + ["collector", "Collector setup"], + ["hosts", "Hosts in Maple"], + ["http-checks", "HTTP checks"], + ["fleet", "Many servers"], + ["alerting", "Alerts"], +] as const; +const tocHeadings = toc.map(([slug, text]) => ({ depth: 2, slug, text })); + +const faqs = [ + { + question: "What is remote server monitoring?", + answer: + "Remote server monitoring is collecting health data from servers you do not sit next to, such as cloud VMs, machines in a colocation rack or a server in a branch office, and alerting when something goes wrong. It covers availability, CPU, memory, disk, network, processes and logs, collected either by an agent on each server or by a central system that polls or probes them.", + }, + { + question: "What is the difference between agent-based and agentless server monitoring?", + answer: + "Agent-based monitoring runs a small program on each server that reads local metrics and logs and sends them out. Agentless monitoring collects from a central system over SNMP, SSH, WMI or with HTTP and ping checks. Agents see far more, including logs and per-mount disk usage, and need only outbound network access. Agentless methods suit devices that cannot run an agent, and they need inbound ports and credentials on every target.", + }, + { + question: "How can I monitor a remote Linux server for free?", + answer: + "Install the OpenTelemetry Collector Contrib, which is open source under the Apache 2.0 license, as a systemd service with the hostmetrics and filelog receivers, and send its output to any OTLP backend. For a one-off look, SSH in and run uptime, vmstat, df -h, ss and journalctl. A fully self-hosted alternative is Prometheus node_exporter with Prometheus and Grafana, which you run and store yourself.", + }, + { + question: "How do I get alerted when a remote server goes down?", + answer: + "Use two alerts. A heartbeat rule fires when the server's agent stops sending data for a few minutes, which catches a crashed host, a full network outage and a stopped agent. An HTTP check from a different machine fires when the service on the server stops answering. Run the check outside the server it watches, or it goes down with it.", + }, + { + question: "Is it secure to send server metrics over the internet?", + answer: + "Yes, when the agent pushes over HTTPS to the backend with a send-only key. The server opens an outbound connection, so it needs no inbound monitoring port, and TLS protects the data in transit. Keep the key in a root-only file, run the agent as an unprivileged user, and rotate the key if a server is compromised. Plain SNMP v1 and v2c and unencrypted syslog are the parts to avoid on the open internet.", + }, +]; + +const siteUrl = Astro.site?.toString().replace(/\/$/, "") ?? "https://maple.dev"; +const articleJsonLd = { + "@context": "https://schema.org", + "@type": "TechArticle", + headline: "Remote server monitoring with OpenTelemetry", + description: pageDescription, + datePublished: published, + dateModified: published, + author: { "@type": "Person", name: author.name, url: authorUrl }, + publisher: { + "@type": "Organization", + name: "Maple", + logo: { "@type": "ImageObject", url: `${siteUrl}/logo192.png` }, + }, + mainEntityOfPage: `${siteUrl}/guides/remote-server-monitoring`, +}; +--- + + + +