...

How to Set Up VPS Monitoring with Prometheus, Grafana, and Node Exporter

Martin Klein

Reading time 1 minute

Prometheus, Grafana, and Node Exporter let you build a complete VPS monitoring system without relying on external SaaS services. Node Exporter provides server system metrics, Prometheus collects and stores them at regular intervals, and Grafana displays the data in easy-to-understand dashboards.

In this guide, we will set up monitoring for CPU load, RAM usage, disk usage, the file system, and network traffic. We will also import a ready-made Node Exporter dashboard, configure retention for Prometheus, and create alerts for a full disk, high load, and Node Exporter unavailability.

For production use, it is better to run the monitoring system and store metrics on a server separate from the monitored target. This helps ensure access to historical data in the event of a serious VPS failure or during incident investigations. For this demonstration, however, we will deploy an all-in-one setup on a single VPS to showcase the technologies and their capabilities.

The system will allow you to:

  • Monitor the VPS status in real time;
  • Analyze CPU, RAM, disk, and network utilization;
  • Store metric history in Prometheus;
  • Identify server resource issues;
  • Monitor Node Exporter availability.

Prometheus and Node Exporter will not be directly accessible from the internet. Only Grafana will be exposed externally, and we will publish it through Nginx over HTTPS. As a result, we will have centralized and secure VPS monitoring with metric history, a dashboard, and basic alerts.

How VPS Monitoring Works with Prometheus and Grafana

For VPS monitoring, it is useful to divide the system into three components: Node Exporter collects system metrics from the server, Prometheus periodically retrieves and stores these metrics, and Grafana is used to visualize the data and build dashboards.

This setup lets you not only view the server’s current state but also analyze how the load changes over time. For example, you can identify when memory usage began to increase, when free disk space ran out, or how much network activity changed after an application update.

What Node Exporter does

Node Exporter is a metrics exporter for Linux systems. It runs on a server and provides Prometheus with information about the operating system and hardware status.

After startup, Node Exporter collects data on the CPU, memory, file systems, disk devices, network interfaces, system uptime, and other parameters. By default, metrics are available via the /metrics HTTP endpoint.

For example: http://127.0.0.1:9100/metrics

In response, Node Exporter returns a set of metrics in Prometheus format. These can include:

node_cpu_seconds_total

node_memory_MemAvailable_bytes

node_filesystem_avail_bytes

node_network_receive_bytes_total

node_network_transmit_bytes_total

Node Exporter itself does not store historical data. Its role is to provide the server’s current state at the time of the request.

How Prometheus Collects and Stores Metrics

Prometheus uses a pull model: it periodically scrapes the specified targets at a configured interval and retrieves metrics from them.

In our case, Prometheus will scrape Node Exporter at 127.0.0.1:9100

The address is specified in the prometheus.yml configuration file:

scrape_configs:

– job_name: “node”

static_configs:

– targets:

– “127.0.0.1:9100”

Prometheus periodically reads these values and stores them in its built-in time series database (TSDB). This makes it possible to work not only with current values, but also with historical data.

Queries are written in PromQL. For example, you can retrieve the amount of available memory with: node_memory_MemAvailable_bytes

Average CPU load can be calculated based on changes in the node_cpu_seconds_total counters over a specified time range.

The data retention period is configured using retention parameters. This allows you to limit the amount of disk space Prometheus uses for metric history.

How Grafana Visualizes Data

Grafana does not collect system metrics on its own. Instead, Prometheus is configured in Grafana as a data source, after which Grafana runs PromQL queries and displays the results in graphs, tables, and other panels.

A single dashboard can display the following at the same time:

  • CPU load;
  • RAM usage;
  • Free and used space;
  • Disk device load;
  • Network traffic;
  • Load average;
  • VPS uptime.

Prebuilt Grafana dashboards are available for Node Exporter. This means you do not need to manually create dozens of panels and PromQL queries: simply import a suitable template and link it to our Prometheus data source.

VPS metrics to monitor

In this guide, we will focus on metrics that let you quickly assess the state of a VPS.

For the CPU, we will monitor utilization percentage and Load Average. Sustained high load may indicate insufficient compute resources, overly resource-intensive processes, or issues in the application.

For RAM, the key metrics are total RAM, available memory, and the percentage of memory in use. If RAM is consistently scarce, the system may start using swap heavily, which can noticeably reduce performance.

For the disk subsystem, we will monitor free space in file systems and key disk I/O metrics. This will help detect both a partition filling up and excessive disk activity in advance.

