Open-source alternatives guide
How to Self-Host n8n Workflow Automation 2026
Self-host n8n in 2026. Sustainable Use License, ~51K stars, TypeScript — visual workflow automation with 400+ integrations. Docker setup, webhooks now.
TL;DR
n8n (Sustainable Use License, ~51K GitHub stars, TypeScript) is the most powerful self-hosted workflow automation platform. Zapier charges $299/month for business plans with 100K tasks. n8n self-hosted is free, supports 400+ integrations (Slack, Gmail, GitHub, databases, AI APIs), runs complex multi-step workflows with conditions/loops, and lets you add custom JavaScript/Python code nodes anywhere in the flow. The visual editor is excellent and the community has thousands of workflow templates.
Key Takeaways
- n8n: ~51K stars, TypeScript — 400+ integrations, visual workflow builder, code nodes
- Self-hosted = unlimited: No task limits, no per-workflow fees, all features available
- Code anywhere: Add JavaScript or Python nodes to do anything an integration doesn't support
- AI Agent nodes: Built-in LangChain integration for AI agent workflows
- Webhooks: Receive webhooks from any service; expose workflows as APIs
- Import community templates: Thousands of ready-to-use workflows from n8n.io/workflows
n8n vs Zapier vs Activepieces
| Feature | n8n | Zapier | Activepieces |
|---|---|---|---|
| License | Sustainable Use | Proprietary | MIT |
| GitHub Stars | ~51K | — | ~10K |
| Cost (self-hosted) | Free | $299/mo | Free |
| Integrations | 400+ | 6000+ | 100+ |
| Code nodes | Yes (JS + Python) | Limited | Yes (TypeScript) |
| AI/LLM nodes | Yes (LangChain) | Yes | Limited |
| Webhook triggers | Yes | Yes | Yes |
| Complex branching | Yes | Limited | Yes |
| Loop/iteration | Yes | Yes | Limited |
| Custom nodes | Yes | No | Yes |
Part 1: Docker Setup
# docker-compose.yml
services:
n8n:
image: n8nio/n8n:latest
container_name: n8n
restart: unless-stopped
ports:
- "5678:5678"
volumes:
- n8n_data:/home/node/.n8n
environment:
N8N_HOST: "n8n.yourdomain.com"
N8N_PORT: 5678
N8N_PROTOCOL: "https"
WEBHOOK_URL: "https://n8n.yourdomain.com/"
N8N_ENCRYPTION_KEY: "${N8N_ENCRYPTION_KEY}"
# Default user (for initial setup):
N8N_DEFAULT_USER_EMAIL: "${ADMIN_EMAIL}"
N8N_DEFAULT_USER_PASSWORD: "${ADMIN_PASSWORD}"
# Disable user registration (after setup):
N8N_USER_MANAGEMENT_DISABLED: "false"
# Optional: external DB instead of SQLite
DB_TYPE: "postgresdb"
DB_POSTGRESDB_HOST: postgres
DB_POSTGRESDB_PORT: 5432
DB_POSTGRESDB_DATABASE: n8n
DB_POSTGRESDB_USER: n8n
DB_POSTGRESDB_PASSWORD: "${POSTGRES_PASSWORD}"
# Timezone:
GENERIC_TIMEZONE: "America/Los_Angeles"
TZ: "America/Los_Angeles"
depends_on:
postgres:
condition: service_healthy
postgres:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: n8n
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
POSTGRES_DB: n8n
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U n8n"]
interval: 10s
start_period: 20s
volumes:
n8n_data:
postgres_data:
# .env
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
ADMIN_EMAIL=admin@yourdomain.com
ADMIN_PASSWORD=your-secure-password
POSTGRES_PASSWORD=your-db-password
docker compose up -d
Visit https://n8n.yourdomain.com — log in with your admin credentials.
Part 2: HTTPS with Caddy
n8n.yourdomain.com {
reverse_proxy localhost:5678
}
Part 3: Core Concepts
Nodes
Each step in a workflow is a node. Node types:
- Trigger nodes: Start the workflow (webhook, schedule, event)
- Action nodes: Do something (send email, create issue, query DB)
- Logic nodes: IF/SWITCH conditions, loops, merge data
- Code nodes: Run JavaScript or Python
Credentials
Credentials are encrypted and stored once — reusable across all workflows:
- Settings → Credentials → Add credential
- Select service (GitHub, Slack, PostgreSQL, etc.)
- Authenticate once → use across all workflows
Workflow activation
Workflows with schedule/webhook triggers must be activated to run automatically:
- Active workflows run when triggered
- Inactive workflows only run manually (for testing)
Part 4: Example Automations
New GitHub issue → Slack notification
Trigger: GitHub — On Issues (event: opened)
Action: Slack — Send message
Channel: #engineering
Message: "New issue in {{ $json.repository.full_name }}: {{ $json.issue.title }}\n{{ $json.issue.html_url }}"
Daily digest email
Trigger: Schedule — Every day at 8 AM
Action: HTTP Request — GET https://yourapi.com/daily-summary
Action: Code node — Format the data as HTML
Action: Gmail — Send Email
To: team@company.com
Subject: Daily Digest {{ $now.format('YYYY-MM-DD') }}
Body: {{ $json.formattedHtml }}
Form submission → database + notification
Trigger: Webhook (POST) — receives form data
Action: PostgreSQL — Insert row
Table: leads
Data: {{ $json.body }}
Action: IF node — Check if email domain is company.com
→ True: Slack notification to #sales-hot-leads
→ False: Add to standard nurture sequence
Action: Gmail — Send confirmation email to submitter
AI content moderation
Trigger: Webhook — receives comment from your app
Action: OpenAI — Chat completion
Model: gpt-4o
Prompt: "Is this comment appropriate? Reply YES or NO:\n{{ $json.comment }}"
Action: IF node — Response contains "NO"
→ True: HTTP Request — DELETE comment via API, notify moderator
→ False: Continue (comment approved)
Part 5: Code Nodes
Add JavaScript or Python anywhere:
// JavaScript code node example:
// Transform data from previous node
const items = $input.all();
return items.map(item => {
const data = item.json;
return {
json: {
fullName: `${data.firstName} ${data.lastName}`,
email: data.email.toLowerCase(),
score: data.views * 0.5 + data.clicks * 2,
tier: data.views > 1000 ? 'premium' : 'standard'
}
};
});
# Python code node example:
import json
from datetime import datetime
items = _input.all()
results = []
for item in items:
data = item.json
results.append({
'json': {
'processed_at': datetime.now().isoformat(),
'word_count': len(data.get('content', '').split()),
'tags': [t.strip() for t in data.get('tags', '').split(',')]
}
})
return results
Part 6: AI Agent Workflows
n8n has built-in LangChain support for AI agents:
Trigger: Chat trigger (or webhook)
Agent node: AI Agent
- Model: OpenAI Chat Model (gpt-4o)
- Tools:
→ Calculator tool
→ HTTP Request tool (search web)
→ Custom tool: Query your PostgreSQL
- Memory: Window Buffer Memory (last 10 messages)
Output: Send response back to user
This creates a conversational agent that can query your database, search the web, and perform calculations.
Part 7: Webhook Workflows
n8n generates unique webhook URLs for each trigger:
# Your workflow webhook URL:
https://n8n.yourdomain.com/webhook/your-unique-id
# Test trigger (inactive workflow):
https://n8n.yourdomain.com/webhook-test/your-unique-id
# Send a test webhook:
curl -X POST https://n8n.yourdomain.com/webhook/your-unique-id \
-H "Content-Type: application/json" \
-d '{"event": "order.created", "orderId": 42, "total": 99.99}'
# With basic auth (if configured on webhook node):
curl -u user:pass -X POST https://n8n.yourdomain.com/webhook/your-unique-id \
-d '{"data": "value"}'
Maintenance
# Update n8n:
docker compose pull
docker compose up -d
# Backup:
tar -czf n8n-backup-$(date +%Y%m%d).tar.gz \
$(docker volume inspect n8n_n8n_data --format '{{.Mountpoint}}')
docker exec n8n-postgres-1 pg_dump -U n8n n8n \
| gzip > n8n-db-$(date +%Y%m%d).sql.gz
# Export all workflows as JSON:
docker exec n8n n8n export:workflow --all --output=/home/node/.n8n/workflows-export.json
# Import workflows:
docker exec n8n n8n import:workflow --input=/home/node/.n8n/workflows-export.json
# Logs:
docker compose logs -f n8n
Why Self-Host n8n
The cost difference between n8n self-hosted and Zapier is staggering. Zapier's Starter plan ($29.99/month, 750 tasks) gets exhausted quickly by active automations. Their Professional plan ($73.50/month, 2,000 tasks) is the minimum for a team with meaningful automation needs. The Business plan at $299/month covers 100,000 tasks. A busy engineering team can hit 100,000 tasks in a month with just a handful of active workflows (GitHub webhooks, Slack notifications, scheduled jobs).
n8n self-hosted costs the price of a VPS — $10-20/month for a 2 vCPU/4GB RAM machine that runs unlimited workflows with unlimited executions. One medium-complexity workflow on Zapier's Business plan might cost $50-100/month equivalent in task consumption. A dozen such workflows and you're at the $1,200/year Zapier bill versus $180/year for a VPS.
Beyond cost, n8n's code nodes are the feature that separates it from most automation tools. When an integration doesn't support exactly what you need, a code node lets you write JavaScript or Python to transform data, call APIs with custom headers, parse unusual formats, or implement complex business logic. Most Zapier workflows either need a "Code" step that's limited and poorly documented, or require building a full serverless function just to do a data transformation. n8n's code nodes are first-class citizens.
The AI integration story is also compelling. n8n's built-in LangChain support lets you build AI agent workflows that combine language models with real actions — query your database, send a Slack message, update a CRM record. This is the kind of automation that previously required custom engineering work.
When NOT to self-host n8n: n8n's Sustainable Use License means you can self-host for free, but building products that embed n8n for customers requires a commercial license. Read the license carefully for commercial use cases. Also, n8n's 400+ integrations is fewer than Zapier's 6,000+ — if you need a niche integration that n8n doesn't support, check before committing.
Prerequisites
n8n with PostgreSQL requires a server capable of running both the application and the database reliably.
Server specs: 2 vCPUs and 2GB RAM is the comfortable minimum for n8n plus PostgreSQL. Workflow executions are CPU-intensive if you have many concurrent runs or complex code nodes. For teams running automations continuously, 4 vCPUs and 4GB RAM gives you comfortable headroom. Check our VPS comparison for self-hosters — the $10-15/month tier from Hetzner or Contabo fits n8n well.
PostgreSQL vs SQLite: n8n ships with SQLite by default, but for production use, PostgreSQL is strongly recommended. SQLite works fine for light use, but it can't handle concurrent workflow executions reliably and doesn't support the full execution history features. The Docker Compose setup in Part 1 uses PostgreSQL.
Domain and HTTPS: n8n's webhook functionality requires a public HTTPS URL. Webhooks from external services (GitHub, Stripe, etc.) can't reach a server without a valid SSL certificate. Your domain needs to be publicly accessible and HTTPS needs to work before activating webhook-triggered workflows.
Encryption key: The N8N_ENCRYPTION_KEY is used to encrypt stored credentials (API keys, OAuth tokens). This key must be 32+ bytes and must never change after initial setup — changing it invalidates all stored credentials and you'll need to re-enter every API key.
Operating system: Ubuntu 22.04 LTS. n8n's Node.js runtime and PostgreSQL both run excellently on Ubuntu, and the Docker images are tested against this environment.
Skill level: Intermediate. n8n's visual editor is beginner-friendly, but server setup and troubleshooting require Docker and Linux familiarity.
Production Security Hardening
n8n stores credentials for every service it connects to — Slack tokens, GitHub API keys, database passwords, email credentials. This makes it a high-value target. Follow the self-hosting security checklist and these n8n-specific steps:
Firewall (UFW): Never expose port 5678 directly. Only allow HTTP/HTTPS through your reverse proxy.
sudo ufw default deny incoming
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
Disable user registration: By default, n8n allows new user registration. After setup, set N8N_USER_MANAGEMENT_JWT_SECRET and configure access control in Settings. For a single-user or small-team setup, consider setting N8N_BASIC_AUTH_ACTIVE: "true" as an additional layer.
Secrets management: The N8N_ENCRYPTION_KEY and database password are critical. Store them in .env:
# .env (never commit this)
N8N_ENCRYPTION_KEY=your-64-char-hex-key
POSTGRES_PASSWORD=your-db-password
ADMIN_PASSWORD=your-admin-password
echo ".env" >> .gitignore
Webhook security: Production webhooks should use authentication. Configure the webhook node with Header Auth or Basic Auth to verify the sender. This prevents random internet traffic from triggering your workflows.
Disable SSH password authentication: Edit /etc/ssh/sshd_config: set PasswordAuthentication no and PermitRootLogin no. Restart: sudo systemctl restart ssh.
Automatic security updates:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades
Regular backups: n8n's database contains all your workflows, credentials, and execution history. Back up both the n8n data volume and PostgreSQL database. See automated server backups with restic for automated off-site backup that handles both Docker volumes and database dumps.
Workflow Design Principles
n8n's flexibility is its greatest strength and biggest footgun. Without discipline, you end up with dozens of undocumented workflows that nobody understands, credentials that expired last year, and automations that silently stopped working months ago.
Name workflows descriptively from the start. "Webhook 3" is useless after a week. "GitHub PR → Slack notify #engineering" tells you immediately what it does, where data comes from, and where it goes. Use a consistent naming format: [Trigger source] → [Action type] [Destination]. This makes the workflow list scannable when you have 50+ workflows.
Add sticky notes to every workflow. n8n supports sticky note nodes that render as visual annotations in the canvas. Use them to document: why this workflow exists, what system it integrates, any non-obvious configuration decisions, and when it was last reviewed. Workflows without documentation become unmaintainable as soon as the person who built them leaves or forgets the context.
Test workflows in inactive mode before activating them. n8n provides separate webhook URLs for testing (/webhook-test/) versus production (/webhook/). Always test with real data in inactive mode, verify the output at each step, and only activate once you're confident the workflow behaves correctly. Activating untested workflows in production causes hard-to-diagnose issues when real data triggers unexpected code paths.
Build error handling explicitly. n8n's default behavior on node failure is to stop execution and mark the run as failed. For production workflows, add error branches using the "Error Trigger" workflow feature — n8n lets you designate a workflow that runs whenever another workflow fails, letting you send Slack alerts or log errors to a database. Without error handling, failures are silent.
Credential hygiene requires regular maintenance. OAuth2 tokens expire, API keys rotate, and services change their authentication requirements. Schedule a quarterly review of all credentials in n8n: test each one, rotate anything older than 6 months, and delete credentials for services you no longer use. An expired credential that silently fails is worse than one that was never set up — it looks like the workflow is working until you check the execution log.
Troubleshooting Common Issues
Webhooks trigger locally but fail from external services
The external service (GitHub, Stripe, etc.) can't reach your n8n server. Verify the WEBHOOK_URL in your .env is your public HTTPS URL, not localhost. Test from outside your network: curl -X POST https://n8n.yourdomain.com/webhook/test-id. Check that port 443 is open in your firewall and that SSL is working. Many external services also have IP allowlist requirements.
Credentials marked "invalid" after server restart
This means N8N_ENCRYPTION_KEY changed between restarts. The encryption key must be stable. If you're generating it dynamically in .env with $(openssl rand -hex 32), that generates a new key every time the environment is evaluated. Set a fixed value and keep it safe. If credentials are already invalid, you'll need to re-enter them all.
Workflow executions stuck in "running" or "waiting"
A workflow can get stuck if a node is waiting for a response that never comes (webhook, HTTP timeout) or if n8n crashed during execution. Check the execution log: n8n → Executions → find the stuck execution. You can stop it manually. Increase node timeouts for slow external services. If n8n itself is unresponsive, restart the container: docker compose restart n8n.
PostgreSQL connection errors after starting
n8n depends on PostgreSQL being fully ready. The healthcheck in the Docker Compose config handles this, but if PostgreSQL is slow to start (common on first run), n8n may timeout. Wait 30-60 seconds after docker compose up -d before accessing n8n. Check docker compose logs postgres to see if PostgreSQL is running.
Code nodes fail with "cannot find module"
n8n's code nodes run in a sandboxed Node.js environment and don't have access to npm packages by default. You can use standard JavaScript built-ins and the $ n8n variables, but require('lodash') won't work. For complex data manipulation, use the available built-in methods or restructure your logic to avoid external dependencies.
See all open source automation tools at OSSAlt.com/categories/automation.
See open source alternatives to n8n on OSSAlt.
The SaaS-to-Self-Hosted Migration Guide (Free PDF)
Step-by-step: infrastructure setup, data migration, backups, and security for 15+ common SaaS replacements. Used by 300+ developers.
Join 300+ self-hosters. Unsubscribe in one click.