How to Monitor Your VPS with n8n Health Check Workflows

DevOps #devops#monitoring#n8n#vps#linux
How to Monitor Your VPS with n8n Health Check Workflows

What You’ll Need


Table of Contents

  1. Why VPS Health Monitoring Matters
  2. Setting Up Your First Health Check Workflow
  3. Monitoring CPU, Memory, and Disk Usage
  4. Creating Multi-Server Dashboards
  5. Advanced Alerting with Webhook Integrations
  6. Getting Started

Why VPS Health Monitoring Matters

I learned this the hard way. A year ago, one of my production servers hit 99% disk capacity at 3 AM because a forgotten log file was consuming all available space. I didn’t notice until a client called me saying their API was timing out.

That’s when I realized: manual monitoring isn’t scalable. Whether you’re running a single Hetzner VPS instance or orchestrating multiple Contabo VPS servers, you need automated health checks that alert you before disaster strikes.

n8n is perfect for this. Unlike expensive SaaS monitoring tools, you can build custom health check workflows that integrate directly with your infrastructure, your Slack channel, or any other platform you use. I’ve been using n8n to monitor 7 production servers for the past 18 months, and I’ve caught critical issues before users even knew something was wrong.


Setting Up Your First Health Check Workflow

Let me walk you through building your first n8n health check workflow from scratch. We’ll create a simple workflow that monitors SSH-accessible servers and reports their status.

Step 1: Create Your Workflow Structure

Log into n8n Cloud or your self-hosted instance. Click Create New Workflow and name it “VPS Health Monitor – Basic.”

We’ll build a workflow with this structure:

  • Trigger: Cron Job (runs every 15 minutes)
  • Worker: SSH node to fetch server metrics
  • Processor: Conditional logic to evaluate health
  • Output: Notification node

Step 2: Set Up the Cron Trigger

Click the “+” button to add a new node. Search for and select Cron.

Configure it like this:

{
  "cronExpression": "*/15 * * * *",
  "timezone": "UTC"
}

This triggers every 15 minutes. Adjust the interval based on how frequently you want to check your servers.

Step 3: Add an SSH Node to Connect to Your VPS

Now add an SSH node. You’ll need:

  • Your VPS hostname or IP address
  • SSH credentials (username + private key, or password)
  • The command to retrieve server metrics

For a Hetzner VPS , click Create New Credential and select SSH Private Key (recommended for security).

In the command field, paste this:

echo "UPTIME:$(uptime | awk '{print $1, $2, $3}') CPU:$(grep 'cpu ' /proc/stat | awk '{usage=($2+$4)*100/($2+$4+$5)} END {print int(usage)}') MEMORY:$(free | grep Mem | awk '{printf("%.0f", $3/$2 * 100)}') DISK:$(df / | tail -1 | awk '{printf("%.0f", $3/$2 * 100)}')"

This single command outputs:

  • Uptime
  • CPU usage percentage
  • Memory usage percentage
  • Disk usage percentage

Step 4: Parse the Response

Add a Function node to parse the SSH output into structured data:

const output = $('SSH').item().json.stdout;
const metrics = {};

const parts = output.split(' ');
parts.forEach(part => {
  const [key, value] = part.split(':');
  if (key && value !== undefined) {
    metrics[key] = value;
  }
});

return {
  timestamp: new Date().toISOString(),
  cpu: parseInt(metrics.CPU),
  memory: parseInt(metrics.MEMORY),
  disk: parseInt(metrics.DISK),
  uptime: metrics.UPTIME,
  server: 'production-01'
};

💡 Fast-Track Your Project: Don’t want to configure this yourself? I build custom n8n pipelines and bots. Message me with code SYS3-HUGO.


Monitoring CPU, Memory, and Disk Usage

Now we need to add conditional logic to alert you when metrics cross critical thresholds.

Step 5: Add Conditional Routing

Add an IF node with this condition:

cpu > 80 OR memory > 85 OR disk > 90

This creates two branches: one for alerting, one for normal operation.

Step 6: Send Alerts to Slack

Connect the true branch to a Slack node. Create a new Slack credential if you don’t have one (you’ll need a bot token from your Slack workspace).

Configure the message:

{
  "channel": "#server-alerts",
  "text": "🚨 Health Alert for {{ $node.Function.json.server }}",
  "blocks": [
    {
      "type": "section",
      "text": {
        "type": "mrkdwn",
        "text": "*VPS Health Alert*\n\nServer: {{ $node.Function.json.server }}\nTime: {{ $node.Function.json.timestamp }}\n\n*Metrics:*\nCPU: {{ $node.Function.json.cpu }}%\nMemory: {{ $node.Function.json.memory }}%\nDisk: {{ $node.Function.json.disk }}%"
      }
    },
    {
      "type": "actions",
      "elements": [
        {
          "type": "button",
          "text": {
            "type": "plain_text",
            "text": "SSH into Server"
          },
          "url": "ssh://{{ $node.Function.json.server }}"
        }
      ]
    }
  ]
}