Network metrics will show the volume of inbound and outbound traffic by interface. They help identify sudden load spikes, unusual activity, and changes in traffic patterns.

In addition, Prometheus will monitor the availability of Node Exporter itself. If the target stops responding, this will become a separate alert condition.

Preparing the VPS for monitoring

Before installing the components, we will update the system, check the available resources, and determine in advance which network ports the monitoring system will need.

Updating the system and checking server resources

This example uses a VPS running Ubuntu. First, update the package index and the installed packages:

sudo apt update

sudo apt upgrade -y

After the update, you can check the system version: lsb_release -a

Check the number of CPU cores: nproc

Amount of RAM: free -h

And the available disk space: df -h

Prometheus stores metric history locally, so it is important to take available disk space into account when choosing the retention period. The more targets, metrics, and retention time you have, the faster the Prometheus data directory will grow.

For a single VPS with Node Exporter, the requirements are relatively modest, but disk space still should not be left unmonitored.

Which ports do Prometheus, Grafana, and Node Exporter use

By default, the components use the following TCP ports:

ComponentPortPurpose
Node Exporter9100VPS system metrics
Prometheus9090web interface, API, and PromQL queries
Grafana3000Grafana web interface
Nginx80HTTP and TLS certificate issuance
Nginx443HTTPS access to Grafana

After configuration, the internal setup will look roughly like this:

Prometheus will collect metrics from Node Exporter locally, and Grafana will connect to the local Prometheus instance.

The only ports that need to be open to the internet are SSH for administration and HTTP/HTTPS for web access through Nginx.

Why monitoring ports should not be left publicly accessible

Node Exporter provides fairly detailed information about the server, including file systems, network interfaces, load, memory, and other system characteristics.

Prometheus, in turn, provides a web interface and an API for working with the collected metrics. These interfaces should not be exposed to the entire internet unless there is a clear need to do so.

In our setup, Node Exporter and Prometheus will be used only locally. They will be available on the VPS to other components of the monitoring system, but they will not be published as external services.

Grafana also runs on its own port, 3000, by default, but we will not leave direct external access to it enabled. At the end of the setup, we will place Grafana behind Nginx and access it over HTTPS.

This leaves only the standard port 443 exposed externally, while the monitoring service interfaces remain hidden from direct external access. For additional security, access to this port can be restricted to the administrator’s external IP address or routed through a tunnel.

Installing and Configuring Node Exporter

The first component we will deploy is Node Exporter. After that, Prometheus will be able to start collecting actual system metrics from the VPS.

Creating a Dedicated Node Exporter User

Node Exporter does not require root access for standard system metrics collection, so we will run it under a dedicated system account.

Create a user without a home directory and without the ability to log in interactively:

sudo useradd \

–no-create-home \

–shell /usr/sbin/nologin \

node_exporter

Verify the account: id node_exporter

This approach separates the monitoring service from the VPS administrative account and reduces the privileges available to the process.

Installing Node Exporter as a systemd service

Download the Node Exporter archive and extract it. In this example, the version number is specified explicitly:

cd /tmp

wget https://github.com/prometheus/node_exporter/releases/download/v1.9.1/node_exporter-1.9.1.linux-amd64.tar.gz

Extract the archive: tar xvf node_exporter-1.9.1.linux-amd64.tar.gz

Install the binary file: sudo cp node_exporter-1.9.1.linux-amd64/node_exporter /usr/local/bin/

Set the owner: sudo chown node_exporter:node_exporter /usr/local/bin/node_exporter

Verify the installation: /usr/local/bin/node_exporter –version

Now create a systemd unit: sudo nano /etc/systemd/system/node_exporter.service

Add the following:

[Unit]

Description=Prometheus Node Exporter

After=network-online.target

Wants=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

Restart=on-failure

RestartSec=5s

[Install]

WantedBy=multi-user.target

The parameter:

–web.listen-address=127.0.0.1:9100

binds Node Exporter to the local interface only. As a result, Node Exporter will not listen on the VPS’s public IP address on port 9100.

Apply the configuration and start the service:

sudo systemctl daemon-reload

sudo systemctl enable –now node_exporter

Verifying exporter operation and available metrics

Check the status: sudo systemctl status node_exporter –no-pager

The service should be in the following state: Active: active (running)

Now make sure the endpoint actually responds locally: curl http://127.0.0.1:9100/metrics | head

The response will include metrics in Prometheus format, for example:

# HELP node_cpu_seconds_total Seconds the CPUs spent in each mode.

