Skip to content

Set up Prometheus, Node Exporter, and Grafana on Debian or Ubuntu ​

Step by step guide to build a monitoring stack on a Debian/Ubuntu server. Versions used below are the latest stable as of this writing (August 2026): Prometheus 3.13.1, Node Exporter 1.12.1, Grafana 13.1.1. Check the release pages before you run this in case newer versions are out.

Three upstream references are worth keeping open alongside this: the Prometheus configuration reference, the list of Node Exporter collectors and what each one costs you, and Grafana's own Debian install docs.

Architecture ​

+----------------+   scrapes :9100   +----------------+
|  Node Exporter | <---------------- |   Prometheus   |
|   :9100        |                   |   :9090        |
+----------------+                   +--------+-------+
                                              |
                                              | PromQL queries :9090
                                              v
                                     +----------------+
                                     |    Grafana     |
                                     |   :3000        |
                                     +----------------+
                                              |
                                              v
                                        Browser / user

Prerequisites ​

  • A Debian or Ubuntu server with sudo access
  • curl and tar installed
  • SSH access for a tunnel. Keep ports 9090, 9100 and 3000 closed to the public internet; this guide runs all three services on loopback.

Part 1: Install Prometheus ​

Step 1: Create the prometheus system user ​

bash
sudo useradd --no-create-home --shell /bin/false prometheus

Step 2: Create directories ​

bash
sudo mkdir -p /etc/prometheus /var/lib/prometheus

Step 3: Download Prometheus ​

bash
cd /tmp
curl -LO https://github.com/prometheus/prometheus/releases/download/v3.13.1/prometheus-3.13.1.linux-amd64.tar.gz
tar -xvf prometheus-3.13.1.linux-amd64.tar.gz

Step 4: Install the binaries ​

bash
sudo cp prometheus-3.13.1.linux-amd64/prometheus /usr/local/bin/
sudo cp prometheus-3.13.1.linux-amd64/promtool /usr/local/bin/
sudo chown prometheus:prometheus /usr/local/bin/prometheus /usr/local/bin/promtool

Step 5: Install console templates and set ownership ​

bash
sudo cp -r prometheus-3.13.1.linux-amd64/consoles /etc/prometheus
sudo cp -r prometheus-3.13.1.linux-amd64/console_libraries /etc/prometheus
sudo chown -R prometheus:prometheus /etc/prometheus /var/lib/prometheus

Step 6: Create the config file ​

bash
sudo tee /etc/prometheus/prometheus.yml > /dev/null <<EOF
global:
  scrape_interval: 10s

scrape_configs:
  - job_name: 'prometheus'
    static_configs:
      - targets: ['localhost:9090']
EOF

Node Exporter's scrape job gets added in Part 3, once it's installed.

Step 7: Create the systemd service ​

bash
sudo tee /etc/systemd/system/prometheus.service > /dev/null <<EOF
[Unit]
Description=Prometheus
Wants=network-online.target
After=network-online.target

