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:
| Component | Port | Purpose |
| Node Exporter | 9100 | VPS system metrics |
| Prometheus | 9090 | web interface, API, and PromQL queries |
| Grafana | 3000 | Grafana web interface |
| Nginx | 80 | HTTP and TLS certificate issuance |
| Nginx | 443 | HTTPS 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:
- Whether Node Exporter is running;
- Whether Prometheus sees it as an UP target;
- 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="idle"}[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 ‘"health":"[^"]*"’
The response should include: "health":"up"
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 "upgrade";
}
}
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:
- Node Exporter responds on /metrics.
- Prometheus sees the target with UP status.
- Grafana is connected to the correct Prometheus Data Source.
- 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.