# TYPE node_cpu_seconds_total counter

node_cpu_seconds_total{cpu="0",mode="idle"} …

To check which interface the service is listening on, run: sudo ss -lntp | grep 9100

Expected address: 127.0.0.1:9100

This confirms that Node Exporter is running and is available to Prometheus locally, while its port is not exposed directly to the internet.

Installing and Configuring Prometheus

After starting Node Exporter, you can install Prometheus. It will regularly query the local endpoint 127.0.0.1:9100, store the collected metrics, and make them available to Grafana.

Creating the Prometheus User and Directories

As with Node Exporter, it is best to run Prometheus under a dedicated system account that cannot be used for interactive login.

Create the user:

sudo useradd \

–no-create-home \

–shell /usr/sbin/nologin \

prometheus

Separate directories are needed for configuration and data storage:

sudo mkdir -p /etc/prometheus

sudo mkdir -p /var/lib/prometheus

Set the owner of the data directory: sudo chown prometheus:prometheus /var/lib/prometheus

The /etc/prometheus directory will be used for the main configuration file and additional rules, and /var/lib/prometheus will be used for the Prometheus time series database.

Installing Prometheus

Download the Prometheus archive to the /tmp directory:

cd /tmp

wget https://github.com/prometheus/prometheus/releases/download/v3.5.0/prometheus-3.5.0.linux-amd64.tar.gz

Extract it: tar xvf prometheus-3.5.0.linux-amd64.tar.gz

Change to the directory: cd prometheus-3.5.0.linux-amd64

Copy the binaries: sudo cp prometheus promtool /usr/local/bin/

Copy the default configuration and web interface directories:

sudo cp prometheus.yml /etc/prometheus/prometheus.yml

sudo cp -r consoles console_libraries /etc/prometheus/

Set the ownership:

sudo chown prometheus:prometheus /usr/local/bin/prometheus

sudo chown prometheus:prometheus /usr/local/bin/promtool

sudo chown -R prometheus:prometheus /etc/prometheus

Check the installed version: prometheus –version

Now create a systemd unit: sudo nano /etc/systemd/system/prometheus.service

Add the following:

[Unit]

Description=Prometheus Monitoring

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

Restart=on-failure

RestartSec=5s

[Install]

WantedBy=multi-user.target

The parameter:

–web.listen-address=127.0.0.1:9090

limits the Prometheus web interface and API to the VPS’s local interface. As a result, port 9090 will not be directly accessible from the internet.

Adding Node Exporter to prometheus.yml

Now specify where Prometheus should scrape system metrics from.

Open the configuration file: sudo nano /etc/prometheus/prometheus.yml

Keep the basic configuration and add a separate job for Node Exporter:

global:

scrape_interval: 15s

evaluation_interval: 15s

scrape_configs:

– job_name: "prometheus"

static_configs:

– targets:

– "127.0.0.1:9090"

– job_name: "node"

static_configs:

– targets:

– "127.0.0.1:9100"

The scrape_interval: 15s setting means that Prometheus will collect new metric values every 15 seconds.

Before restarting, check the syntax: sudo promtool check config /etc/prometheus/prometheus.yml

If the configuration is valid, the following message will appear: SUCCESS: /etc/prometheus/prometheus.yml is valid prometheus config file syntax

Apply the changes:

sudo systemctl daemon-reload

sudo systemctl enable –now prometheus

Check the status: sudo systemctl status prometheus –no-pager

The service should be in the active (running) state.

Checking targets in Prometheus

Since the Prometheus web interface is available only locally, you can check targets through its HTTP API: curl -s http://127.0.0.1:9090/api/v1/targets

To make the check easier, you can filter the output: curl -s http://127.0.0.1:9090/api/v1/targets | grep -o ‘"health":"[^"]*"’

For a running Node Exporter, you should see: "health":"up"

Later, when Grafana is connected to Prometheus, all this data will be available through the graphical interface.

Configuring Prometheus Metrics Storage

Prometheus stores collected time series locally. Without a retention limit, the data directory will gradually grow, so it is best to define the retention period and maximum storage size in advance.

How retention works in Prometheus

Retention defines how long Prometheus stores historical metrics.

The longer the history, the more data is available for analysis. For example, a week of history lets you compare load across days, while a month of history helps identify longer-term trends.

However, the amount of data depends on several factors:

  • The number of targets;
  • The number of metrics collected;
  • The scrape interval;
  • The retention period;
  • How active the time series are.

