What You'll Need
- n8n Cloud or self-hosted n8n
- Hetzner VPS or Contabo VPS for hosting
- Namecheap if domain needed
- SSH client (built into Mac/Linux, PuTTY for Windows)
- Basic command-line familiarity
Table of Contents
- The Reality of Running Multiple Systems on Minimal Infrastructure
- System 1: Lead Capture to CRM Pipeline
- System 2: Daily Content Publishing Scheduler
- System 3: Performance Monitoring and Alert Relay
- Resource Optimization Strategies
- Getting Started with Your Own Setup
The Reality of Running Multiple Systems on Minimal Infrastructure {#reality}
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 {#system-1-lead-capture}
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:
- Webhook trigger – listens for form submissions
- Validation node – checks email format and required fields
- Deduplication – queries SQLite to prevent duplicate entries
- CRM HTTP call – POSTs to my CRM API
- 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-DEVTO.
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.
Originally published on Automation Insider.
Top comments (0)