When you outgrow 2,500 monthly executions on managed workflow clouds—or run polling and AI loops that burn per-operation credits—self-hosting n8n Community Edition on a single Linux VPS gives you unlimited workflow runs for a flat server bill ($10–$20/month).
This guide walks through a clean, production-safe Docker Compose setup backed by PostgreSQL (instead of the default SQLite file) and Caddy for automatic HTTPS.
1. Why PostgreSQL + Reverse Proxy Matter in Production
- Avoid SQLite locking: n8n's default SQLite database works for quick local tests, but concurrent webhook bursts cause write locks. PostgreSQL handles parallel executions and retains clean execution logs.
- Bind n8n to 127.0.0.1: Never expose port 5678 directly to the public internet. Bind it to localhost and terminate TLS via Caddy or Nginx.
- Pin N8N_ENCRYPTION_KEY: n8n encrypts all API credentials stored in its database using this key. Losing it locks you out of every saved credential on restore.
2. Server Prerequisites (Ubuntu 24.04 LTS)
- 2 vCPU / 2–4 GB RAM VPS (enough for most small-team automations)
- Docker Engine + Docker Compose plugin installed
- A domain or subdomain ( 8n.yourdomain.com) with an A record pointing to your VPS IP
- Ports 80 and 443 open in your firewall (ufw allow 80,443/tcp)
3. Generate Secrets & .env
Create a project directory /opt/n8n and generate strong secrets before starting containers:
ash
mkdir -p /opt/n8n && cd /opt/n8n
openssl rand -hex 32 # Use for N8N_ENCRYPTION_KEY
openssl rand -hex 20 # Use for POSTGRES_PASSWORD
Save /opt/n8n/.env:
env
N8N_DOMAIN=n8n.yourdomain.com
POSTGRES_PASSWORD=your_generated_postgres_password
N8N_ENCRYPTION_KEY=your_generated_32_byte_hex_key
4. Docker Compose Configuration (docker-compose.yml)
`yaml
services:
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: n8n
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n -d n8n"]
interval: 10s
timeout: 5s
retries: 5
n8n:
image: docker.n8n.io/n8nio/n8n:latest
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
ports:
- "127.0.0.1:5678:5678"
environment:
DB_TYPE: postgresdb
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_PORT: 5432
DB_POSTGRESDB_DATABASE: n8n
DB_POSTGRESDB_USER: n8n
DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}
N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
N8N_HOST: ${N8N_DOMAIN}
N8N_PROTOCOL: https
WEBHOOK_URL: https://${N8N_DOMAIN}/
GENERIC_TIMEZONE: UTC
volumes:
- n8n_data:/home/node/.n8n
volumes:
pg_data:
n8n_data:
`
Start the stack with docker compose up -d.
5. Automatic HTTPS via Caddy (/etc/caddy/Caddyfile)
Install Caddy on the host and point it to 127.0.0.1:5678:
caddy
n8n.yourdomain.com {
reverse_proxy 127.0.0.1:5678
}
Reload Caddy (systemctl reload caddy) and your instance will automatically provision a Let's Encrypt certificate so OAuth callbacks and webhooks resolve over HTTPS.
6. Essential Backup Routine & Trade-Offs
- Daily automated backup: Run pg_dump via cron (docker compose exec -T postgres pg_dump -U n8n n8n > backup.sql) and back up /opt/n8n/.env separately in an encrypted password vault.
- Who should skip self-hosting: While the Community Edition software license is free with unlimited executions, it excludes enterprise SAML/LDAP SSO, multiple environments, and managed uptime. If you have zero Linux/Docker admin bandwidth and run fewer than 2,500 workflows per month, managed cloud tiers remain simpler.