For a single VPS with Node Exporter, the data volume usually remains moderate, but even in this case it is useful to set explicit limits.

Limiting the metrics retention period

The retention period is set using the following parameter: –storage.tsdb.retention.time

For example, to retain history for 15 days: –storage.tsdb.retention.time=15d

Open the unit file: sudo nano /etc/systemd/system/prometheus.service

Add the parameter to ExecStart:

ExecStart=/usr/local/bin/prometheus \

–config.file=/etc/prometheus/prometheus.yml \

–storage.tsdb.path=/var/lib/prometheus \

–storage.tsdb.retention.time=15d \

–web.listen-address=127.0.0.1:9090

After modifying the unit file, reload the systemd configuration:

sudo systemctl daemon-reload

sudo systemctl restart prometheus

Prometheus will now delete data older than the configured retention period.

Limiting storage size

You can also limit the maximum size of the local TSDB: –storage.tsdb.retention.size=5GB

The final startup block will then look like this:

ExecStart=/usr/local/bin/prometheus \

–config.file=/etc/prometheus/prometheus.yml \

–storage.tsdb.path=/var/lib/prometheus \

–storage.tsdb.retention.time=15d \

–storage.tsdb.retention.size=5GB \

–web.listen-address=127.0.0.1:9090

This limits retention by both time and storage size.

Check the service status: sudo systemctl status prometheus –no-pager

And the actual startup parameters: ps aux | grep ‘[p]rometheus’

The output should include:

–storage.tsdb.retention.time=15d

–storage.tsdb.retention.size=5GB

Installing and Configuring Grafana

Now we will install Grafana. It will use Prometheus as a data source and provide a convenient web interface for viewing VPS metrics.

Installing Grafana on a VPS

First, install the required packages: sudo apt install -y apt-transport-https software-properties-common wget

Add the Grafana repository key: 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

Add the official repository:

echo "deb [signed-by=/etc/apt/keyrings/grafana.gpg] https://apt.grafana.com stable main" | \

sudo tee /etc/apt/sources.list.d/grafana.list

Update the package index: sudo apt update

Install Grafana: sudo apt install -y grafana

You can check the installed version with the following command: grafana-server -v

Starting Grafana via systemd

Enable automatic startup and start the service: sudo systemctl enable –now grafana-server

Check the status: sudo systemctl status grafana-server –no-pager

By default, Grafana listens on port 3000.

Check it: sudo ss -lntp | grep 3000

At this stage, you can use the interface for the initial setup, but in the final configuration we will not leave direct public access to port 3000 enabled. Later, Grafana will be published through Nginx and HTTPS.

Adding Prometheus as a Data Source

Open Grafana in a browser at: http://203.0.113.10:3000

For the first login, use the default credentials:

Login: admin

Password: admin

After the first login, Grafana will prompt you to change the administrator password.

To connect Prometheus, open: Connections → Data sources → Add data source → Prometheus

In the URL field, enter: http://127.0.0.1:9090

Because Grafana and Prometheus are running on the same VPS, there is no need to access Prometheus through a public or internal IP address.

Save the data source and test the connection. Grafana should confirm that it has successfully connected to Prometheus.

After that, Grafana will be able to run PromQL queries against Prometheus and use the collected Node Exporter metrics to build dashboards.

Importing a Prebuilt Dashboard for Node Exporter

After connecting Prometheus to Grafana, you can move from individual queries to a full dashboard. This is more convenient than manually building dozens of panels for CPU, memory, disk, and network metrics from scratch.

Prebuilt templates already exist for Node Exporter, so you only need to choose a suitable dashboard, import it, and associate it with our Prometheus data source.

Selecting a Node Exporter dashboard

To monitor a single VPS, it is convenient to use a prebuilt dashboard that already includes the core panels for a Linux server.

One of the most popular options is the Node Exporter Fulldashboard. It is often used as a base template because it already includes:

  • CPU load;
  • RAM usage;
  • Load average;
  • Disk activity;
  • File systems;
  • Network traffic;
  • Uptime;
  • Basic I/O and inode metrics.

The advantage of a prebuilt dashboard is that it comes with vetted PromQL queries and a familiar layout for displaying system metrics. This lets you start using working monitoring faster and, if needed, refine only individual panels later.

Importing a dashboard into Grafana

In the Grafana interface, open: Dashboards → New → Import

If you are using an ID from the Grafana dashboard library, you can paste it into the import field. For Node Exporter, the following ID is commonly used: 1860

After the dashboard loads, Grafana will prompt you to select a Data Source. Specify the source you created earlier: Prometheus

