How I Run 3 Automated Systems on a Single $7/Month VPS

n8n #vps#automation#n8n#selfhosted#sideproject
How I Run 3 Automated Systems on a Single $7/Month VPS

What You’ll Need


Table of Contents


The Reality of Running Multiple Systems on Minimal Infrastructure

I manage three separate automation workflows on a $7/month VPS from Contabo VPS , and honestly, it’s become my favorite experiment in lean operations. Before you roll your eyes thinking this is some clickbait nonsense—it’s not. It works. But it requires intentional architecture choices.

Most people assume you need separate servers, managed cloud services, or at minimum a $20+ VPS tier. I’m here to tell you that’s marketing speak. I’ve consolidated workflows that would typically cost $60–80 per month in SaaS fees into a single machine. The key isn’t magic; it’s understanding resource constraints and designing workflows that respect them.

When I started, I had a naive approach: just install n8n on a cheap server and throw everything at it. The VPS crawled. CPU usage spiked unpredictably. Workflows would timeout. Then I learned what actually matters: memory footprint, database efficiency, and intelligent scheduling. This post walks through exactly how I solved each problem.


System 1: Lead Capture to CRM Pipeline

My first automated system grabs leads from a web form, validates them, and pipes them into my CRM—no manual touch. This runs 24/7 and is the most critical workflow.

Setting Up n8n on Your VPS

First, SSH into your Contabo VPS and get n8n installed:

curl -fsSL https://raw.githubusercontent.com/n8n-io/n8n/master/packages/node-dev/bin/n8n | bash

Create a dedicated user for n8n to avoid permission headaches:

sudo useradd -m -s /bin/bash n8n
sudo su - n8n

Install Node.js version 18 (n8n’s sweet spot for performance):

curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs

Install n8n globally:

npm install -g n8n

Create a systemd service file so n8n starts automatically:

sudo nano /etc/systemd/system/n8n.service

Paste this configuration:

[Unit]
Description=n8n Workflow Automation
After=network.target

[Service]
Type=simple
User=n8n
WorkingDirectory=/home/n8n
Environment="NODE_ENV=production"
Environment="N8N_PORT=5678"
Environment="N8N_PROTOCOL=https"
Environment="N8N_HOST=your-domain.com"
Environment="N8N_SECURE_COOKIE=true"
Environment="N8N_DB_TYPE=sqlite"
Environment="N8N_DB_SQLITE_DB_FILE=/home/n8n/.n8n/database.sqlite"
ExecStart=/usr/bin/n8n start
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Enable and start the service:

sudo systemctl daemon-reload
sudo systemctl enable n8n
sudo systemctl start n8n

Set up Nginx as a reverse proxy (this protects n8n and handles SSL):

sudo apt-get install -y nginx certbot python3-certbot-nginx

Create an Nginx config:

sudo nano /etc/nginx/sites-available/n8n

Add this:

server {
    listen 80;
    server_name your-domain.com;

    location / {
        proxy_pass http://127.0.0.1:5678;
        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_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Enable the site and get SSL:

sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/n8n
sudo certbot certonly --standalone -d your-domain.com

Update Nginx to use HTTPS:

sudo nano /etc/nginx/sites-available/n8n

Replace with:

server {
    listen 80;
    server_name your-domain.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name your-domain.com;

    ssl_certificate /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    location / {
        proxy_pass http://127.0.0.1:5678;
        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 https;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }
}

Reload Nginx:

sudo systemctl reload nginx

The Lead Workflow

Inside n8n, I built this workflow that’s been processing 50–100 leads daily:

  1. Webhook trigger – listens for form submissions
  2. Validation node – checks email format and required fields
  3. Deduplication – queries SQLite to prevent duplicate entries
  4. CRM HTTP call – POSTs to my CRM API
  5. Slack notification – alerts me of new qualified leads

Here’s the actual workflow JSON structure (simplified for clarity, but production-ready):

{
  "nodes": [
    {
      "parameters": {
        "path": "webhook/lead-capture",
        "responseMode": "onReceived",
        "responseData": "customJson",
        "responseValue": "{\"status\": \"received\"}"
      },
      "id": "webhook-trigger",
      "name": "Lead Form Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [250, 300]
    },
    {
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "{{ $json.email }}",
              "operation": "regex",
              "value2": "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"
            },
            {
              "value1": "{{ $json.name }}",
              "operation": "isEmpty",
              "value2": null
            }
          ]
        }
      },
      "id": "if-valid",
      "name": "Validate Email & Name",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [550, 300]
    },
    {
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT COUNT(*) as count FROM leads WHERE email = '{{ $json.email }}' AND created_at > datetime('now', '-7 days')",
        "database": "/home/n8n/.n8n/database.sqlite"
      },
      "id": "check-duplicate",
      "name": "Check Duplicate (Last 7 Days)",
      "type": "n8n-nodes-base.sqlite",
      "typeVersion": 1,
      "position": [850, 300]
    },
    {
      "parameters": {
        "url": "https://api.your-crm.com/leads",
        "method": "POST",
        "headers": {
          "Authorization": "Bearer {{ $env.CRM_API_KEY }}",
          "Content-Type": "application/json"
        },
        "body": {
          "name": "{{ $json.name }}",
          "email": "{{ $json.email }}",
          "phone": "{{ $json.phone }}",
          "message": "{{ $json.message }}",
          "source": "web_form"
        }
      },
      "id": "post-to-crm",
      "name": "Send to CRM",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.1,
      "position": [1150, 300]
    },
    {
      "parameters": {
        "text": "🎯 New Lead: {{ $json.name }} ({{ $json.email }})"
      },
      "id": "slack-notify",
      "name": "Slack Alert",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2,
      "position": [1450, 300]
    }
  ],
  "connections": {
    "webhook-trigger": {
      "main": [
        [
          {
            "node": "if-valid",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "if-valid": {
      "main": [
        [
          {
            "node": "check-duplicate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "check-duplicate": {
      "main": [
        [
          {
            "node": "post-to-crm",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "post-to-crm": {
      "main": [
        [
          {
            "node": "slack-notify",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}

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

The deduplication step is crucial on a shared VPS. Instead of spamming your CRM or burning API rate limits, I query SQLite locally first. This is where choosing the right database matters—I cover this decision deeper in my guide on SQLite vs PostgreSQL for Small Projects: When to Use Which .


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 →