[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/usr/local/bin/prometheus \\
  --config.file=/etc/prometheus/prometheus.yml \\
  --storage.tsdb.path=/var/lib/prometheus \\
  --web.listen-address=127.0.0.1:9090 \\
  --web.console.templates=/etc/prometheus/consoles \\
  --web.console.libraries=/etc/prometheus/console_libraries \\
  --storage.tsdb.retention.time=365d
ExecReload=/bin/kill -HUP \$MAINPID

[Install]
WantedBy=multi-user.target
EOF

ExecReload is what makes systemctl reload prometheus work in Part 3. Without it systemd refuses the reload with "Job type reload is not applicable", and your only option is a full restart. Note the backslash in \$MAINPID: this heredoc is unquoted so the shell would otherwise expand the variable to nothing while writing the file.

Step 8: Start and enable Prometheus ​

bash
sudo systemctl daemon-reload
sudo systemctl enable --now prometheus

Step 9: Verify ​

bash
sudo systemctl status prometheus
curl -s http://localhost:9090/-/healthy

From your own computer, open an SSH tunnel and leave it running:

bash
ssh -N -L 9090:127.0.0.1:9090 -L 3000:127.0.0.1:3000 user@server

Replace user@server with your SSH destination. Open http://localhost:9090 on your computer to confirm the UI loads.

Clean up the Prometheus download ​

bash
rm -rf /tmp/prometheus-3.13.1.linux-amd64*

Part 2: Install Node Exporter ​

Step 1: Create the node_exporter system user ​

bash
sudo useradd --no-create-home --shell /bin/false node_exporter

Step 2: Download Node Exporter ​

bash
cd /tmp
curl -LO https://github.com/prometheus/node_exporter/releases/download/v1.12.1/node_exporter-1.12.1.linux-amd64.tar.gz
tar -xvf node_exporter-1.12.1.linux-amd64.tar.gz

Step 3: Install the binary ​

bash
sudo cp node_exporter-1.12.1.linux-amd64/node_exporter /usr/local/bin/
sudo chown node_exporter:node_exporter /usr/local/bin/node_exporter

Step 4: Create the systemd service ​

bash
sudo tee /etc/systemd/system/node_exporter.service > /dev/null <<EOF
[Unit]
Description=Node Exporter
Wants=network-online.target
After=network-online.target

[Service]
User=node_exporter
Group=node_exporter
Type=simple
ExecStart=/usr/local/bin/node_exporter --web.listen-address=127.0.0.1:9100

[Install]
WantedBy=multi-user.target
EOF

Step 5: Start and enable Node Exporter ​

bash
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter

Step 6: Verify ​

bash
curl -s http://localhost:9100/metrics | head

Clean up the Node Exporter download ​

bash
rm -rf /tmp/node_exporter-1.12.1.linux-amd64*

Part 3: Point Prometheus at Node Exporter ​

Step 1: Add the scrape job ​

bash
sudo vim /etc/prometheus/prometheus.yml

Add under scrape_configs:

yaml
  - job_name: "node_exporter"
    static_configs:
      - targets: ['localhost:9100']

Step 2: Validate the config ​

bash
promtool check config /etc/prometheus/prometheus.yml

Step 3: Reload Prometheus ​

bash
sudo systemctl reload prometheus

Step 4: Verify the target is up ​

Through the SSH tunnel, open http://localhost:9090/targets, both prometheus and node_exporter jobs should show State = UP.

Part 4: Install Grafana ​

Official method: APT repository. apt-get update refreshes package lists; apt-get install --only-upgrade grafana installs an available upgrade. Source: https://grafana.com/docs/grafana/latest/setup-grafana/installation/debian/

Step 1: Install prerequisites ​

bash
sudo apt-get install -y apt-transport-https wget gnupg

Step 2: Import the GPG key ​

bash
sudo mkdir -p /etc/apt/keyrings
sudo wget -O /etc/apt/keyrings/grafana.asc https://apt.grafana.com/gpg-full.key
sudo chmod 644 /etc/apt/keyrings/grafana.asc

Step 3: Add the stable repository ​

bash
echo "deb [signed-by=/etc/apt/keyrings/grafana.asc] https://apt.grafana.com stable main" | sudo tee -a /etc/apt/sources.list.d/grafana.list

Step 4: Update package list ​

bash
sudo apt-get update

Step 5: Install Grafana OSS ​

bash
sudo apt-get install -y grafana

Step 6: Start and enable Grafana ​

bash
sudo mkdir -p /etc/systemd/system/grafana-server.service.d
printf '[Service]\nEnvironment=GF_SERVER_HTTP_ADDR=127.0.0.1\n' | sudo tee /etc/systemd/system/grafana-server.service.d/listen.conf
sudo systemctl daemon-reload
sudo systemctl enable grafana-server
sudo systemctl restart grafana-server

Step 7: Verify ​

bash
sudo systemctl status grafana-server

Through the SSH tunnel, open http://localhost:3000. Default login is admin / admin, you'll be asked to change it on first sign-in.

Part 5: Optional Grafana config (custom domain / HTTPS) ​

The SSH tunnel is enough for private access. For a public dashboard, point grafana.example.com at the server and terminate TLS at nginx. Grafana stays on loopback HTTP; do not enable HTTPS on its upstream port.

Step 1: Edit grafana.ini ​

bash
sudo vim /etc/grafana/grafana.ini
ini
[server]
http_addr = 127.0.0.1
http_port = 3000
domain = grafana.example.com
root_url = https://grafana.example.com/
enforce_domain = true
protocol = http

Step 2: Restart Grafana ​

bash
sudo systemctl restart grafana-server

Step 3 (optional): Nginx reverse proxy ​

nginx
map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

server {
    listen 80;
    listen [::]:80;

    server_name grafana.example.com;

    location / {
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://127.0.0.1:3000;
    }

    location /api/live {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass http://127.0.0.1:3000;
    }
}

Install nginx and Certbot, then save the configuration above as /etc/nginx/sites-available/grafana.example.com. Replace the example domain throughout:

bash
sudo apt-get install -y nginx certbot python3-certbot-nginx
sudo ln -s /etc/nginx/sites-available/grafana.example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d grafana.example.com
sudo certbot renew --dry-run

Allow ports 80 and 443 for nginx, not 3000. Certbot installs the TLS configuration and renewal timer. Keep Grafana authentication enabled and change the initial password through the SSH tunnel before exposing the domain.

Part 6: Connect Grafana to Prometheus ​

Step 1: Add the data source ​

In Grafana: Connections > Data sources > Add data source > Prometheus.

Set URL to:

http://localhost:9090

Click Save & test, you should see "Successfully queried the Prometheus API."

Step 2: Import the Node Exporter dashboard ​

Dashboards > New > Import, enter dashboard ID:

1860

Select your Prometheus data source when prompted, click Import. This is the community "Node Exporter Full" dashboard and it covers CPU, memory, disk, and network out of the box.

Service management cheat sheet ​

service          start                 status                enable
---------------  --------------------  --------------------  -------------------------------
prometheus       systemctl start       systemctl status       systemctl enable prometheus
node_exporter    systemctl start       systemctl status       systemctl enable node_exporter
grafana-server   systemctl start       systemctl status       systemctl enable grafana-server

Reload configs without downtime:

bash
sudo systemctl reload prometheus
sudo systemctl restart grafana-server

Where to go next ​

One server is where this starts, not where it ends. The next two steps are usually alerting (Alertmanager, so a full disk pages you at 3am instead of surprising you at 9) and a second exporter for whatever the box actually runs. If that box is a website, self-hosting WordPress with Docker and Redis is the other half of the same setup.

Monitoring at larger scale is a good chunk of my work: Grafana and Prometheus observability, including a benchmarking rig that dispatched ten million requests in fifteen minutes. Happy to talk it through.

Uninstall ​

Remove Prometheus ​

bash
sudo systemctl disable --now prometheus
sudo rm -f /usr/local/bin/prometheus /usr/local/bin/promtool
sudo rm -rf /etc/prometheus /var/lib/prometheus /etc/systemd/system/prometheus.service
sudo userdel prometheus
sudo systemctl daemon-reload

Remove Node Exporter ​

bash
sudo systemctl disable --now node_exporter
sudo rm -f /usr/local/bin/node_exporter /etc/systemd/system/node_exporter.service
sudo userdel node_exporter
sudo systemctl daemon-reload

Remove Grafana ​

bash
sudo systemctl stop grafana-server
sudo apt-get remove -y grafana
sudo rm -i /etc/apt/sources.list.d/grafana.list