Then confirm the import.

After that, a ready-made dashboard with a set of panels for the VPS will appear in Grafana. Initially, you can leave it mostly unchanged and then adapt it to the specific server if necessary—for example, by hiding unnecessary panels or renaming headings.

Verifying Metric Ingestion

After import, open the dashboard and make sure the panels are not empty. If Prometheus is already receiving data from Node Exporter, Grafana will start rendering graphs immediately.

For a quick check, confirm that the dashboard displays:

  • Current CPU load;
  • Used and available memory;
  • Free space on file systems;
  • Network traffic by interface;
  • Disk activity metrics.

If the panels remain empty, check three points in the pipeline:

  1. Whether Node Exporter is running;
  2. Whether Prometheus sees it as an UP target;
  3. Whether the correct Data Source is configured in Grafana.

In practice, if the previous steps are configured correctly, the prebuilt dashboard starts displaying metrics immediately after import.

Monitoring Key VPS Resources

After importing the dashboard, it is important to understand which of its metrics are genuinely useful in practice. The charts themselves are convenient, but monitoring only becomes valuable when they allow you to assess the status of the VPS and detect issues in time.

CPU Utilization and Load Average

For CPU, two related but distinct metrics are typically monitored: utilization percentage and Load Average.

CPU utilization percentage shows how much processor time is spent on useful computation versus idle time. If utilization remains high for an extended period, it may indicate heavy background processes, inefficient application code, recurring load spikes, or insufficient VPS resources.

Load Average shows the average number of tasks that are running or waiting to run. This metric is especially useful when evaluated against the number of CPU cores.

For example, if a VPS has two vCPUs and the load consistently stays above 2, this may already indicate overload. Short spikes are not always critical, but a persistently high Load Average is a good reason to check running processes, CPU usage, and the overall load on the application.

Memory Usage

For memory, it is important to look not only at the overall utilization percentage, but also at the breakdown of RAM usage.

On Linux, some memory is actively used for the page cache and buffers, so a high amount of used memory does not necessarily indicate a problem on its own. More meaningful indicators include:

  • The amount of available memory;
  • The share of truly free memory;
  • Swap usage;
  • Changes in memory usage over time.

If available memory gradually decreases and is not freed, this may indicate a memory leak in the application. If swap also starts being used heavily, VPS performance usually degrades.

In practice, it is therefore useful to monitor both overall RAM usage and the trend: whether memory usage is increasing steadily, rising in spikes, or remaining at its normal level.

Free Space and File System Usage

One of the most common issues on a VPS is running out of disk space. When space is exhausted, applications, backups, the database, logging, and system updates may stop working, and even SSH access may become unavailable.

The dashboard should track:

  • Total file system size;
  • Available free space;
  • Usage percentage;
  • Space distribution across the main mount points.

It is especially important to monitor the system partition, as well as the directories that store:

  • Logs;
  • Application files;
  • The database;
  • Backups;
  • Prometheus metrics.

Even if the application is running normally, a full file system can quickly turn normal load into an incident. That is why we will configure a separate alert for disk usage later.

Disk I/O

Disk I/O helps you understand how actively a VPS is reading and writing data. These metrics are especially important for servers with databases, logging, task queues, and frequent file operations.

In practice, the following are usually monitored:

  • Read and write throughput;
  • Number of read and write operations;
  • I/O wait time;
  • Device utilization.

If CPU and memory usage look normal but the application is slow, the disk subsystem may be the cause. High I/O and long wait times often indicate that the server is bottlenecked by disk performance rather than CPU.

Network Traffic

Network metrics show the volume of inbound and outbound traffic across interfaces. They help identify:

  • Increased load on the web application;
  • Heavy background synchronization;
  • Anomalous activity;
  • The effects of updates or migrations.

For a typical VPS, sudden changes in traffic are often the first noticeable sign. For example, a sudden spike in inbound traffic may be related to a peak in visits, bots, or a misconfiguration, while a sharp increase in outbound traffic may indicate data export, backups, or suspicious activity.

As a result, the Grafana dashboard becomes more than just a set of charts; it becomes the main dashboard for monitoring server health.

Configuring alerts

Charts are useful for analysis, but alerts are needed for a prompt response to issues. In this guide, we will configure three basic alert rules in Grafana based on Prometheus data:

  • Disk space filling up;
  • High load;
  • Node Exporter unavailability.

This set covers the most common scenarios: running out of disk space, VPS overload, and loss of the system metrics source itself.

