Self-Hosting n8n on a Budget VPS: A Production-Ready Setup Guide
When I first started exploring workflow automation tools back in 2021, I quickly realized that paying $20-50/month for hosted automation platforms adds up fast—especially when you're running side projects, client pipelines, and personal experiments simultaneously. That's when I discovered n8n, the fair-code workflow automation powerhouse, and decided to take the self-hosting route.
Over the past three years, I've deployed n8n on everything from a Raspberry Pi to enterprise-grade Kubernetes clusters. But the sweet spot I've found—and what I recommend to most clients—is a $5 VPS running Docker with Cloudflare Tunnels for secure access and PM2 for process management. It's the kind of setup that costs less than a coffee per week but handles production workloads reliably.
In this guide, I'll walk you through the exact n8n VPS setup I use for my own automation infrastructure, including the gotchas that aren't obvious from the official documentation.
Why Self-Host n8n Instead of Using n8n Cloud?
Before we dive into the technical setup, let's talk about why n8n self-hosted makes sense for developers and small teams:
- Cost efficiency: $5-10/month vs $20-50/month for hosted plans with similar execution limits
- Data sovereignty: Your workflows, credentials, and execution data never leave your server
- Unlimited executions: Most cloud plans cap you at 1,000-10,000 executions per month
- Custom integrations: Full access to the filesystem means you can run custom nodes and scripts
- Learning value: You'll understand Docker, reverse proxies, and process management better
The trade-off is operational responsibility—but as a DevOps engineer, that's part of the fun.
Prerequisites: What You'll Need
Before we start the n8n production deployment, gather these essentials:
- A VPS provider — I recommend Hetzner, DigitalOcean, or Vultr for their $4-6/month tiers with good performance
- A domain name — Even a cheap $1 domain from Namecheap works fine
- Cloudflare account — Free tier is sufficient for tunnel access
- Basic Linux knowledge — Comfort with SSH and command-line editing
- About 45 minutes — That's realistically how long this takes the first time
For this guide, I'll assume you're using Ubuntu 22.04 LTS as your server OS. The steps are nearly identical for Debian.
Step 1: Initial VPS Hardening and Docker Installation
Once you SSH into your fresh VPS, the first thing I always do is harden the basics. Skip this step at your own risk—I've seen too many self-hosted instances compromised because of lazy initial setup.
# Update the system
sudo apt update && sudo apt upgrade -y
# Create a non-root user for running n8n
sudo adduser n8nadmin
sudo usermod -aG sudo n8nadmin
# Configure basic firewall
sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw enable
# Install Docker and Docker Compose
sudo apt install -y ca-certificates curl gnupg
sudo mkdir -m 0755 -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
# Add your user to docker group (logout/login required)
sudo usermod -aG docker n8nadmin
Pro tip: Set up SSH key authentication and disable password authentication in /etc/ssh/sshd_config before you close your first terminal session. Lose access to a VPS once due to a brute-force attack, and you'll never skip this step again.
Step 2: Creating the n8n Docker Compose Configuration
Now for the heart of our Docker n8n setup. I'll create a dedicated directory structure that makes backups and updates painless.
mkdir -p ~/n8n-setup/{n8n-data,postgres-data,backups}
cd ~/n8n-setup
Create a docker-compose.yml file with proper production settings:
version: '3.8'
services:
postgres:
image: postgres:15-alpine
restart: always
environment:
- POSTGRES_DB=n8n
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=${DB_PASSWORD}
volumes:
- ./postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n"]
interval: 10s
timeout: 5s
retries: 5
n8n:
image: n8nio/n8n:latest
restart: always
depends_on:
postgres:
condition: service_healthy
ports:
- "127.0.0.1:5678:5678"
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${DB_PASSWORD}
- N8N_HOST=${SUBDOMAIN}.${DOMAIN}
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN}/
- GENERIC_TIMEZONE=UTC
- EXECUTIONS_DATA_PRUNE=true
- EXECUTIONS_DATA_MAX_AGE=168
volumes:
- ./n8n-data:/home/node/.n8n
Then create a .env file with sensitive values:
DB_PASSWORD=your_super_secure_postgres_password
SUBDOMAIN=automation
DOMAIN=yourdomain.com
The reason I'm binding n8n to 127.0.0.1:5678 instead of exposing it directly is security. We'll front it with Cloudflare Tunnel, so there's no need for the world to see port 5678.
Step 3: Why I Use Postgres Instead of SQLite
You might wonder why I'm bothering with Postgres for a budget setup. Here's the honest answer: for production n8n, SQLite becomes a bottleneck once you hit a few hundred executions per day. Concurrent write operations can cause "database is locked" errors that silently kill your workflows.
The Postgres container adds maybe 50MB of RAM to your footprint—and on a $5 VPS with 1GB RAM, that's negligible. It also gives you proper transactional integrity for webhook processing, which matters when you're handling client billing automations or e-commerce integrations.
Step 4: Setting Up Cloudflare Tunnel (Skip the Reverse Proxy Headache)
Traditional n8n VPS setup guides insist you set up Nginx or Caddy as a reverse proxy with Let's Encrypt certificates. I've done this dozens of times, and frankly, it's overkill for most use cases.
Cloudflare Tunnels give you:
- Automatic HTTPS with valid certificates
- No need to open ports 80/443 publicly
- Built-in DDoS protection
- Free tier covers most use cases
Install cloudflared on your VPS:
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg > /dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared focal main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt update && sudo apt install -y cloudflared
Authenticate and create your tunnel:
cloudflared tunnel login
cloudflared tunnel create n8n-tunnel
cloudflared tunnel route dns n8n-tunnel automation.yourdomain.com
Create the tunnel config at ~/.cloudflared/config.yml:
tunnel: n8n-tunnel
credentials-file: /home/n8nadmin/.cloudflared/<TUNNEL_ID>.json
ingress:
- hostname: automation.yourdomain.com
service: http://127.0.0.1:5678
- service: http_status:404
Install as a system service:
sudo cloudflared service install
sudo systemctl enable cloudflared
sudo systemctl start cloudflared
Your n8n instance is now accessible via HTTPS at your chosen subdomain—without exposing any ports on your VPS.
Step 5: PM2 for Robust Process Management
Wait—you might be asking—if we're using Docker, why do we need PM2? This is a great question, and it's about n8n production resilience beyond just the container.
PM2 handles the orchestration layer around Docker. If Docker ever crashes or needs to restart after a reboot, PM2 ensures everything comes back online in the right order. It also provides:
- Centralized logs (
pm2 logs) - Memory monitoring
- Zero-downtime restarts during updates
- Startup script management
Install PM2 globally:
sudo npm install -g pm2
Create an ecosystem file:
// ecosystem.config.js
module.exports = {
apps: [{
name: 'n8n-docker',
script: 'docker-compose',
args: 'up -d',
cwd: '/home/n8nadmin/n8n-setup',
autorestart: true,
watch: false,
max_memory_restart: '900M',
env: {
NODE_ENV: 'production'
}
}]
}
Set up startup scripts and save the process list:
pm2 startup
pm2 start ecosystem.config.js
pm2 save
Step 6: Automated Backups (The Part People Forget)
I've had clients lose hundreds of workflows because they didn't back up their n8n-data directory. Don't be that person.
Create a backup script at ~/n8n-setup/backup.sh:
#!/bin/bash
BACKUP_DIR=/home/n8nadmin/n8n-setup/backups
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Backup n8n data
tar -czf $BACKUP_DIR/n8n-data-$TIMESTAMP.tar.gz /home/n8nadmin/n8n-setup/n8n-data
# Database dump
docker exec $(docker ps -q -f name=postgres) pg_dump -U n8n n8n | gzip > $BACKUP_DIR/n8n-db-$TIMESTAMP.sql.gz
# Keep only last 7 days of backups
find $BACKUP_DIR -type f -mtime +7 -delete
Schedule it with cron:
chmod +x ~/n8n-setup/backup.sh
crontab -e
# Add: 0 3 * * * /home/n8nadmin/n8n-setup/backup.sh
Step 7: Performance Tuning for Budget VPS Constraints
Running workflow automation on limited resources requires discipline. Here are the optimizations that matter:
Enable Execution Pruning
Already in our compose file, but worth emphasizing. Set EXECUTIONS_DATA_MAX_AGE=168 (7 days). If you need longer history, export important runs to a database instead.
Configure Swap Space
On a 1GB VPS, swap prevents OOM kills during heavy workflows:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Use the Queue Mode for Scaling
For higher throughput, switch n8n to queue mode after validating your setup. This requires Redis and a separate worker container—a great topic for a future post.
Monitoring and Maintenance Tips
Once your n8n VPS setup is running, ongoing maintenance is minimal but non-negotiable:
- Weekly: Check
docker psandpm2 status - Monthly: Update n8n with
docker-compose pull && docker-compose up -d - Quarterly: Review cleanup of old credentials and unused workflows
- Always: Monitor disk space with
df -h—execution logs fill up faster than you'd expect
For better observability, consider adding Uptime Kuma (another fantastic self-hosted tool) to alert you when your n8n instance goes down.
Cost Breakdown: The Real Numbers
Let me show you what this setup actually costs monthly:
| Component | Cost | |-----------|------| | Hetzner CX22 VPS (2GB) | $4.50 | | Domain name (annual, prorated) | $0.10 | | Cloudflare (free tier) | $0 | | Backups storage (same VPS) | $0 | | Total | ~$4.60/month |
Compare this to n8n Cloud's Starter plan at $20/month with 10,000 executions, and you're getting nearly unlimited runtime for less than the cost of a sandwich.
Common Pitfalls I've Learned to Avoid
After deploying this configuration dozens of times, here are the mistakes I see repeatedly:
1. Skipping Postgres — Yes, SQLite is "simpler," but you'll regret it during traffic spikes.
2. Using latest Docker tag in production — Pin your version (e.g., n8nio/n8n:1.50.0) for reproducible deployments.
3. Forgetting about webhook URLs — Update WEBHOOK_URL to match your actual domain, or external integrations will fail silently.
4. Not setting timezone correctly — GENERIC_TIMEZONE affects cron triggers. UTC is safest unless you have a specific need.
5. Exposing 5678 publicly — Always front it with Cloudflare Tunnel or Nginx with proper auth.
Final Thoughts on Production-Ready n8n
This setup has served me well across personal projects, client work, and even a few small SaaS products. It's not the absolute cheapest possible deployment—that would involve SQLite and direct port exposure—but it offers the reliability and security you'd expect from production infrastructure, at a fraction of typical costs.
The beauty of n8n is that once you have it running, the possibilities explode. Connect it to your CRM, automate social media, build AI-powered workflows with the OpenAI node, or orchestrate complex multi-step business processes. All without paying per execution or worrying about your data leaving your infrastructure.
If you want help with advanced configurations—queue mode with Redis, custom nodes, Kubernetes deployments, or high-availability setups—I'm available for consulting through my Fiverr profile. Otherwise, happy automating!
Frequently Asked Questions
How much RAM does n8n actually need for production workloads?
For most small-to-medium businesses running n8n production deployments, 2GB of RAM is the sweet spot. My $5 VPS setup with 2GB handles roughly 50,000 executions per month comfortably. If you're processing webhook bursts or running heavy data transformations, you'll want to monitor memory usage and consider scaling vertically before hitting limits.
Can I run n8n without Docker on a VPS?
Absolutely—n8n supports direct Node.js installation with npm install n8n -g. However, Docker n8n deployments are easier to update, isolate dependencies, and replicate across environments. The overhead is minimal (maybe 30MB extra RAM), and you get much cleaner upgrade paths.
Is self-hosting n8n actually more secure than the cloud version?
It depends on your operational maturity. Cloud-hosted n8n handles security patching and infrastructure hardening for you. Self-hosting means you're responsible for OS updates, Docker security, SSH hardening, and credential management. With proper setup (which this guide provides), self-hosting can be equally or more secure—but it requires discipline.
What's the difference between n8n's queue mode and regular mode?
Regular mode runs everything in one container—good for hundreds of executions per day. Queue mode uses Redis to distribute work across multiple worker containers, enabling horizontal scaling. For a single $5 VPS, regular mode is fine. When you outgrow it, queue mode lets you add workers without redesigning your architecture.
Do I really need Cloudflare Tunnel, or can I use Nginx directly?
You don't need Cloudflare Tunnel—Nginx with Let's Encrypt works fine. The Cloudflare approach just hides your server's IP address from the public internet, adding an extra layer of protection. If you're comfortable managing Nginx and certificate renewal, that's a perfectly valid alternative. I've used both approaches successfully across different deployments.