n8n Self-Hosting Guide — Free Automation Without Limits

Photo: Walls.io / Pexels
n8n self-hosted gives you unlimited automation for $5/month in server costs. While Zapier charges $19-99/month with task limits, self-hosted n8n on a cheap VPS handles millions of workflow executions with no per-task pricing.
This guide walks through the complete setup — server provisioning, Docker deployment, security hardening, and scaling considerations.
Why Self-Host?
Cost: Zapier at scale costs $300-1000/month. Self-hosted n8n: $5-20/month for the server. Savings compound over time.
Privacy: All your data stays on your infrastructure. No third party sees your business workflows or data. Critical for HIPAA, GDPR, or sensitive business processes.
No limits: Build 500 automations, run each 10,000 times per day. No throttling, no quotas, no surprise bills.
Customization: Add custom nodes, modify the codebase, integrate with internal systems that Zapier can't reach.
Drawbacks:
- Requires basic Linux/Docker knowledge
- You own uptime and security
- No managed support (community help available)
Hardware Requirements
For most personal/small business use:
| Setup | Suitable For | Cost |
|---|---|---|
| 1 vCPU, 1GB RAM | Light personal use (< 50 workflows/day) | $4-6/mo |
| 2 vCPU, 2GB RAM | Small business (< 500 workflows/day) | $10-15/mo |
| 4 vCPU, 8GB RAM | Heavy use (< 10,000/day) | $40-60/mo |
| Dedicated server | Enterprise (millions/day) | $100+/mo |
VPS providers (good options):
- Hetzner (cheapest, German)
- DigitalOcean (most popular, US)
- Vultr
- Linode (now Akamai)
- AWS Lightsail (if you want AWS ecosystem)
For most users, Hetzner CX11 ($4.59/mo, 1 vCPU, 2GB) or DigitalOcean $6/mo droplet is plenty to start.
Method 1: Quick Setup with Docker Compose
The easiest approach. Run as a Docker container with persistent storage.
Step 1: Provision a Server
Sign up for any VPS provider. Create an Ubuntu 22.04 LTS server. SSH in:
ssh root@your-server-ip
Step 2: Install Docker
# Update system
apt update && apt upgrade -y
# Install Docker
curl -fsSL https://get.docker.com | sh
# Install Docker Compose
apt install docker-compose-plugin -y
# Verify
docker --version
docker compose version
Step 3: Create n8n Configuration
mkdir -p /opt/n8n
cd /opt/n8n
Create docker-compose.yml:
version: '3.8'
services:
n8n:
image: n8nio/n8n:latest
restart: unless-stopped
ports:
- "5678:5678"
environment:
- N8N_HOST=your-domain.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://your-domain.com/
- N8N_ENCRYPTION_KEY=GENERATE_RANDOM_KEY_HERE
- GENERIC_TIMEZONE=Europe/Istanbul
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=CHANGE_THIS_STRONG_PASSWORD
volumes:
- n8n_data:/home/node/.n8n
volumes:
n8n_data:
Generate a random encryption key:
openssl rand -hex 32
Replace GENERATE_RANDOM_KEY_HERE with the output.
Step 4: Start n8n
docker compose up -d
n8n is now running on port 5678. Check:
docker compose ps
docker compose logs -f n8n
Step 5: Test Access
In browser: http://your-server-ip:5678
You should see the n8n login page. Sign in with the basic auth credentials.
Method 2: With Reverse Proxy + HTTPS (Recommended)
For production use, you want a proper domain and HTTPS.
Step 1: Point Domain to Server
Add an A record in your DNS:
n8n.yourdomain.com → your-server-ip
Step 2: Add Caddy Reverse Proxy
Caddy automatically handles SSL certificates via Let's Encrypt.
Update docker-compose.yml:
version: '3.8'
services:
n8n:
image: n8nio/n8n:latest
restart: unless-stopped
environment:
- N8N_HOST=n8n.yourdomain.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://n8n.yourdomain.com/
- N8N_ENCRYPTION_KEY=your-key
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=strong-password
volumes:
- n8n_data:/home/node/.n8n
caddy:
image: caddy:latest
restart: unless-stopped
ports:
- "80:80"
- "443:443"
volumes:
-./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
volumes:
n8n_data:
caddy_data:
caddy_config:
Create Caddyfile:
n8n.yourdomain.com {
reverse_proxy n8n:5678
}
Restart:
docker compose down
docker compose up -d
Visit https://n8n.yourdomain.com — Caddy handles SSL automatically.
Database Configuration
n8n uses SQLite by default (fine for personal use). For production, use PostgreSQL:
services:
n8n:
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_PORT=5432
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=strong-db-password
depends_on:
- postgres
postgres:
image: postgres:16
restart: unless-stopped
environment:
- POSTGRES_DB=n8n
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=strong-db-password
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
PostgreSQL is faster, more reliable, and required for the queue execution mode.
Queue Execution Mode (For Scale)
For heavy workloads, run n8n in queue mode with Redis. The main process handles UI; workers handle workflow execution.
services:
n8n:
environment:
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
depends_on:
- redis
- postgres
n8n-worker:
image: n8nio/n8n:latest
command: worker
environment:
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- DB_TYPE=postgresdb
#... same DB config as main n8n
depends_on:
- redis
- postgres
redis:
image: redis:7
restart: unless-stopped
Scale workers by spinning up more n8n-worker containers.
Security Hardening
1. Firewall
# Allow only SSH, HTTP, HTTPS
ufw allow 22
ufw allow 80
ufw allow 443
ufw enable
# Verify
ufw status
2. Fail2ban
Block brute force attempts:
apt install fail2ban -y
systemctl enable fail2ban
3. Strong Authentication
In docker-compose.yml:
- N8N_BASIC_AUTH_USER=randomusername123
- N8N_BASIC_AUTH_PASSWORD=use-a-very-long-random-password
Better: configure SSO via your identity provider (Okta, Google Workspace) — supported in n8n Enterprise.
4. Regular Updates
cd /opt/n8n
docker compose pull
docker compose up -d
Subscribe to n8n's security mailing list for critical vulnerability notifications.
5. Automated Backups
# Backup script
#!/bin/bash
BACKUP_DIR=/backups/n8n
mkdir -p $BACKUP_DIR
# Backup n8n data volume
docker run --rm -v n8n_n8n_data:/data -v $BACKUP_DIR:/backup ubuntu \
tar czf /backup/n8n_$(date +%Y%m%d).tar.gz /data
# Backup PostgreSQL
docker exec n8n-postgres-1 pg_dump -U n8n n8n > $BACKUP_DIR/db_$(date +%Y%m%d).sql
# Optionally rsync to remote storage
Add to crontab to run daily.
First Automation: Test Setup
Once n8n is running, create your first workflow:
- Sign in to your n8n instance
- Create workflow — click "+ New"
- Add a Webhook trigger (search "Webhook" in node search)
- Copy the webhook URL that n8n provides
- Add an Email action (or any other node)
- Connect the nodes
- Activate the workflow
- Test by sending a POST request to the webhook URL:
curl -X POST https://n8n.yourdomain.com/webhook/test \
-H "Content-Type: application/json" \
-d '{"test": "hello"}'
You should receive the email. If yes — setup works.
Common Use Cases for Self-Hosted n8n
1. Internal API integrations — Connect to internal systems Zapier can't reach.
2. High-volume processing — Process millions of records/month at no per-task cost.
3. Custom logic — Use the Code node to run Python or JavaScript for complex workflows.
4. Privacy-sensitive workflows — Healthcare, legal, financial data that can't leave your infrastructure.
5. Multi-step approval workflows — Native error handling and approvals.
6. Webhook hubs — Receive webhooks, transform them, route to multiple destinations.
Monitoring
Basic monitoring with Docker:
# Resource usage
docker stats
# n8n logs
docker compose logs -f n8n
# Worker logs
docker compose logs -f n8n-worker
For production, add proper monitoring:
- Uptime Kuma (self-hosted, monitors n8n endpoint)
- Grafana + Prometheus (advanced metrics)
- Better Stack / UptimeRobot (third-party uptime monitoring)
When to Use Cloud n8n vs Self-Host
Use n8n Cloud ($20-50/mo) when:
- You want zero infrastructure work
- Compliance handled by n8n
- Small team (1-5 users)
- No specific reason to self-host
Self-host when:
- High volume (>10K workflow executions/day)
- Privacy/compliance requires it
- You're comfortable with Linux/Docker
- Cost matters (long-term saves significantly)
Advanced Scaling: Worker Architecture and Load Balancing
As workflow volume grows, basic n8n hits CPU/memory limits. The queue mode with multiple workers distributes execution across containers, scaling nearly linearly.
How Queue Mode Works
In standard mode, a single n8n process handles everything: the UI, webhook ingestion, and execution. This creates a bottleneck when workflows run concurrently. Queue mode separates these:
- Main n8n: Handles UI, API, webhook ingestion, database.
- Workers: Execute workflows pulled from Redis queue.
version: '3.8'
services:
n8n:
image: n8nio/n8n:latest
restart: unless-stopped
environment:
- N8N_EXECUTION_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
# ... other DB config
depends_on:
- redis
- postgres
n8n-worker-1:
image: n8nio/n8n:latest
command: worker
restart: unless-stopped
environment:
- N8N_EXECUTION_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
# ... same DB config
depends_on:
- redis
- postgres
n8n-worker-2:
image: n8nio/n8n:latest
command: worker
restart: unless-stopped
environment:
- N8N_EXECUTION_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
depends_on:
- redis
- postgres
redis:
image: redis:7-alpine
restart: unless-stopped
volumes:
- redis_data:/data
postgres:
image: postgres:16
restart: unless-stopped
environment:
- POSTGRES_DB=n8n
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=secure-password
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
redis_data:
postgres_data:
Spin up as many workers as needed. Add a load balancer (Nginx, Traefik) in front to distribute webhook traffic. This setup handles 100K+ workflow executions per day.
Performance Tuning for High Volume
1. Connection Pooling — Database connections are expensive. Use pgBouncer to pool Postgres connections:
apt install pgbouncer -y
Configure /etc/pgbouncer/pgbouncer.ini:
[databases]
n8n = host=postgres port=5432 dbname=n8n
[pgbouncer]
pool_mode = transaction
max_client_conn = 1000
default_pool_size = 25
This prevents worker connection exhaustion.
2. Redis Cluster — For very high concurrency (>1000 concurrent workflows), a single Redis node becomes a bottleneck. Use Redis Cluster:
redis-cluster:
image: redis:7-alpine
command: redis-server --cluster-enabled yes --cluster-node-timeout 5000 --cluster-replica-validity-factor 10 --appendonly yes
volumes:
- redis_cluster_data:/data
3. Webhook Rate Limiting — Prevent a single endpoint from overwhelming workers:
// In an n8n Code node
if ($input.all().length > 1000) {
throw new Error("Too many items; please batch smaller requests");
}
Disaster Recovery and Backup Strategy
Self-hosting means you own backup responsibility. Data loss is permanent unless backups exist.
Daily Automated Backup Script
#!/bin/bash
# /root/backup-n8n.sh
BACKUP_DIR=/backups/n8n
RETENTION_DAYS=30
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
# Backup PostgreSQL
docker exec n8n-postgres-1 pg_dump -U n8n n8n \
| gzip > $BACKUP_DIR/db_$DATE.sql.gz
# Backup n8n data volume
docker run --rm \
-v n8n_n8n_data:/data \
-v $BACKUP_DIR:/backup \
alpine tar czf /backup/n8n_data_$DATE.tar.gz /data
# Backup Redis (if queue mode)
docker exec n8n-redis-1 redis-cli --rdb /backups/n8n/redis_$DATE.rdb
# Upload to S3 (optional)
aws s3 sync $BACKUP_DIR s3://my-n8n-backups/ --delete
# Cleanup old backups (keep 30 days)
find $BACKUP_DIR -name "db_*.sql.gz" -mtime +$RETENTION_DAYS -delete
find $BACKUP_DIR -name "n8n_data_*.tar.gz" -mtime +$RETENTION_DAYS -delete
Add to crontab:
0 2 * * * /root/backup-n8n.sh > /var/log/n8n-backup.log 2>&1
Restore Workflow
# Stop n8n
docker compose down
# Restore database
docker compose up -d postgres
docker exec -i n8n-postgres-1 psql -U n8n n8n < db_DATE.sql
# Restore data volume
docker run --rm -v n8n_n8n_data:/data -v /backups:/backup alpine tar xzf /backup/n8n_data_DATE.tar.gz
# Restart all services
docker compose up -d
Test this restore process quarterly. The worst time to learn backups don't work is after data loss.
Troubleshooting Common Issues
n8n doesn't start / port already in use:
# Check if port 5678 is in use
lsof -i :5678
# Kill the process
kill -9 <PID>
# Or use a different port in docker-compose.yml:
ports:
- "8080:5678"
Can't connect to external APIs (timeout/connection refused):
Likely firewall or DNS issue. Test connectivity from the container:
# SSH into container
docker exec -it <container-name> bash
# Test DNS
nslookup api.example.com
# Test connectivity
curl -v https://api.example.com
# Check firewall (from host)
curl -I https://api.example.com
Database connection errors:
# Check PostgreSQL container
docker compose ps
docker compose logs postgres
# Verify connection credentials
docker exec postgres psql -U n8n -d n8n -c "SELECT 1;"
Workflows execute but nothing happens (silent failure):
Check logs with verbosity:
docker compose down
docker compose up -d -L n8n
docker compose logs -f n8n
Look for error messages in n8n UI: Execution History → failed execution → view details.
Source: n8n Community Forum & GitHub Issues (2024–2026)
Advanced Webhooks and Error Handling
Webhooks are how external services trigger n8n workflows. Production workflows need robust error handling.
Multi-Trigger Webhooks with Validation
Webhooks are a core concept for understanding how automation platforms work. For deeper context on how APIs and webhooks function, see our dedicated explainer. Here's a production-ready webhook validation pattern:
{
"name": "Webhook with validation",
"nodes": [
{
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"properties": {
"path": "process-order",
"httpMethod": "POST",
"options": {
"responseMode": "responseNode"
}
}
},
{
"name": "Validate Payload",
"type": "n8n-nodes-base.if",
"properties": {
"conditions": {
"boolean": [
{
"value1": "{{ $json.orderId }}",
"operation": "isEmpty",
"value2": null
}
]
}
}
},
{
"name": "Return Error",
"type": "n8n-nodes-base.respondToWebhook",
"properties": {
"statusCode": 400,
"responseBody": "{{ { error: 'Missing orderId' } }}"
}
}
]
}
The Respond to Webhook node lets you return HTTP responses, crucial for synchronous workflows.
Retry Logic with Exponential Backoff
n8n's Error Workflow feature triggers on failure. Combine with delays for robust retry:
# On error, wait 5s, then retry. If retry fails, send alert.
Error Workflow:
1. Add a delay (Wait node: 5 seconds)
2. Retry the original workflow
3. If still fails, send Slack alert
Asynchronous Webhook Handling
For long-running tasks (file processing, API calls), use asynchronous patterns:
// Webhook receives request, returns immediately
// Meanwhile, workflow continues processing in background
1. Webhook → Return 202 (Accepted) immediately
2. Workflow saves request to database or queue
3. Background process handles the actual work
4. Send callback to original requester when done
This prevents timeouts and allows webhook endpoints to handle high volume.
Source: n8n Documentation (2024) — Webhook handling patterns; n8n Community recipes
Performance Optimization: Caching and Batch Processing
Caching API Responses
External API calls are slow (100-500ms each). Cache responses when possible:
// In a Code node
const cacheKey = $json.userId;
const cachedUser = await n8n.cache.get(cacheKey);
if (cachedUser) {
return cachedUser; // Instant, no API call
}
// Otherwise, fetch and cache
const user = await fetch(`/api/users/${userId}`).json();
await n8n.cache.set(cacheKey, user, 3600); // Cache 1 hour
return user;
This reduces API costs and latency dramatically.
Batch Processing
Instead of processing items one-at-a-time (100 items = 100 API calls), batch them:
// Process 100 items in 1 API call (batch endpoint)
const batch = $input.all().map(item => item.json);
const response = await fetch('/api/users/batch', {
method: 'POST',
body: JSON.stringify({ users: batch })
});
return response.json();
Batch processing reduces API calls by 10-100×, critical for high-volume workflows.
Lazy Loading with Code Node
// Only process records that changed
const lastRun = await n8n.db.get('lastRunTime') || '2024-01-01';
const newRecords = await fetch(`/api/records?since=${lastRun}`);
// Update lastRunTime
await n8n.db.set('lastRunTime', new Date().toISOString());
return newRecords.json();
This prevents reprocessing and keeps execution time low.
Running n8n in Kubernetes (for enterprise scale)
For organizations already running Kubernetes (learn more in our guide on containerization), Helm charts simplify deployment:
helm repo add n8n https://helm.n8n.io
helm repo update
# Install n8n with PostgreSQL + Redis
helm install n8n n8n/n8n \
--set postgresql.enabled=true \
--set redis.enabled=true \
--set replicaCount=3 \
--namespace n8n --create-namespace
This instantly scales n8n to 3 replicas with managed database and cache. HorizontalPodAutoscaler automatically scales workers based on queue depth.
Benefits over Docker Compose:
- Self-healing: Kubernetes restarts failed pods
- Rollouts: Zero-downtime updates
- Monitoring: Native Prometheus metrics
- Networking: Service mesh integration (Istio)
- Secrets management: Kubernetes Secrets for API keys
Downsides: Higher complexity, requires K8s expertise, overkill for small workflows.
Source: n8n Helm Chart documentation (2024–2026); Kubernetes best practices
Integration Patterns and Custom Nodes
While n8n has 400+ built-in nodes, custom integrations are often needed.
Creating a Custom Node
// custom-node.ts
import { INodeType, NodeOperationError } from 'n8n-workflow';
export class MyCustomNode implements INodeType {
description = {
displayName: 'My Custom API',
name: 'myCustomApi',
group: ['transform'],
properties: [
{
displayName: 'API Key',
name: 'apiKey',
type: 'string',
typeOptions: { password: true }
}
]
};
async execute() {
const apiKey = this.getNodeParameter('apiKey', 0);
const response = await fetch('https://api.custom.com/data', {
headers: { Authorization: `Bearer ${apiKey}` }
});
return response.json();
}
}
Build and package as npm module, then add to n8n:
docker run ... -v /path/to/custom-nodes:/custom-nodes \
n8n:latest
Custom nodes enable deep integration with proprietary systems.
Cost Comparison: Self-Hosted vs Cloud vs Competitors
| Service | Pricing | Workflows | Executions/Mo | Uptime SLA | Best For |
|---|---|---|---|---|---|
| n8n Cloud | $20-300/mo | 20-unlimited | 20K-5M | 99.95% | Small teams, compliance |
| Self-hosted n8n | $5-60/mo (server) | Unlimited | Unlimited | You own it | Cost-conscious, privacy-first |
| Zapier | $19-299/mo | Limited | 5K-5M | 99.9% | Ease of use, many integrations |
| Make.com | $10-299/mo | Limited | 10K-10M | 99.99% | Mid-market, good builder UX |
| Integromat | $9-99/mo | Variable | 10K-1M | 99.5% | Budget option |
For a solo founder processing 1M workflow executions monthly, self-hosted n8n costs $50-100/month. Zapier would cost $1,000+/month for equivalent volume. The breakeven is around 10K executions/month.
Frequently Asked Questions
Q: Is n8n really free to self-host?
A: Yes, the Community Edition is fully free for commercial and personal use under the Sustainable Use License. You only pay for server infrastructure ($5-60/month depending on scale). Cloud versions have paid tiers.
Q: How does self-hosted n8n compare to Zapier?
A: n8n is more flexible (custom Code nodes, full logic control) and dramatically cheaper at scale (no per-task pricing). Zapier has more pre-built integrations (6,000+ vs 400+) and a gentler learning curve. Choose n8n if you need control and volume, Zapier if you want simplicity.
Q: Will my workflows survive server reboots?
A: Yes. Docker volumes (as configured) persist across restarts. Active executions in-progress may be lost, but workflow definitions and history remain intact.
Q: Can I migrate existing Zapier workflows to n8n?
A: No automated path exists — you rebuild workflows manually in n8n. The good news: most Zapier concepts (triggers, actions, filters) map one-to-one to n8n nodes. Plan 2-4 hours per moderately complex workflow.
Q: What happens when n8n updates and breaks my setup?
A: Test updates in a staging environment first. Pin your Docker image to a specific version (e.g., n8n:1.45.0 instead of latest) in docker-compose.yml for production stability. Read the n8n release notes before upgrading.
Q: How do I handle secrets (API keys, passwords) securely?
A: Never hardcode secrets in docker-compose.yml. Use Docker Secrets or environment variables from a .env file (git-ignored). n8n also encrypts stored credentials in the database using the encryption key you provide. Rotate keys regularly via the n8n UI.
Read next:
Follow new articles
Add SciHub101 to your feed reader to see new science and technology stories.
About the Author
SciHub101 is an independent science and technology publication founded and edited by Buğra Sözeri, a software engineer and app developer based in Türkiye. AI tools assist with drafting and research. Source documentation in the existing archive is uneven, and articles are being reviewed and corrected. The Editorial Team byline identifies editorial responsibility; it does not certify specialist or independent human review. Where we compare products, anything built by our own team is labelled as such — see the editorial guidelines and About page for how corrections and disclosures work.
Related Articles

n8n Self-Hosted Guide: Docker Setup, Costs, and Licensing
How to self-host n8n with Docker: real cost breakdown, Sustainable Use License rules, Postgres setup, first workflow, and n8n vs Zapier vs Make.

Webhook Design Patterns 2026 — Reliability and Security
Webhook design patterns for reliable, secure integrations: signature verification, idempotency, retries, queues, and dead-letter handling. Updated for 2026.

Marketing Automation Stack 2026 — Tools, Workflows, ROI
Marketing automation stack for 2026: compare HubSpot, ActiveCampaign, Customer.io, Klaviyo, and Mailchimp, build core workflows, and meet inbox sender rules.

Notion API Automation 2026 — Build Powerful Workflows
Notion API automation explained: connections, data sources, webhooks, rate limits, and Python patterns for syncing GitHub, reports, and AI tagging.

n8n Workflow Automation — Beginner's Guide
Learn n8n from scratch: nodes, triggers, integrations, and how it compares to Zapier and Make for self-hosted workflow automation.

Python Automation Scripts for Beginners
Automate repetitive tasks with Python: file management, web scraping, emails, API calls, and scheduling — with practical code examples.