To deliver notifications, Grafana uses Contact points. They can be used to send alerts, for example, to email, messengers, or external incident management systems.

The choice of a specific channel and its configuration depend on the infrastructure in use, so in this guide we will focus on creating and validating the alert rules themselves.

Disk space alert

The first alert will warn you when free space on the file system is getting too low.

A convenient condition is the percentage of free space. For example, the rule should trigger if less than 15% is available.

In Grafana, open: Alerts & IRM → Alert rules → New alert rule

You can use the following expression:

(

node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"}

/

node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}

) * 100 < 15

This query calculates the percentage of free space and excludes temporary file systems such as tmpfs and overlay, which are typically not used to store persistent data.

Name the rule, for example:

DiskSpaceLow

Then set a short delay before firing, such as for 5m, to avoid reacting to very brief fluctuations.

High Load Alert

The second rule will monitor high CPU utilization.

To do this, you can use an expression based on node_cpu_seconds_total that calculates the share of time when the CPU is not idle:

100 – (

avg by(instance) (

rate(node_cpu_seconds_total{mode=&quot;idle&quot;}[5m])

) * 100

) > 80

If the result exceeds 80, it means that CPU utilization has averaged more than 80% over the last 5 minutes.

Name the rule: HighLoad

Also set a confirmation window, for example, for 5m.

This approach is better than an instant comparison with the current value because it filters out very short load spikes and highlights sustained overload.

Node Exporter Unavailability Alert

The third rule should fire if Prometheus stops receiving metrics from Node Exporter.

To do this, use the standard availability metric up: up{job=”node”} == 0

If the target is unavailable, Prometheus will record 0, and the rule will enter the alert state.

Name it: NodeExporterDown

This alert is especially important because if Node Exporter stops or connectivity to it is lost, all other system metrics will stop being updated. Without this rule, it could create the false impression that the server is simply “healthy,” even though the data has actually stopped coming in.

Verifying alert rules

After the rules are created, Grafana will display them in the main alert rules list. At this stage, make sure that:

  • All three rules have been saved;
  • Prometheus is selected as the data source;
  • The expressions run without errors;
  • The initial state of the rules is Normal.

The list should include at least:

DiskSpaceLow

HighLoad

NodeExporterDown

Even before testing actual alert triggering, it is useful to confirm that the rules have been created correctly and are visible in the interface.

Verifying Alert Triggers

After creating alert rules, you need to make sure they actually transition to the alert state when the specified condition is met. The best way to do this is to use a controlled test scenario that does not create a real issue for the VPS.

Creating a Test Condition Manually

The simplest option is to temporarily stop Node Exporter. In this case, Prometheus will stop receiving metrics from the target node, and the NodeExporterDown rule should fire.

Stop the service: sudo systemctl stop node_exporter

Check its status: sudo systemctl status node_exporter –no-pager

The service should be shown as stopped.

Prometheus will continue scraping on schedule, but the request to:

127.0.0.1:9100

will start failing.

If the rule uses the following condition: up{job=”node”} == 0

then after the next evaluation, Prometheus will pass Grafana a value indicating that the target is unavailable.

Checking an alert’s transition from Pending to Firing

If a pending period is set for the rule, for example for 5m, it usually does not enter Firing immediately.

First, the state changes to: Pending

This means that the condition is already being met, but the specified period has not yet elapsed.

If Node Exporter remains unavailable for long enough, the state changes to: Firing

In Grafana, open: Alerts & IRM → Alert rules

and find the rule: NodeExporterDown

If the test is successful, the rule should be shown in the Firing state.

You can test other rules in the same way, but artificially filling the system disk or creating sustained high load on a production server is usually unnecessary. Safely disabling Node Exporter is sufficient to verify the basic logic.

Restoring the system to normal operation

After the check, start Node Exporter again: sudo systemctl start node_exporter

Make sure the service is running: sudo systemctl status node_exporter –no-pager

After a few scrape cycles, Prometheus will see the target as available again.

You can verify this through the API: curl -s http://127.0.0.1:9090/api/v1/targets | grep -o ‘&quot;health&quot;:&quot;[^&quot;]*&quot;’

The response should include: &quot;health&quot;:&quot;up&quot;

After the target is restored, the NodeExporterDown rule will return to the Normal state.

Securing Monitoring Interfaces

At this stage, the monitoring system is already running, but it is important to ensure that its service interfaces are not directly accessible from the internet.

Node Exporter and Prometheus should be used only locally, while Grafana will be exposed through Nginx over HTTPS.

