GuidesGuide

Monitor a Web Hosting Server with Prometheus and Grafana (Ubuntu 24.04)

A hosting server almost always fails in predictable ways: the disk fills up, a service stops, a certificate expires, a backup quietly stops running. To find out before your customers do, you need four pieces: Prometheus to collect the data, node_exporter for CPU, memory and disks, blackbox_exporter to check the sites from the outside, and Grafana to see it all. On Ubuntu 24.04 they install in fifteen minutes. This guide has the commands, a ready-made configuration and the alert rules worth turning on first.

How the stack fits together

  • Prometheus polls HTTP endpoints (the exporters) at regular intervals, stores the numbers they return and evaluates alert rules.
  • node_exporter exposes system metrics: CPU, memory, disks, network, load.
  • blackbox_exporter requests your sites like a visitor would: is it up, which HTTP status, when does the certificate expire.
  • Alertmanager receives alerts from Prometheus and sends them by email, Telegram, Slack and more.
  • Grafana draws the graphs.

The most important advice comes before any command: run Prometheus on a different server from the one it watches. If the web server hangs, a Prometheus running on it hangs too and never tells you. A VPS costing a few euros a month is enough to watch dozens of servers.

1. Install Prometheus and node_exporter

Ubuntu 24.04 ships the packages in its own repositories:

sudo apt update
sudo apt install prometheus prometheus-node-exporter prometheus-alertmanager prometheus-blackbox-exporter

Prometheus listens on port 9090, node_exporter on 9100, Alertmanager on 9093 and blackbox_exporter on 9115. The main configuration file is /etc/prometheus/prometheus.yml.

On every web server, install only node_exporter and open port 9100 to the monitoring server alone:

sudo apt install prometheus-node-exporter
sudo ufw allow from 203.0.113.50 to any port 9100 proto tcp

Never leave 9100 open to the world: the metrics reveal disk names, versions and load.

2. Configure Prometheus

Replace /etc/prometheus/prometheus.yml on the monitoring server with something like this (the addresses are examples):

global:
  scrape_interval: 30s
  evaluation_interval: 30s