Step 7: Log Success Metrics

For the false branch (all metrics normal), add a PostgreSQL or MySQL node to log the healthy state. This creates a historical record for trend analysis.

If you’re using n8n Cloud, you can also use Airtable as a simple database:

{
  "server": "{{ $node.Function.json.server }}",
  "timestamp": "{{ $node.Function.json.timestamp }}",
  "cpu": {{ $node.Function.json.cpu }},
  "memory": {{ $node.Function.json.memory }},
  "disk": {{ $node.Function.json.disk }},
  "status": "healthy"
}

Creating Multi-Server Dashboards

Once you’ve monitored one server successfully, scaling to multiple servers is straightforward. But you’ll want a way to visualize all that data.

Step 8: Build a Multi-Server Loop

Modify your workflow to accept multiple servers. Add a Set node before the SSH step and define your server list:

{
  "servers": [
    {
      "name": "production-01",
      "hostname": "prod1.example.com",
      "credentialId": "ssh-prod-1"
    },
    {
      "name": "production-02",
      "hostname": "prod2.example.com",
      "credentialId": "ssh-prod-2"
    },
    {
      "name": "staging-01",
      "hostname": "staging.example.com",
      "credentialId": "ssh-staging"
    }
  ]
}

Then wrap your SSH and Function nodes in a Loop that iterates over each server.

Step 9: Create a Consolidated Status Report

Add a final Function node that aggregates all server statuses:

const results = $node.Loop.data.resultData;
const summary = {
  timestamp: new Date().toISOString(),
  total_servers: results.length,
  healthy: results.filter(r => r.json.status === 'healthy').length,
  warning: results.filter(r => r.json.cpu > 70 || r.json.memory > 75).length,
  critical: results.filter(r => r.json.cpu > 85 || r.json.memory > 90 || r.json.disk > 95).length,
  servers: results.map(r => ({
    name: r.json.server,
    cpu: r.json.cpu,
    memory: r.json.memory,
    disk: r.json.disk,
    status: r.json.status
  }))
};

return summary;

Step 10: Send Daily Summary Reports

Add a second Cron node that triggers once daily (at 9 AM):

{
  "cronExpression": "0 9 * * *",
  "timezone": "America/New_York"
}

Route this to a Slack message with your consolidated summary and a Google Sheets update for long-term trend tracking.


Advanced Alerting with Webhook Integrations

Sometimes Slack isn’t enough. You might want alerts in Telegram, PagerDuty, or custom webhooks.

Step 11: Add PagerDuty Integration for Critical Issues

When disk usage hits 95%, you probably want someone on-call to know immediately. Add a PagerDuty node:

{
  "routing_key": "{{ env('PAGERDUTY_ROUTING_KEY') }}",
  "event_action": "trigger",
  "payload": {
    "summary": "CRITICAL: Disk usage {{ $node.Function.json.disk }}% on {{ $node.Function.json.server }}",
    "severity": "critical",
    "source": "n8n Health Monitor",
    "custom_details": {
      "cpu": {{ $node.Function.json.cpu }},
      "memory": {{ $node.Function.json.memory }},
      "disk": {{ $node.Function.json.disk }},
      "timestamp": "{{ $node.Function.json.timestamp }}"
    }
  }
}

Step 12: Add Custom Webhook Alerts

If you have a custom internal monitoring dashboard, add a Webhook node to send metrics:

{
  "method": "POST",
  "url": "https://monitoring.internal.yourcompany.com/api/metrics",
  "authentication": "genericCredentialType",
  "genericAuth": {
    "auth": "header",
    "headerName": "Authorization",
    "headerValue": "Bearer {{ env('MONITORING_API_KEY') }}"
  },
  "body": {
    "server": "{{ $node.Function.json.server }}",
    "metrics": {
      "cpu": {{ $node.Function.json.cpu }},
      "memory": {{ $node.Function.json.memory }},
      "disk": {{ $node.Function.json.disk }}
    },
    "timestamp": "{{ $node.Function.json.timestamp }}",
    "alert": {{ $node.IF.json.alert }}
  },
  "sendQuery": false,
  "sendBody": true,
  "bodyContentType": "application/json"
}

Step 13: Email Notifications for Sustained Issues

Sometimes you want email escalation if an issue persists. Add a counter in your database:

const alertCount = $('Function1').json.cpu > 80 

Want to automate this yourself?

Start with n8n Cloud (free tier available) or self-host on a Hetzner VPS for full control.

📬 Get Weekly Automation Tips

One email per week with tutorials, tools, and workflows. No spam, unsubscribe anytime.

Subscribe Free →