Restricting external access to ports 9090 and 9100

We have already started Node Exporter and Prometheus with the addresses 127.0.0.1:9100 and 127.0.0.1:9090.

This means the services listen only on the VPS loopback interface.

Let’s verify: sudo ss -lntp | grep -E ‘9090|9100’

Expected output:

  • 127.0.0.1:9090
  • 127.0.0.1:9100

If UFW is used, also make sure that only the required ports are allowed from outside:

sudo ufw allow OpenSSH

sudo ufw allow 80/tcp

sudo ufw allow 443/tcp

sudo ufw enable

Check the rules: sudo ufw status

Ports 9090, 9100, and 3000 do not need to be opened separately.

Accessing Grafana via Nginx

Install Nginx: sudo apt install -y nginx

Grafana will need a domain, for example: grafana.example.com

The DNS A record must point to the public IP address of the VPS.

Create the configuration: sudo nano /etc/nginx/sites-available/grafana

Add:

server {

listen 80;

server_name grafana.example.com;

location / {

proxy_pass http://127.0.0.1:3000;

proxy_http_version 1.1;

proxy_set_header Host $host;

proxy_set_header X-Real-IP $remote_addr;

proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

proxy_set_header X-Forwarded-Proto $scheme;

proxy_set_header Upgrade $http_upgrade;

proxy_set_header Connection &quot;upgrade&quot;;

}

}

Enable the site: sudo ln -s /etc/nginx/sites-available/grafana /etc/nginx/sites-enabled/grafana

Check the configuration: sudo nginx -t

If the syntax is correct, reload Nginx:

sudo systemctl reload nginx

Grafana will now be available via Nginx at the domain, while direct access to Prometheus and Node Exporter will remain blocked.

Configuring HTTPS for Grafana

Install Certbot: sudo apt install -y certbot python3-certbot-nginx

Request a certificate: sudo certbot –nginx -d grafana.example.com

Certbot will obtain a TLS certificate and automatically update the Nginx configuration.

Grafana will then be available at: https://grafana.example.com

Check the configuration: sudo nginx -t

Also check the Nginx status: sudo systemctl status nginx –no-pager

Verifying service availability after port restrictions

Grafana should be accessible in the browser only over HTTPS: https://grafana.example.com

At the same time, requests to the VPS public IP address on service ports must not provide access to the interfaces:

http://203.0.113.10:9090

http://203.0.113.10:9100

http://203.0.113.10:3000

Locally, however, all services will continue to communicate with each other:

curl http://127.0.0.1:9100/metrics | head

curl http://127.0.0.1:9090/-/healthy

This keeps Grafana accessible to the user while preventing the internal monitoring components from being exposed directly to the internet.

Monitoring System Maintenance

After the initial setup, the system does not require constant intervention, but you should periodically check service status, logs, and target availability.

Checking Service Status

You can quickly check all key components with separate commands:

sudo systemctl status node_exporter –no-pager

sudo systemctl status prometheus –no-pager

sudo systemctl status grafana-server –no-pager

sudo systemctl status nginx –no-pager

For a more concise check, you can use: systemctl is-active node_exporter prometheus grafana-server nginx

For all running services, the system should return: active

Viewing Prometheus, Grafana, and Node Exporter logs

systemd logs help you quickly identify the cause of startup errors, unavailable targets, or configuration issues.

Node Exporter: sudo journalctl -u node_exporter -n 50 –no-pager

Prometheus: sudo journalctl -u prometheus -n 50 –no-pager

Grafana: sudo journalctl -u grafana-server -n 50 –no-pager

To view new messages in real time, use the -f option: sudo journalctl -u prometheus -f

This is especially useful after changing the configuration or restarting a service.

Updating monitoring components

Grafana installed from the APT repository can be updated using the standard system package management tools:

sudo apt update

sudo apt upgrade

In our example, Node Exporter and Prometheus were installed from archives. To update them, download the new version, replace the binary file, and restart the corresponding systemd service.

Before replacing Prometheus, it is advisable to back up the current configuration: sudo cp /etc/prometheus/prometheus.yml /etc/prometheus/prometheus.yml.bak

After the update, check the configuration: sudo promtool check config /etc/prometheus/prometheus.yml

Only then should you restart the service: sudo systemctl restart prometheus

The TSDB directory itself does not need to be deleted during a standard update.

What to do if Prometheus stops receiving metrics

If Grafana stops updating data, first check the entire chain from Node Exporter to the visualization layer.