rule_files:
  - /etc/prometheus/rules/*.yml

alerting:
  alertmanagers:
    - static_configs:
        - targets: ["localhost:9093"]

scrape_configs:
  - job_name: node
    static_configs:
      - targets: ["web1.example.com:9100", "web2.example.com:9100"]

  - job_name: sites
    metrics_path: /probe
    params:
      module: [http_2xx]
    static_configs:
      - targets:
          - https://www.customer-one.com
          - https://www.customer-two.com
    relabel_configs:
      - source_labels: [__address__]
        target_label: __param_target
      - source_labels: [__param_target]
        target_label: instance
      - target_label: __address__
        replacement: localhost:9115

The relabel_configs block is the part that confuses everyone: it tells Prometheus to call blackbox_exporter (localhost:9115) and pass it the site to check as a parameter.

Check the syntax and restart:

sudo mkdir -p /etc/prometheus/rules
promtool check config /etc/prometheus/prometheus.yml
sudo systemctl restart prometheus

Open http://server-ip:9090/targets (better through an SSH tunnel: ssh -L 9090:localhost:9090 server) and check that every target is UP.

3. The alert rules that actually matter

Too many alerts are worse than none: after a week of noise you stop reading them. Start with these five, in /etc/prometheus/rules/hosting.yml:

groups:
  - name: hosting
    rules:
      - alert: ServerDown
        expr: up{job="node"} == 0
        for: 3m
        annotations:
          summary: "{{ $labels.instance }} has not answered for 3 minutes"

      - alert: DiskAlmostFull
        expr: node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes < 0.10
        for: 10m
        annotations:
          summary: "{{ $labels.instance }}: less than 10% free on {{ $labels.mountpoint }}"

      - alert: SiteDown
        expr: probe_success{job="sites"} == 0
        for: 2m
        annotations:
          summary: "{{ $labels.instance }} is not answering"

      - alert: CertificateExpiring
        expr: probe_ssl_earliest_cert_expiry - time() < 14 * 86400
        for: 1h
        annotations:
          summary: "The certificate of {{ $labels.instance }} expires in less than 14 days"

      - alert: OutOfMemory
        expr: node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes < 0.05
        for: 10m
        annotations:
          summary: "{{ $labels.instance }}: available memory below 5%"

The for field prevents false alarms: the condition has to stay true for that long before the alert fires. Check with promtool check rules /etc/prometheus/rules/hosting.yml and reload Prometheus.

Why 14 days for certificates? Certbot renews Let's Encrypt certificates about 30 days before they expire: if the old certificate is still there with 14 days left, automatic renewal is broken and you still have two weeks to fix it.

4. Send the alerts by email

Alertmanager reads /etc/prometheus/alertmanager.yml. A minimal email setup:

global:
  smtp_smarthost: smtp.example.com:587
  smtp_from: alerts@example.com
  smtp_auth_username: alerts@example.com
  smtp_auth_password: "the-password"

route:
  receiver: email
  group_wait: 1m
  repeat_interval: 12h

receivers:
  - name: email
    email_configs:
      - to: you@example.com

repeat_interval: 12h repeats the alert while the problem is open, but no more than twice a day. Restart with sudo systemctl restart prometheus-alertmanager.

5. Grafana for the graphs

Grafana is not in Ubuntu's repositories: install it from the official one.

sudo apt install -y apt-transport-https wget gnupg
sudo mkdir -p /etc/apt/keyrings
wget -q -O - https://apt.grafana.com/gpg.key | gpg --dearmor | sudo tee /etc/apt/keyrings/grafana.gpg > /dev/null
echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update && sudo apt install grafana
sudo systemctl enable --now grafana-server

Grafana listens on port 3000 (initial login admin / admin, it asks you to change it right away). In Connections › Data sources add Prometheus at http://localhost:9090, then in Dashboards › Import enter ID 1860 ("Node Exporter Full"): you instantly get CPU, memory, disk and network graphs for every server.

6. What node_exporter cannot see

node_exporter knows the system, not the sites. It doesn't know which sites you host, who owns them, how much space each one uses, when the last backup ran or which PHP version they use. For that you have to write scripts that produce metrics (with node_exporter's textfile collector) or use a panel that exposes them itself.

Monitoring with Koapanel

Koapanel exposes hosting metrics in the Prometheus format at /api/v1/metrics on every panel, since version 0.27. Nothing to install on the web server: an administrator API key, created in API keys and saved in a file on the monitoring server, is all it takes.

  - job_name: koapanel
    scheme: https
    metrics_path: /api/v1/metrics
    scrape_interval: 60s
    authorization:
      credentials_file: /etc/prometheus/koapanel.key
    static_configs:
      - targets: ["panel.example.com:8443"]

On top of CPU, memory, disks and services you get:

Metric What it tells you
koapanel_sites online and suspended sites
koapanel_site_info every site with owner, PHP version and whether it runs WordPress
koapanel_site_disk_bytes disk space used by each site
koapanel_site_ssl_expiry_timestamp_seconds certificate expiry of each site
koapanel_site_last_backup_timestamp_seconds last successful backup of each site
koapanel_service_up nginx, MariaDB, PHP-FPM, fail2ban and SSH running or stopped
koapanel_update_available a new panel version is out

The most useful rule node_exporter can't give you is the one on backups:

      - alert: BackupMissing
        expr: time() - koapanel_site_last_backup_timestamp_seconds > 2 * 86400
        for: 1h
        annotations:
          summary: "{{ $labels.domain }}: no successful backup for two days"

If you'd rather not run Prometheus, the panel still emails you by itself about expiring certificates whose renewal failed, failed backups and disks over 90%: see System emails. The full Prometheus setup is in the manual, section API and webhooks.

FAQ

Does Prometheus use a lot of resources?

Not for a few dozen servers and sites: a VPS with 1-2 GB of RAM is enough. Disk space depends on how many days you keep (15 by default): change it with --storage.tsdb.retention.time in /etc/default/prometheus.

Prometheus or an external uptime service?

Both. The external service tells you a site is down even when your monitoring server is down too; Prometheus tells you why, and warns you before it happens (disk, memory, certificates).

Can I use Zabbix or Netdata instead of Prometheus?

Yes. Both read the Prometheus format, so they also read Koapanel's /api/v1/metrics.

Should Prometheus be reachable from the Internet?

No. Keep 9090, 9093 and 3000 closed and reach them through an SSH tunnel or behind a reverse proxy with authentication.

Try Koapanel

Look around the public demo or install it on a fresh Ubuntu 24.04, free for up to 3 sites:

curl -fsSL https://get.koapanel.app | sudo bash

Every integration, from WHMCS to the API, is on the Integrations page.

Try Koapanel on your server

One command on Ubuntu 24.04, free up to 3 sites. Are you a provider or an agency? Let's talk wholesale pricing and migrations.

More guides