
Your hosting server usually fails in slow motion. Disk usage inches up. Load spikes at the same time every day. Mail queues grow. Then SSH stops responding. This server monitoring tutorial shows you how to spot those signals early on a Linux VPS or dedicated server, using lightweight tools that are safe in production.
The goal isn’t “observability.” It’s coverage you’ll actually use: uptime checks, CPU/RAM/disk alerts, and a few log-based warnings you can act on fast. The setup fits a typical hosting stack (Nginx/Apache, PHP-FPM, WordPress, mail) and keeps overhead low.
What you’ll build (alerting that matches real hosting failures)
- Uptime checks for HTTP/HTTPS and SSH, from outside your server
- Resource alerts for disk, memory pressure, CPU load, and swap churn
- Service checks for Nginx/Apache, PHP-FPM, and your MTA (Postfix/Exim)
- Log-based warnings for repeated auth failures, 5xx bursts, and disk-full conditions
- One notification channel (email via SMTP relay), so alerts actually arrive
If you run customer sites, pair monitoring with a restore-first plan.
Keep a separate runbook for recovery so alerts turn into predictable actions.
If you don’t have one yet, start with this restore-first disaster recovery runbook.
Prerequisites (and the baseline you should not skip)
This tutorial assumes a Linux server you control:
- Ubuntu 24.04 LTS or 26.04 LTS, Debian 12/13, AlmaLinux 9/10, or Rocky Linux 9/10
- Root access (or a sudo user)
- One domain (optional) for HTTPS monitoring
Before you add monitoring, tighten admin access.
If you still log in as root over SSH, fix that first: sudo setup guide for creating a non-root admin user.
Hosting note: monitoring works best with predictable resources.
In noisy shared environments, symptoms can stay hidden until things break.
For consistent performance and full control, use a HostMyCode VPS (or go managed if you want help with server-side maintenance).
Architecture: one tiny agent + one external checker
You’ll monitor from the inside (an agent) and from the outside (black-box checks). You want both.
- External checks show what users see (site down, TLS broken, DNS issues).
- Internal checks show why (disk full, memory pressure, service crash, queue backlog).
We’ll use:
- Uptime Kuma (external-ish) to run checks from a separate monitor node
- Monit (internal) to alert on load, disk, processes, and ports
- journalctl + simple log grep to flag patterns that correlate with hosting incidents
Why these in 2026? They’re stable and easy to audit.
They also avoid pulling you into a full metrics stack.
Step 1: Create a separate “monitor node” (recommended)
Don’t run uptime checks on the same server you’re checking.
If the server is down, your checker is down too.
The smallest reliable setup is a second tiny VPS (1 vCPU, 1 GB RAM) in a different location/provider.
- Provision a small VPS as the monitor node.
- Harden SSH and firewall basics.
- Install Uptime Kuma on the monitor node.
If you want the monitoring node managed (patching and baseline hardening handled), consider managed VPS hosting for the monitor box and keep your production server self-managed.
Step 2: Install Uptime Kuma on the monitor node
Uptime Kuma runs HTTP/TCP/ping checks and sends notifications.
In 2026, Docker is still the quickest install path.
On the monitor node (Ubuntu/Debian):
sudo apt update
sudo apt install -y docker.io docker-compose-plugin
sudo systemctl enable --now docker
Create a directory and compose file:
sudo mkdir -p /opt/uptime-kuma
cd /opt/uptime-kuma
sudo nano compose.yml
Paste:
services:
uptime-kuma:
image: louislam/uptime-kuma:latest
container_name: uptime-kuma
restart: unless-stopped
ports:
- "3001:3001"
volumes:
- ./data:/app/data
Start it:
sudo docker compose up -d
sudo docker ps
Then open http://MONITOR_NODE_IP:3001, set an admin password, and create your first monitor.
Checks to add immediately:
- HTTPS monitor for your main site URL
- TCP port monitor for
22(SSH) - TCP port monitor for
443(HTTPS)
Tip: add a monitor for /wp-login.php (WordPress) only if you already protect it.
Otherwise you’ll just invite bot traffic.
Step 3: Configure clean alert delivery (SMTP relay, not “local sendmail”)
If the server is having mail trouble, relying on local mail for alerts is a mistake.
Route alerts through a reputable SMTP relay (or your existing mail provider).
Do this on the monitoring node and on the production server.
If you’re on cPanel, follow this cPanel SMTP relay setup tutorial.
On a plain Linux server, install msmtp for outbound notifications:
sudo apt update
sudo apt install -y msmtp msmtp-mta ca-certificates
Create /etc/msmtprc:
sudo nano /etc/msmtprc
# /etc/msmtprc
defaults
auth on
tls on
tls_trust_file /etc/ssl/certs/ca-certificates.crt
logfile /var/log/msmtp.log
account relay
host smtp.yourprovider.com
port 587
from alerts@yourdomain.com
user alerts@yourdomain.com
password YOUR_APP_PASSWORD
account default : relay
Lock permissions:
sudo chown root:root /etc/msmtprc
sudo chmod 600 /etc/msmtprc
Quick send test:
printf "Subject: msmtp test\n\nAlert path OK\n" | msmtp you@yourdomain.com
If your tests fail due to STARTTLS/cert issues, fix that before you go any further.
Use this SMTP TLS troubleshooting guide to resolve port 587 and certificate problems.
Step 4: Install Monit on your hosting VPS (internal checks)
Monit runs on the server, checks system health, and emails you when something crosses a threshold.
It’s a good fit for “disk hit 90%” and “PHP-FPM stopped” incidents.
Ubuntu/Debian:
sudo apt update
sudo apt install -y monit
Alma/Rocky:
sudo dnf install -y epel-release
sudo dnf install -y monit
Enable and start:
sudo systemctl enable --now monit
sudo systemctl status monit --no-pager
Main config files:
/etc/monit/monitrc(global settings)/etc/monit/conf-enabled/or/etc/monit.d/(per-check configs; varies by distro)
Step 5: Configure Monit email notifications
Edit /etc/monit/monitrc:
sudo nano /etc/monit/monitrc
Add or adjust:
set daemon 30
set logfile syslog
set mailserver localhost
set alert you@yourdomain.com only on { nonexist, timeout, resource, icmp, connection }
set mail-format {
from: monit@yourhostname
subject: [monit] $SERVICE $EVENT on $HOST
message: $EVENT Service $SERVICE
Date: $DATE
Action: $ACTION
Host: $HOST
Description: $DESCRIPTION
}
If you used msmtp-mta, sending through localhost usually works.
That package provides a sendmail-compatible wrapper.
Restart Monit:
sudo monit reload
sudo monit status
Quick verification: temporarily set a low disk threshold (like 1%) on a test mount.
Reload Monit and confirm you receive an alert. Then put the threshold back.
Step 6: Add resource checks that catch 80% of outages
Create a new file (Debian/Ubuntu commonly uses /etc/monit/conf-enabled):
sudo nano /etc/monit/conf-enabled/system-health
Paste a sane baseline for a hosting VPS:
check system $HOST
if loadavg (1min) > 6 for 5 cycles then alert
if loadavg (5min) > 4 for 10 cycles then alert
if memory usage > 85% for 5 cycles then alert
if swap usage > 30% for 10 cycles then alert
check filesystem rootfs with path /
if space usage > 85% then alert
if space usage > 92% then alert
if inode usage > 85% then alert
check directory varlog with path /var/log
if changed timestamp then alert
Reload Monit:
sudo monit reload
sudo monit validate
How to tune the load thresholds:
- For 1 vCPU: alert around 2–3 sustained
- For 2 vCPU: alert around 4–6 sustained
- For 4 vCPU: alert around 8–12 sustained
Don’t chase perfect numbers.
You want an early warning, before users feel it.
Step 7: Add service checks for web hosting (Nginx/Apache + PHP-FPM)
Next, monitor the services your sites rely on.
Create:
sudo nano /etc/monit/conf-enabled/web-stack
For Nginx + PHP-FPM:
check process nginx with pidfile /run/nginx.pid
start program = "/usr/bin/systemctl start nginx"
stop program = "/usr/bin/systemctl stop nginx"
if failed port 80 protocol http then restart
if failed port 443 type tcp then restart
if 5 restarts within 5 cycles then alert
check process php-fpm with matching "php-fpm"
start program = "/usr/bin/systemctl start php8.3-fpm"
stop program = "/usr/bin/systemctl stop php8.3-fpm"
if failed unixsocket /run/php/php8.3-fpm.sock then restart
if 5 restarts within 5 cycles then alert
If you run Apache instead:
check process apache2 with pidfile /run/apache2/apache2.pid
start program = "/usr/bin/systemctl start apache2"
stop program = "/usr/bin/systemctl stop apache2"
if failed port 80 protocol http then restart
if 5 restarts within 5 cycles then alert
Important: match the PHP-FPM version and service name to your system (php8.2-fpm, php8.3-fpm, etc.).
Confirm with:
systemctl list-units --type=service | grep -E 'php.*fpm'
Reload and validate:
sudo monit reload
sudo monit summary
Step 8: Add mail service checks (so you catch queue explosions)
Even if you don’t “host email,” your sites still send mail.
Contact forms and password resets depend on outbound delivery.
A stuck queue often shows up before the first support ticket.
Create:
sudo nano /etc/monit/conf-enabled/mail
For Postfix:
check process postfix with pidfile /var/spool/postfix/pid/master.pid
start program = "/usr/bin/systemctl start postfix"
stop program = "/usr/bin/systemctl stop postfix"
if failed port 25 type tcp then alert
check program mailq with path "/usr/sbin/postqueue -p"
every 5 cycles
Monit isn’t great at parsing queue depth directly.
Pair it with a simple cron + alert script (next step).
If you’re already dealing with backlogs, keep this mail queue troubleshooting tutorial bookmarked.
Step 9: Add log-based alerts with simple scripts (auth failures, 5xx spikes, disk-full)
Resource alerts cover the obvious failures. Logs catch the messy ones.
A compromised WordPress site can hammer endpoints without pegging CPU.
Credential stuffing can flood auth logs and trigger lockouts. Logs show what’s being attempted.
Create a small script directory:
sudo mkdir -p /opt/monitoring/scripts
sudo chown root:root /opt/monitoring/scripts
sudo chmod 700 /opt/monitoring/scripts
A) SSH auth failure burst (journal-based, systemd systems):
sudo nano /opt/monitoring/scripts/check-ssh-failures.sh
#!/bin/bash
set -euo pipefail
WINDOW_MIN=10
THRESHOLD=40
COUNT=$(journalctl -u ssh -S "${WINDOW_MIN} min ago" 2>/dev/null | grep -c "Failed password" || true)
if [ "$COUNT" -ge "$THRESHOLD" ]; then
printf "Subject: [alert] SSH failures spike (%s in %s min)\n\nHost: %s\nCount: %s\n" "$COUNT" "$WINDOW_MIN" "$(hostname -f)" "$COUNT" | /usr/sbin/sendmail you@yourdomain.com
fi
Make executable:
sudo chmod 700 /opt/monitoring/scripts/check-ssh-failures.sh
If you suspect brute force already, follow a targeted fix path: SSH brute-force troubleshooting.
B) Nginx 5xx burst (adjust paths for Apache):
sudo nano /opt/monitoring/scripts/check-5xx.sh
#!/bin/bash
set -euo pipefail
LOG="/var/log/nginx/access.log"
WINDOW_MIN=5
THRESHOLD=80
if [ ! -f "$LOG" ]; then
exit 0
fi
SINCE=$(date -d "${WINDOW_MIN} min ago" "+%d/%b/%Y:%H:%M")
COUNT=$(awk -v since="$SINCE" '$4 >= "["since { if ($9 ~ /^5/) c++ } END { print c+0 }' "$LOG")
if [ "$COUNT" -ge "$THRESHOLD" ]; then
printf "Subject: [alert] HTTP 5xx spike (%s in %s min)\n\nHost: %s\nLog: %s\n" "$COUNT" "$WINDOW_MIN" "$(hostname -f)" "$LOG" | /usr/sbin/sendmail you@yourdomain.com
fi
Enable:
sudo chmod 700 /opt/monitoring/scripts/check-5xx.sh
C) Disk almost full (hard stop before 100%):
sudo nano /opt/monitoring/scripts/check-disk.sh
#!/bin/bash
set -euo pipefail
THRESHOLD=92
USED=$(df -P / | awk 'NR==2 {gsub(/%/,"",$5); print $5}')
if [ "$USED" -ge "$THRESHOLD" ]; then
printf "Subject: [alert] Disk usage critical (%s%%)\n\nHost: %s\nMount: /\n" "$USED" "$(hostname -f)" | /usr/sbin/sendmail you@yourdomain.com
fi
sudo chmod 700 /opt/monitoring/scripts/check-disk.sh
If disk bloat is already happening, fix it methodically rather than deleting random files.
Use: VPS cleanup tutorial for logs, cache, and backups.
Schedule these checks via cron:
sudo crontab -e
*/10 * * * * /opt/monitoring/scripts/check-ssh-failures.sh
*/5 * * * * /opt/monitoring/scripts/check-5xx.sh
*/5 * * * * /opt/monitoring/scripts/check-disk.sh
Step 10: Add one “known-good” HTTP endpoint (so checks mean something)
Uptime checks that hit “/” can give false confidence.
A PHP fatal error can still return a cached 200.
Add a tiny endpoint that exercises the stack you care about.
For Nginx + PHP-FPM, create /var/www/healthcheck.php:
sudo nano /var/www/healthcheck.php
<?php
header('Content-Type: text/plain');
echo "ok\n";
echo "time=" . time() . "\n";
Then add a server/location block that maps /health to this file and restricts it to your monitor node IP.
Example snippet (conceptual):
location = /health {
allow MONITOR_NODE_IP;
deny all;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME /var/www/healthcheck.php;
}
Reload Nginx:
sudo nginx -t && sudo systemctl reload nginx
Add an Uptime Kuma HTTP(s) monitor for https://yourdomain.com/health.
Now you’ll detect a broken PHP-FPM socket, not just a running web server process.
Step 11: Create an alert checklist (so you don’t improvise at 3 a.m.)
Alerts only matter if they point to a next action.
Keep this checklist in your password manager or runbook.
- Disk > 92%: run
du -xh /var | sort -h | tail, clear safe targets (old logs, cache), confirm backups aren’t looping. - Load spike: check
top,ps aux --sort=-%cpu | head, then Nginx access logs for abusive IPs. - Memory pressure: check
free -h, PHP-FPM children count, and MySQL/MariaDB memory settings if applicable. - 5xx burst: check app logs first, then PHP-FPM status, then upstream timeouts.
- SSH failures spike: confirm it’s not your own automation, then block by IP/range or tighten access.
If you want to cut brute-force traffic without blocking real users, add a controlled edge rule.
Nginx-based sites can use Nginx rate limiting tuned for login pages and XML-RPC.
Step 12: Test failure modes (prove the monitoring works)
Don’t trust green dashboards.
Trigger a few safe failures during a quiet window, then confirm you get the right alerts.
- Stop PHP-FPM:
sudo systemctl stop php8.3-fpm. Confirm Monit alerts and Uptime Kuma health endpoint fails. - Break DNS temporarily (on a non-production domain) and confirm external checks go red.
- Fill disk slightly in
/tmpwith a test file and confirm the disk script triggers before 100%. - Simulate 5xx by pointing a vhost to a missing upstream, then revert.
After testing, revert everything and write down what surprised you.
If restores or rollbacks feel uncertain, fix that while it’s fresh.
Troubleshooting: common monitoring pitfalls on hosting servers
- Alerts never arrive: fix SMTP first. Check
/var/log/msmtp.log(or mail logs) and confirm outbound port 587 is allowed. - Too many alerts: raise thresholds and add “for X cycles” rules. Paging on every 1-minute load blip trains you to ignore alerts.
- Uptime checks show up but real users still complain: monitor a real endpoint like
/healththat touches PHP-FPM and (optionally) the database. - Disk usage alerts but you can’t find the culprit: look for inodes and hidden files. Use
df -iand check deleted-but-open files vialsof | grep deleted.
Summary: a practical monitoring baseline you can run on any VPS
This setup combines external uptime checks, local resource alerts, and a few log-based warnings.
It avoids extra infrastructure and stays intentionally plain.
It also catches failures that tend to cost money: disk-full outages, crashed PHP workers, mail backlogs, and persistent attacks.
If you want predictable performance for these checks (and enough headroom for traffic spikes), run your sites on a HostMyCode VPS.
If you’d rather have patching and core maintenance handled while you focus on sites and customers, use managed VPS hosting from HostMyCode (Affordable & Reliable Hosting).
If you’re building a monitoring baseline for client sites or production WordPress, start with a VPS that delivers consistent CPU and disk performance. HostMyCode offers VPS hosting for hands-on admins and managed VPS hosting when you want the platform maintained while you manage the application layer.
FAQ
Do I really need a separate monitoring node?
For production, yes. If your only server is down or network-isolated, internal checks can’t notify you.
A tiny external node catches outages users see.
What should I alert on first if I want the minimum viable setup?
Disk > 85% (warning) and > 92% (critical), HTTPS uptime, and a health endpoint that hits PHP-FPM.
Those three catch a surprising share of hosting incidents.
How do I monitor TLS expiry without writing scripts?
Uptime Kuma supports certificate expiry warnings on HTTPS monitors.
Set the threshold (for example, 14 days) so you have time to fix renewal issues.
My server runs cPanel/WHM. Can I still use Monit and Uptime Kuma?
Yes. Use Uptime Kuma externally, and keep Monit focused on system resources and key services.
For panel-side issues like AutoSSL failures, use your panel’s native tooling and targeted troubleshooting guides.
What’s the best next step after monitoring is in place?
Prove you can recover. Pair alerts with restore tests and a short runbook so every “disk full” or “service down” page has a repeatable fix.