Check Node Exporter: curl http://127.0.0.1:9100/metrics | head

If the endpoint does not respond, check the service: sudo systemctl status node_exporter –no-pager

If Node Exporter is running, check the target status in Prometheus: curl -s http://127.0.0.1:9090/api/v1/targets

Next, check the configuration: sudo promtool check config /etc/prometheus/prometheus.yml

And the logs: sudo journalctl -u prometheus -n 50 –no-pager

It is also important to make sure Prometheus is actually listening on the local port: sudo ss -lntp | grep 9090

If Prometheus is receiving metrics but Grafana shows No data, the problem is usually at the Data Source level, in the PromQL query, or in the imported dashboard itself.

Checking each link in the chain one by one helps you quickly identify exactly where the data stopped coming in, without immediately changing the entire monitoring configuration.

Conclusion

Prometheus, Grafana, and Node Exporter let you build a full-featured monitoring system on a VPS without using an external SaaS service. Node Exporter provides Linux system metrics, Prometheus collects and stores them at regular intervals, and Grafana turns time series into clear charts and dashboards.

As a result of the configuration, we set up monitoring for CPU, memory, file systems, disk I/O, and network traffic, imported a ready-made dashboard, limited the metric retention period and storage volume, and configured basic alerts for disk space usage, high load, and Node Exporter unavailability.

We paid special attention to security: Prometheus and Node Exporter listen only on local interfaces, while Grafana is available through Nginx over HTTPS. This means the monitoring service ports do not need to be exposed directly to the internet.

This stack is suitable both for a single VPS and as a starting point for a larger infrastructure. As the project grows, you can add new targets and exporters to Prometheus, expand Grafana dashboards, and add new alerting rules to the system.

FAQ

Do I need to install Node Exporter on every VPS?

Yes. If you need to collect system metrics from multiple Linux servers, Node Exporter is typically run on each one. Prometheus then scrapes all specified targets and stores the resulting time series. Node Exporter is specifically designed to export hardware and system metrics from Unix-like systems.

Can Grafana be used without Prometheus?

Yes. Grafana supports many data sources, including Prometheus, various SQL databases, Loki, and other backend systems. In this configuration, Prometheus is responsible for collecting and storing system metrics, while Grafana is used to visualize them.

Do I need to expose port 9100 to the internet?

No, not if Prometheus is running on the same VPS. In this case, Node Exporter can be bound to 127.0.0.1:9100, and Prometheus will collect metrics locally. This reduces the attack surface and matches the setup used in the guide.

If Prometheus is on a different server, access to port 9100 should be provided through a private network, VPN, or restrictive firewall rules, rather than exposing the port to the entire internet.

How does Prometheus differ from Grafana?

Prometheus collects metrics, stores time series, and lets you query them using PromQL. Grafana connects to Prometheus as a data source and uses the retrieved data to build panels and dashboards.

How long does Prometheus retain metrics?

The retention period depends on the configured retention settings. Prometheus can limit data retention by time and by storage size. When planning local storage capacity, the Prometheus developers recommend leaving spare disk space rather than allowing the TSDB to use the entire available disk.

Why does an alert first move to Pending instead of going directly to Firing?

If a pending period is configured for a rule, the condition must remain true for the specified amount of time. Until this period has elapsed, the rule remains in the Pending state. If the condition continues to be met, the rule moves to Firing. This delay helps avoid reacting to short-lived spikes. Grafana lets you configure the evaluation interval and pending period for alert rules.

What should you do if a dashboard shows “No data” after import?

First, check the entire chain:

  1. Node Exporter responds on /metrics.
  2. Prometheus sees the target with UP status.
  3. Grafana is connected to the correct Prometheus Data Source.
  4. The imported dashboard uses the appropriate metrics, and the correct data source is selected.

Grafana supports importing prebuilt dashboards and lets you select the appropriate Data Source during import.

Can this setup be used for multiple VPS instances?

Yes. You can add multiple Node Exporter targets to prometheus.yml. Prometheus will then regularly scrape each server, and Grafana will be able to filter and compare metrics by instance.

For a small infrastructure, this is often sufficient. As it grows, it makes sense to plan centralized storage, Prometheus scaling, and alert delivery separately.

Sources

  1. Prometheus Documentation — Overview
  2. Prometheus Documentation — Monitoring Linux host metrics with the Node Exporter
  3. Prometheus Documentation — Storage
  4. Grafana Documentation — Prometheus data source and alerting

Subscribe to our newsletter and receive articles and news

    Check out our other materials