Small and medium business teams do not need enterprise-grade observability platforms to achieve strong reliability. They need the right metrics, actionable alerts, clear dashboards, and a repeatable incident response loop. This checklist covers host metrics, application metrics, uptime monitoring, log aggregation, alert routing, on-call setup, and a practical tool comparison so your team can build a monitoring program that catches problems before customers do.
Why Monitoring Matters for SMBs
Without monitoring, your customers are your alerting system. They find the outage, they experience the slowness, and they leave. A basic monitoring setup for an SMB costs $0-50/month and catches 90% of issues before they become customer-visible.
The three goals of any monitoring program:
Host Metrics Checklist
Every server in your infrastructure needs these five categories of host-level metrics:
CPU
<h1>Key metrics to track</h1>
cpu_usage_percent # Overall CPU utilization
cpu_iowait_percent # Time waiting for disk I/O (high = disk bottleneck)
cpu_steal_percent # Time stolen by hypervisor (high = noisy neighbor on VPS)
load_average_1m # 1-minute load average vs core count
- Alert threshold: CPU above 85% for 5+ minutes.
- Investigate: CPU steal above 10% means your VPS host is overcommitted.
RAM
memory_used_percent # Actual memory usage
memory_available_bytes # Memory available to applications
swap_used_percent # Swap usage (any sustained swap = problem)
oom_kill_count # OOM killer invocations
- Alert threshold: Available memory below 15% for 5+ minutes. Any OOM kill is a SEV2.
- Action: If swap is consistently used, you need more RAM or a memory leak investigation.
Disk
disk_used_percent # Per-mount usage
disk_iops_read # Read operations per second
disk_iops_write # Write operations per second
disk_latency_ms # Average I/O latency
inode_used_percent # Inode usage (can run out before disk space)
- Alert threshold: Disk above 80% or inodes above 80%. Disk latency above 20ms sustained.
- Critical: Disk at 95% is an emergency -- services will crash.
Network
network_bytes_in # Incoming bandwidth
network_bytes_out # Outgoing bandwidth
network_errors # Interface errors
network_drops # Dropped packets
tcp_connections_active # Active TCP connections
- Alert threshold: Network errors or drops above 0 sustained. Connection count exceeding expected baseline by 3x.
Saturation
cpu_runqueue_length # Processes waiting for CPU
disk_queue_length # I/O requests waiting
network_socket_overflow # TCP socket overflows
- Why track saturation: A resource can be at 50% utilization but fully saturated if requests are queuing. Saturation metrics catch bottlenecks that utilization metrics miss.
Application Metrics Checklist
Host metrics tell you if the server is healthy. Application metrics tell you if the business is healthy.
The Four Golden Signals
| Signal | What it measures | Example metric | Alert condition |
| Request rate | Traffic volume | `http_requests_total` | Drop below 50% of baseline |
| Error rate | Failure percentage | `http_5xx_total / http_requests_total` | Above 1% for 5 min |
| Latency | Response time | `http_request_duration_p95` | P95 above 500ms |
| Saturation | Resource pressure | `thread_pool_active / thread_pool_max` | Above 80% |
Instrumenting Your Application
For a Node.js/Express application:
// Prometheus metrics middleware
const promClient = require("prom-client");
const httpRequestDuration = new promClient.Histogram({
name: "http_request_duration_seconds",
help: "Duration of HTTP requests in seconds",
labelNames: ["method", "route", "status_code"],
buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5]
});
const httpRequestTotal = new promClient.Counter({
name: "http_requests_total",
help: "Total HTTP requests",
labelNames: ["method", "route", "status_code"]
});
app.use((req, res, next) => {
const end = httpRequestDuration.startTimer();
res.on("finish", () => {
end({ method: req.method, route: req.route?.path || req.path, status_code: res.statusCode });
httpRequestTotal.inc({ method: req.method, route: req.route?.path || req.path, status_code: res.statusCode });
});
next();
});
// Expose /metrics endpoint
app.get("/metrics", async (req, res) => {
res.set("Content-Type", promClient.register.contentType);
res.end(await promClient.register.metrics());
});
For Python/FastAPI:
from prometheus_client import Counter, Histogram, generate_latest
from starlette.middleware.base import BaseHTTPMiddleware
import time
REQUEST_COUNT = Counter("http_requests_total", "Total requests", ["method", "endpoint", "status"])
REQUEST_LATENCY = Histogram("http_request_duration_seconds", "Request latency", ["method", "endpoint"])
class MetricsMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
start = time.time()
response = await call_next(request)
duration = time.time() - start
REQUEST_COUNT.labels(request.method, request.url.path, response.status_code).inc()
REQUEST_LATENCY.labels(request.method, request.url.path).observe(duration)
return response
app.add_middleware(MetricsMiddleware)
@app.get("/metrics")
async def metrics():
return Response(generate_latest(), media_type="text/plain")
Uptime Monitoring
External uptime monitoring checks your services from outside your infrastructure. If your server is down, internal monitoring cannot alert you.
What to Monitor Externally
- Homepage and critical pages: HTTP 200 check every 60 seconds.
- API health endpoint: JSON response validation every 30 seconds.
- SSL certificate expiry: Alert 30 days before expiration.
- DNS resolution: Verify A/CNAME records resolve correctly.
- Key user flows: Synthetic transactions (login, checkout) every 5 minutes.
<h1>Simple uptime check script</h1>
#!/bin/bash
ENDPOINTS=(
"https://example.com|200"
"https://example.com/api/health|200"
"https://api.example.com/v1/status|200"
)
for entry in "${ENDPOINTS[@]}"; do
url=$(echo "$entry"
cut -d"
" -f1)
expected=$(echo "$entry"
cut -d"
" -f2)
status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "$url")
if [[ "$status" != "$expected" ]]; then
echo "ALERT: $url returned $status (expected $expected)"
# Send alert via webhook, email, or PagerDuty
fi
done
Log Aggregation
Logs are the narrative of what happened. Metrics tell you something is wrong; logs tell you why.
Log Aggregation Stack Options
| Stack | Components | Best for | Monthly cost (self-hosted) |
| Loki + Grafana | Promtail, Loki, Grafana | Teams already using Grafana | $0 (OSS) + compute |
| ELK | Filebeat, Elasticsearch, Kibana | Full-text search heavy use | $0 (OSS) + 4GB+ RAM |
| Vector + ClickHouse | Vector, ClickHouse, Grafana | High volume, cost-sensitive | $0 (OSS) + compute |
| Managed (Datadog/New Relic) | SaaS | Teams that cannot self-host | $50-200/month |
For most SMBs, Loki plus Grafana is the best starting point. It integrates with Prometheus dashboards and uses minimal resources.
Structured Logging Best Practices
{"timestamp":"2026-04-01T12:00:00Z","level":"error","service":"api","message":"payment processing failed","error_code":"STRIPE_TIMEOUT","user_id":12345,"duration_ms":5000,"trace_id":"abc123"}
Rules for effective logging:
- Always use structured format (JSON), never free-text.
- Include a trace ID for request correlation across services.
- Log at the right level: ERROR for failures, WARN for degradation, INFO for business events.
- Never log passwords, tokens, PII, or credit card numbers.
- Set retention policies: 7 days hot, 30 days warm, 90 days cold.
Alert Routing and On-Call Setup
Alerts are useless if they go to the wrong person or drown in noise.
Alert Severity Framework
| Severity | Definition | Response time | Notification | Example |
| SEV1 | Customer-facing outage | 5 minutes | Phone + SMS + Slack | Site down, data loss |
| SEV2 | Degraded but functional | 30 minutes | Slack + email | High error rate, slow API |
| SEV3 | Non-urgent issue | Next business day | Email or ticket | Disk at 75%, cert expiring |
Alert Routing Rules
<h1>Example PagerDuty / Alertmanager routing</h1>
routes:
- match:
severity: sev1
receiver: oncall-phone
repeat_interval: 5m
- match:
severity: sev2
receiver: oncall-slack
repeat_interval: 30m
- match:
severity: sev3
receiver: team-email
repeat_interval: 24h
On-Call for Small Teams
Teams of 2-5 engineers can sustain on-call with these rules:
- Rotate weekly. Each person is on-call 1 week out of N (where N is team size).
- Primary plus secondary on-call. Secondary is backup if primary does not acknowledge in 10 minutes.
- Compensate on-call time. Either paid stipend or time off in lieu.
- Review every alert in a weekly meeting. If an alert did not require action, tune or remove it.
Monitoring Tool Comparison
| Tool | Type | Free tier | Best for | Complexity |
| Prometheus + Grafana | Metrics + dashboards | Unlimited (OSS) | Teams with Kubernetes | Medium |
| Uptime Kuma | Uptime monitoring | Unlimited (OSS) | Simple uptime checks | Low |
| Netdata | Host monitoring | Unlimited (OSS) | Single-server visibility | Low |
| Datadog | Full observability | 5 hosts free | Teams wanting SaaS | Low |
| New Relic | Full observability | 100 GB/month | APM-focused teams | Low |
| Grafana Cloud | Metrics + logs | 10K metrics, 50 GB logs | Grafana-native teams | Medium |
| UptimeRobot | External uptime | 50 monitors | Basic uptime alerting | Very low |
Review how these tools integrate with your infrastructure at DevOps Tools and Services Overview.
Dashboard Design
Build three dashboards and resist the urge to add more until these are battle-tested:
1. Executive Health Dashboard
Shows overall system status. Green/yellow/red for each service. Uptime percentage for the month. Error budget remaining.
2. On-Call Triage Dashboard
Shows the four golden signals for each service. Active alerts with severity and duration. Recent deployments timeline. Quick links to logs and runbooks.
3. Capacity Planning Dashboard
Shows resource usage trends over 30/60/90 days. Projected exhaustion dates for disk, memory, and CPU. Cost trends and forecasts.
Weekly and Monthly Cadence
Weekly (30 minutes)
- Review all alerts that fired: were they actionable? Tune noisy alerts.
- Check for any unresolved SEV3 alerts.
- Verify backup and restore jobs completed successfully.
Monthly (1 hour)
- Review SLO performance (did we meet our uptime and latency targets?).
- Capacity planning: any resources projected to exhaust within 60 days?
- Run one disaster recovery drill or restore test.
- Update runbooks based on recent incidents.
Frequently Asked Questions
How many alerts should a small team have?
Start with 5-10 alerts total. Each alert should map to a specific runbook and a specific action. If an engineer gets paged and the answer is "do nothing" or "I do not know what to do," the alert needs to be fixed or removed. Alert fatigue is the number one cause of monitoring failure in small teams.
Do I need APM (Application Performance Monitoring)?
Not initially. Start with the four golden signals (request rate, error rate, latency, saturation) using Prometheus or a similar tool. Add APM (distributed tracing, code-level profiling) when you have performance problems that the golden signals detect but cannot diagnose.
What should I monitor first if I have nothing today?
Set up three things in this order: (1) external uptime monitoring with UptimeRobot or similar, (2) host metrics with Netdata or node_exporter, (3) application error rate from your logs. These three give you 80% of the value for 20% of the effort.
How do I avoid alert fatigue?
Three rules: alert on symptoms (user impact) not causes (CPU usage), require every alert to have a runbook, and review alert noise weekly. Delete any alert that fires more than twice without requiring action.
Should I use SaaS monitoring or self-hosted?
If your team is under 5 engineers and nobody enjoys infrastructure work, use SaaS (Datadog, New Relic, Grafana Cloud). The free tiers are often sufficient for SMB needs. Self-host only if you have the skills and want to avoid vendor lock-in or data residency requirements.
Final Takeaway
A lightweight but disciplined monitoring program improves reliability faster than adding more tools. Start with host metrics, the four golden signals, external uptime checks, and 5-10 actionable alerts. Review weekly, plan capacity monthly, and iterate based on real incidents.
For implementation support, contact us.