Why self-host n8n?
n8n Cloud is convenient, but self-hosting gives you unlimited executions, full data control, and zero per-execution costs. For a business running dozens of workflows, self-hosting can save hundreds of dollars per month.
In this guide, I’ll walk through deploying a production-ready n8n instance that pairs well with a static site on Cloudflare Pages.
Architecture overview
Internet → Cloudflare (DNS + SSL) → VPS
↓
Nginx Reverse Proxy
↙ ↘
n8n (Docker) Static Files
↓
PostgreSQL (Docker)
↓
Redis (Docker)
Prerequisites
- A VPS (I recommend Hetzner CX22 at €4.5/month or DigitalOcean $6/month)
- A domain name
- A Cloudflare account (free tier works)
- Docker and Docker Compose installed on the VPS
Step 1: Set up DNS on Cloudflare
-
Add your domain to Cloudflare
-
Create an A record pointing to your VPS IP:
automation.yourdomain.com→your.vps.ip- Enable the orange cloud (proxy) for DDoS protection and SSL
-
Create another A record for your static site:
yourdomain.com→ Cloudflare Pages (configured separately)
Step 2: Docker Compose setup
Create a docker-compose.yml file:
version: '3.8'
services:
postgres:
image: postgres:15-alpine
restart: always
environment:
POSTGRES_DB: n8n
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U n8n']
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
restart: always
command: redis-server --requirepass ${REDIS_PASSWORD}
volumes:
- redis_data:/data
n8n:
image: n8nio/n8n:latest
restart: always
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_started
environment:
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- DB_POSTGRESDB_DATABASE=n8n
- DB_POSTGRESDB_USER=n8n
- DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
- N8N_HOST=automation.yourdomain.com
- N8N_PORT=5678
- N8N_PROTOCOL=https
- WEBHOOK_URL=https://automation.yourdomain.com/
- EXECUTIONS_MODE=queue
- REDIS_HOST=redis
- REDIS_PASSWORD=${REDIS_PASSWORD}
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=${N8N_USER}
- N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
- N8N_ENCRYPTION_KEY=${ENCRYPTION_KEY}
- GENERIC_TIMEZONE=America/New_York
volumes:
- n8n_data:/home/node/.n8n
ports:
- "127.0.0.1:5678:5678"
volumes:
postgres_data:
redis_data:
n8n_data:
Step 3: Environment variables
Create a .env file:
POSTGRES_PASSWORD=your_secure_password_here
REDIS_PASSWORD=your_redis_password_here
N8N_USER=admin
N8N_PASSWORD=your_secure_n8n_password
ENCRYPTION_KEY=your_32_char_encryption_key_here
Generate a secure encryption key:
openssl rand -hex 32
Step 4: Nginx reverse proxy
Install Nginx on the VPS:
sudo apt install nginx
Create a configuration file at /etc/nginx/sites-available/n8n:
server {
listen 80;
server_name automation.yourdomain.com;
# Redirect to HTTPS
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name automation.yourdomain.com;
# SSL certificates (managed by Cloudflare)
ssl_certificate /etc/ssl/certs/cloudflare.crt;
ssl_certificate_key /etc/ssl/private/cloudflare.key;
# Cloudflare real IP
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
real_ip_header CF-Connecting-IP;
# n8n proxy
location / {
proxy_pass http://127.0.0.1:5678;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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;
# WebSocket support
proxy_read_timeout 86400;
}
# Webhook endpoint (no basic auth for public webhooks)
location /webhook/ {
proxy_pass http://127.0.0.1:5678/webhook/;
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;
}
}
Enable the site:
sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Step 5: SSL with Cloudflare
Since Cloudflare proxies your traffic, you can use Cloudflare’s SSL. Set the SSL mode to “Full” in Cloudflare dashboard.
For the origin certificate, generate one from Cloudflare’s dashboard:
- Go to SSL/TLS → Origin Server
- Create Certificate
- Save the certificate and key to your VPS
Step 6: Start the stack
docker-compose up -d
Check that everything is running:
docker-compose ps
docker-compose logs n8n
Navigate to https://automation.yourdomain.com and you should see the n8n login page.
Step 6: Backup strategy
Set up automated backups with a cron job:
#!/bin/bash
# backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_DIR=/backups/n8n
mkdir -p $BACKUP_DIR
# Backup PostgreSQL
docker-compose exec -T postgres pg_dump -U n8n n8n | gzip > $BACKUP_DIR/db_$DATE.sql.gz
# Backup n8n data
tar czf $BACKUP_DIR/n8n_data_$DATE.tar.gz -C ./n8n_data .
# Keep only last 7 days
find $BACKUP_DIR -name "*.gz" -mtime +7 -delete
# Upload to S3-compatible storage (optional)
# aws s3 sync $BACKUP_DIR s3://your-bucket/n8n-backups/
Add to crontab:
0 2 * * * /path/to/backup.sh
Monitoring
Health check endpoint
n8n provides a health check endpoint at /healthz. Use it with a monitoring service:
curl https://automation.yourdomain.com/healthz
# Returns: {"status":"ok"}
Uptime monitoring
I use Uptime Kuma (also self-hosted) to monitor:
- n8n health endpoint
- PostgreSQL connectivity
- Redis connectivity
- Disk space on the VPS
Log monitoring
# View n8n logs
docker-compose logs -f n8n
# View PostgreSQL logs
docker-compose logs -f postgres
Security checklist
- Firewall configured (ufw) — only allow 80, 443, and SSH
- SSH key authentication enabled, password auth disabled
- Fail2ban installed for SSH protection
- n8n basic auth enabled
- Strong encryption key set
- SSL configured via Cloudflare
- Regular backups configured
- n8n updated to latest version
- Docker images updated regularly
Updating n8n
docker-compose pull
docker-compose up -d
This pulls the latest n8n image and restarts the container. The database and data volumes are preserved.
Conclusion
Self-hosting n8n gives you unlimited automation power at a fraction of the cost of cloud alternatives. With Docker, Nginx, and Cloudflare, you can have a production-ready setup in under an hour. The key is to invest in proper security, backups, and monitoring from the start.