
Your VPS can look “up” while customers see a white screen, timeouts, or a checkout that won’t load. This VPS monitoring tutorial gives you a solid baseline. You’ll run uptime checks from outside the server, resource alerts from inside it, and a log routine that gets you to the cause fast.
This stack matches real hosting failures. You’ll monitor SSH reachability, HTTP status, and TLS expiry.
You’ll also track CPU/RAM pressure, disk space and inodes, plus the services that keep WordPress and PHP sites running. That includes Nginx/Apache, PHP-FPM, and MySQL/MariaDB.
Everything runs cleanly on Ubuntu Server 24.04 LTS and stays easy to maintain in 2026.
What you’ll build (and why this specific setup works)
- External uptime monitoring with Uptime Kuma (HTTP/HTTPS, keyword checks, ping, TCP port checks).
- Internal metrics + alert rules with Prometheus + Node Exporter (CPU, RAM, disk, network, load, pressure stall info).
- Dashboards with Grafana (prebuilt Node Exporter dashboards, plus a couple of useful panels).
- Log triage with journalctl + Nginx/Apache logs (so alerts lead to fixes).
If you want a server that stays predictable during spikes, set up monitoring before you start “tuning.” Graphs and timelines let you make targeted changes.
That includes changes like the ones in our Nginx performance optimization tutorial. You can also prove the change worked.
Prerequisites and sizing checklist
You’ll need one Ubuntu VPS to host the monitoring stack.
For a small fleet (1–10 VPS), a 2 vCPU / 4 GB RAM server is usually enough. Keep retention modest (7–15 days).
For 30+ servers, step up to 4 vCPU / 8 GB. Increase disk to match.
- Ubuntu Server 24.04 LTS (monitoring host)
- At least 40–80 GB disk (Grafana + Prometheus data)
- One domain or subdomain (optional but recommended):
monitor.example.com - Ports: 22 (SSH), 80/443 (web), plus internal-only 3000/9090/9100 where possible
If you don’t want to maintain the OS, patches, and firewall rules yourself, a managed VPS hosting plan from HostMyCode can keep both monitoring and production workloads steady.
If you prefer full control, start with a standard HostMyCode VPS.
Step 1: Harden the monitoring host basics (fast, not fancy)
Dashboards don’t belong on a wide-open box with password SSH and exposed ports. Keep it simple. Patch, use SSH keys, and lock the firewall down.
Update and reboot if needed
sudo apt update
sudo apt -y upgrade
sudo reboot
Confirm SSH keys and disable password auth (recommended)
If you haven’t hardened SSH yet, follow this guide: VPS hardening tutorial.
At a minimum, set:
sudo nano /etc/ssh/sshd_config.d/99-hardening.conf
PasswordAuthentication no
PermitRootLogin no
sudo systemctl reload ssh
Enable UFW with explicit ports
Only expose 80/443 publicly. Keep Prometheus (9090) and Node Exporter (9100) private.
sudo apt -y install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
For a more complete hosting-friendly UFW pattern (including Fail2Ban-friendly rules), use: UFW firewall setup tutorial.
Step 2: Install Docker and Uptime Kuma for external checks
Uptime Kuma is your first line of defense. It measures what users experience.
These checks run from the monitoring host, not from inside the server you’re testing.
Install Docker Engine (Ubuntu 24.04)
sudo apt -y install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt -y install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker
Run Uptime Kuma
sudo mkdir -p /opt/uptime-kuma
sudo docker run -d \
--name uptime-kuma \
--restart=always \
-p 3000:3000 \
-v /opt/uptime-kuma:/app/data \
louislam/uptime-kuma:1
Kuma is now available at http://YOUR_SERVER_IP:3000. Don’t keep it exposed like this.
Put it behind HTTPS and basic auth with Nginx.
Step 3: Reverse proxy Kuma with Nginx + HTTPS
Monitoring dashboards collect sensitive details. That can include hostnames, admin URLs, and internal notes.
Treat this like an admin panel. Lock it down.
Install Nginx
sudo apt -y install nginx
sudo systemctl enable --now nginx
Create basic auth credentials
sudo apt -y install apache2-utils
sudo htpasswd -c /etc/nginx/.htpasswd monitoradmin
Nginx site config for monitor.example.com
sudo nano /etc/nginx/sites-available/monitor.example.com
server {
listen 80;
server_name monitor.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
auth_basic "Monitoring";
auth_basic_user_file /etc/nginx/.htpasswd;
}
}
sudo ln -s /etc/nginx/sites-available/monitor.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Issue a Let’s Encrypt certificate
Use the same certbot approach you’d use for any VPS site. For the full walkthrough, see: SSL certificate setup guide.
sudo apt -y install certbot python3-certbot-nginx
sudo certbot --nginx -d monitor.example.com
Your dashboard is now on https://monitor.example.com, protected by HTTPS and basic auth.
Step 4: Add Uptime Kuma monitors that catch real hosting outages
Set up checks that match how sites actually fail. “Ping works” doesn’t mean checkout works.
- HTTP(s) 200 check:
https://www.example.com/ - Keyword check: match a string that only appears on a healthy page (for example, a footer brand line). This catches “200 OK” error templates.
- TCP port check: 443 and 22 for basic reachability.
- Certificate expiry: Kuma can warn before TLS certs expire (set 14/7/3 day reminders).
- DNS check (practical): monitor the apex and
wwwhostnames to catch mispointed records after migrations.
Alert routing: email and Slack/Telegram
If you plan to page yourself by email from your VPS, treat deliverability as a setup task. Don’t leave it for later.
Configure SPF/DKIM/DMARC, then test with real inboxes.
Also confirm reverse DNS for the sending IP. Many providers penalize you without it: reverse DNS setup guide.
Step 5: Install Prometheus + Node Exporter for internal resource monitoring
Kuma answers “is it down?” Prometheus helps you answer “what changed?”
It helps you catch disk filling up, RAM exhaustion, or CPU steal time spikes on a noisy host.
This section installs Prometheus and Grafana on the monitoring host. Then you’ll install Node Exporter on each VPS you want to watch.
Install Prometheus and Grafana from Ubuntu packages
Ubuntu 24.04 ships stable builds that work well for small and mid-size fleets. If you later need a specific upstream release, you can switch then.
For most setups, the Ubuntu packages are a sensible starting point.
sudo apt -y install prometheus prometheus-node-exporter grafana
sudo systemctl enable --now prometheus
sudo systemctl enable --now grafana-server
Grafana listens on port 3000 by default. Don’t expose it directly.
Put it behind Nginx on a separate subdomain (or keep it private on a VPN). Below, you’ll move Grafana to port 3001 to avoid conflicting with Kuma.
Move Grafana to port 3001 (avoid clashing with Kuma)
sudo nano /etc/grafana/grafana.ini
Set:
[server]
http_addr = 127.0.0.1
http_port = 3001
sudo systemctl restart grafana-server
Reverse proxy Grafana with Nginx + basic auth
sudo htpasswd -c /etc/nginx/.htpasswd-grafana grafanaadmin
sudo nano /etc/nginx/sites-available/grafana.example.com
server {
listen 80;
server_name grafana.example.com;
location / {
proxy_pass http://127.0.0.1:3001;
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;
auth_basic "Grafana";
auth_basic_user_file /etc/nginx/.htpasswd-grafana;
}
}
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
Log in at https://grafana.example.com. The default Grafana credentials are typically admin / admin.
Change them right away.
Install Node Exporter on each monitored VPS
On each target VPS (the servers running your sites), install Node Exporter:
sudo apt update
sudo apt -y install prometheus-node-exporter
sudo systemctl enable --now prometheus-node-exporter
By default it binds on :9100. Don’t expose it to the internet.
Lock Node Exporter down to the monitoring host
On the target VPS, allow port 9100 only from your monitoring host IP.
sudo ufw allow from MONITORING_HOST_IP to any port 9100 proto tcp
sudo ufw status
If you use firewalld instead of UFW (common on AlmaLinux/Rocky), add a rich rule or source-limited port rule.
The requirement doesn’t change: source limit it.
Add scrape targets to Prometheus
On the monitoring host:
sudo nano /etc/prometheus/prometheus.yml
Add your servers under scrape_configs:
scrape_configs:
- job_name: "node"
static_configs:
- targets:
- "server1.example.com:9100"
- "server2.example.com:9100"
- "203.0.113.10:9100"
sudo systemctl restart prometheus
Quick sanity check from the monitoring host:
curl -s http://server1.example.com:9100/metrics | head
Step 6: Add dashboards you’ll actually use
You don’t need dozens of dashboards. You need one view that answers, fast: is it CPU, RAM, disk, or network?
Then add a WordPress-specific view if you host WordPress.
Import a Node Exporter dashboard
In Grafana:
- Go to Connections → Data sources and add Prometheus with URL
http://127.0.0.1:9090. - Go to Dashboards → New → Import.
- Import a Node Exporter dashboard (common community dashboards include “Node Exporter Full”).
Two panels worth adding: disk inode usage and CPU steal
Disk space alerts catch the obvious failures. Inode exhaustion wastes hours.
The disk can look “fine,” but uploads, cache writes, or plugin updates start failing.
- Inodes used (%) (per filesystem):
(node_filesystem_files_free / node_filesystem_files) * 100(invert to show used) - CPU steal (%) (virtualization contention):
rate(node_cpu_seconds_total{mode="steal"}[5m])
If steal time stays high, your VM is fighting for CPU at the hypervisor layer. That’s usually your cue to move to a larger VPS or a dedicated box.
Step 7: Create alert rules that map to hosting incidents
Charts are for diagnosis. Alerts are for action.
In 2026, the simplest setup on Ubuntu is Grafana Alerting (rules + contact points) with Prometheus as the data source.
Recommended starter alerts (copy and tune)
- Disk space: warn at 80%, critical at 90% for
/and for your web/data volumes. - Inodes: warn at 70%, critical at 85%.
- RAM pressure: sustained low available memory (or swap activity) for 10 minutes.
- Load spike: load average > (vCPU count × 1.5) for 10 minutes.
- HTTP error rate: if you have a blackbox check (optional), alert when 5xx appears repeatedly.
Example PromQL snippets
Disk used (%) (exclude tmpfs):
100 * (1 - (node_filesystem_avail_bytes{fstype!~"tmpfs|overlay"} / node_filesystem_size_bytes{fstype!~"tmpfs|overlay"}))
Memory available (%):
100 * (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)
Swap activity (page-in rate):
rate(node_vmstat_pswpin[5m])
Give alerts a duration window (5–15 minutes) so you don’t get paged for harmless spikes.
Step 8: Tie alerts to fixes with a log triage routine
Build a repeatable loop: alert → confirm → isolate → fix.
The commands below are a dependable starting set for WordPress and PHP hosting.
Quick commands for incident response
-
Disk space and inodes
df -h df -i sudo du -xh /var | sort -h | tail -n 20 -
Top CPU/RAM consumers
top ps aux --sort=-%mem | head ps aux --sort=-%cpu | head -
Nginx / Apache errors
sudo tail -n 200 /var/log/nginx/error.log sudo tail -n 200 /var/log/apache2/error.log -
PHP-FPM status
sudo systemctl status php8.3-fpm --no-pager sudo journalctl -u php8.3-fpm -n 200 --no-pager -
Kernel and OOM kills
sudo journalctl -k -n 200 --no-pager sudo journalctl -k | grep -i "out of memory" | tail
If you see repeated 429/403 from bots or credential stuffing on /wp-login.php, rate limiting often calms things down fast.
Use the specific config patterns in our Nginx rate limiting tutorial.
Step 9: Add one migration-safe check (prevents the classic outage)
Migrations usually fail at DNS or TLS, not during file transfer.
Add these Kuma checks before you move anything:
- Monitor the old server IP and the new server IP (temporary direct checks)
- Monitor
https://example.comandhttps://www.example.comseparately - Add a keyword check that confirms the correct site version (e.g., “Release: 2026.06.05” in the footer)
For a migration sequence that consistently lands near-zero downtime, follow: Server migration tutorial.
Operational checklist (printable)
- Uptime Kuma behind HTTPS + basic auth
- At least: HTTP check, keyword check, TCP 443 check, cert expiry warning
- Prometheus scraping Node Exporter for each VPS
- Node Exporter port 9100 restricted to monitoring host IP
- Grafana behind HTTPS + basic auth (or VPN-only)
- Alerts: disk %, inode %, memory available %, swap activity, sustained load
- Documented “alert → logs → fix” routine in your ops notes
Summary: your next 60 minutes of monitoring work
Start with Kuma monitors for your storefront and admin endpoints. Then add Node Exporter on the servers that matter most.
After you’ve collected a week of “normal,” adjust thresholds to your workload instead of guessing.
If you’re doing this for customer sites (or multiple WordPress installs), you’ll get the cleanest signal from a dedicated monitoring node on a stable network.
HostMyCode lets you scale from a small HostMyCode VPS to dedicated servers as your monitoring and hosting fleet grows.
If you run client sites, treat monitoring as part of the service—not an add-on. A dedicated monitoring VPS keeps alerts and dashboards online even when a production server is struggling. Start with a HostMyCode VPS, or choose managed VPS hosting if you want help with OS maintenance and secure defaults.
FAQ
Should I run monitoring on the same VPS as my websites?
For a personal site, you can. For client sites or anything revenue-critical, don’t.
If the web VPS runs out of disk or locks up, your monitoring goes down too. You lose visibility when you need it most.
How many servers can one small monitoring VPS handle?
As a practical baseline, a 2 vCPU / 4 GB RAM monitoring VPS can handle about 10–20 Node Exporter targets with 15s–30s scrape intervals and 7–15 days retention.
As you add targets, keep an eye on Prometheus disk usage and memory.
What’s the single most useful alert for WordPress hosting?
Disk space plus inode usage. WordPress updates, caches, and backups can burn through inodes long before the disk “fills.”
Alerts at 70–85% inode usage prevent a lot of late-night outages.
Can I get alerts by email reliably from a VPS?
Yes, but only if you configure DNS (SPF/DKIM/DMARC) and rDNS correctly and avoid sending from a “new” IP with no reputation.
Use the deliverability tutorials linked above before you rely on email paging.
Do I need Grafana if I already have Uptime Kuma?
You can start with Kuma alone, but you’ll still end up guessing root cause.
Grafana + Prometheus pays for itself the first time you correlate a 502 spike with memory pressure or disk saturation.