n8n is an open-source workflow automation platform that serves as an alternative to Zapier or Make. The Community Edition can be self-hosted - on Azure for approximately 45-90 EUR/month, significantly cheaper than the cloud version.
Architecture Overview
┌─────────────────────┐
│ Azure Front Door │
│ (HTTPS/SSL/WAF) │
└──────────┬──────────┘
│
┌──────────────────────────────┼──────────────────────────────────┐
│ Azure Container Instances │
│ │ │
│ ┌───────────────────────────▼───┐ ┌─────────────────┐ │
│ │ n8n-main │ │ n8n-runner │ │
│ │ (UI/API/Webhooks/Broker) │ │ (Task Runner) │ │
│ │ Port 5678 │ │ │ │
│ └───────────────────────────────┘ └─────────────────┘ │
│ │ │
│ Azure Files Share (/home/node/.n8n) │
└────────────────────────┼─────────────────────────────────────────┘
│
┌──────▼──────┐
│ PostgreSQL │
│ Flexible │
└─────────────┘
Components and Costs
| Component | Azure Service | SKU | ~Cost/Month |
|---|---|---|---|
| n8n Container | Container Instances | 2x 1 vCPU, 2 GB RAM | 30-60 EUR |
| Database | PostgreSQL Flexible Server | B_Standard_B1ms | 15-25 EUR |
| Persistence | Storage Account + File Share | Standard LRS | 1-5 EUR |
| Reverse Proxy | Azure Front Door | Standard | incl. |
| Secrets | Azure Key Vault | Standard | <1 EUR |
| Monitoring | Log Analytics | 30 days retention | incl. |
| Backup | Recovery Services Vault | Daily, 7 days | <5 EUR |
| Total | 45-90 EUR |
Container Configuration (n8n 2.x)
n8n 2.x uses two containers:
n8n-main - Main container for UI, API, Webhooks and Task Broker:
- Image:
n8nio/n8n:2.2.3 - Port: 5678 (external), 5679 (Task Broker internal)
- Resources: 1 vCPU, 2 GB RAM
n8n-runner - Isolated code execution for Task Runner:
- Image:
n8nio/n8n-runner:2.2.3 - Connects to broker at
http://127.0.0.1:5679 - Resources: 1 vCPU, 2 GB RAM
Essential Environment Variables
# Database
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=<postgres-server>.postgres.database.azure.com
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8nadmin
DB_POSTGRESDB_PASSWORD=<from-key-vault>
DB_POSTGRESDB_SSL_ENABLED=true
# URLs (important for webhooks!)
N8N_HOST=n8n.example.com
N8N_PROTOCOL=https
N8N_PORT=443
WEBHOOK_URL=https://n8n.example.com/
N8N_EDITOR_BASE_URL=https://n8n.example.com/
# Security
N8N_ENCRYPTION_KEY=<from-key-vault>
N8N_SECURE_COOKIE=true
# Task Runner (n8n 2.x)
N8N_RUNNERS_ENABLED=true
N8N_RUNNERS_MODE=internal_launcher
N8N_RUNNERS_BROKER_LISTEN_ADDRESS=127.0.0.1
# Runner Container
N8N_RUNNERS_AUTH_TOKEN=<from-key-vault>
N8N_RUNNER_TASK_BROKER_URI=http://127.0.0.1:5679
# Timezone
GENERIC_TIMEZONE=Europe/Berlin
Azure Front Door Configuration
Front Door handles HTTPS termination, SSL certificate management, and WAF.
Origin Group:
- Health Probe:
GET /healthzevery 30 seconds via HTTP - Load Balancing: Latency-based
Origin:
- Host:
<container-name>.<region>.azurecontainer.io - HTTP Port: 5678
- Forwarding Protocol: HTTP Only (container has no SSL)
Route:
- Custom Domain with Managed Certificate
- HTTPS Redirect enabled
- Caching disabled (SPA compatibility)
Fixing HTTP/2 Protocol Errors
A common issue: Browsers show ERR_HTTP2_PROTOCOL_ERROR even though curl works fine. The cause is Brotli compression by Front Door.
Solution: Create a Rule Set that removes the Accept-Encoding header:
# Create Rule Set
az afd rule-set create \
--profile-name <profile> \
--resource-group <rg> \
--rule-set-name N8NNoCompression
# Add Rule
az afd rule create \
--profile-name <profile> \
--resource-group <rg> \
--rule-set-name N8NNoCompression \
--rule-name RemoveAcceptEncoding \
--order 1 \
--action-name ModifyRequestHeader \
--header-action Delete \
--header-name "Accept-Encoding"
# Connect Rule Set with Route
az afd route update \
--profile-name <profile> \
--resource-group <rg> \
--endpoint-name <endpoint> \
--route-name <route> \
--rule-sets N8NNoCompression
PostgreSQL Configuration
- Version: 15
- SKU: B_Standard_B1ms (Burstable, cost-effective)
- SSL: Required
- Firewall: Allow Azure Services (for Container Instances)
Persistence with Azure Files
n8n stores credentials and binary data under /home/node/.n8n. Without persistent storage, these are lost on container restart.
# Storage Account
resource "azurerm_storage_account" "n8n" {
name = "stn8nautomation"
resource_group_name = azurerm_resource_group.main.name
location = azurerm_resource_group.main.location
account_tier = "Standard"
account_replication_type = "LRS"
}
# File Share
resource "azurerm_storage_share" "n8n" {
name = "n8n-data"
storage_account_id = azurerm_storage_account.n8n.id
quota = 5 # GB
}
Mount in the container as a volume to /home/node/.n8n.
Secrets Management
Never store passwords and keys in Terraform state or environment variables in plain text:
# Generate random secrets
resource "random_password" "postgres" {
length = 32
special = true
}
resource "random_password" "n8n_encryption_key" {
length = 64
special = false
}
# Store in Key Vault
resource "azurerm_key_vault_secret" "postgres_password" {
name = "postgres-admin-password"
value = random_password.postgres.result
key_vault_id = azurerm_key_vault.main.id
}
Monitoring and Alerts
Log Analytics Workspace with ContainerInsights for:
- CPU/Memory utilization
- Container restarts
- Health probe status
Recommended Alerts:
- CPU > 80% over 5 minutes
- Memory > 85% over 5 minutes
- Container Restart Count > 3
Backup Strategy
Recovery Services Vault for Azure File Share:
- Daily backup
- 7 days retention
- Point-in-time recovery possible
n8n 2.x Specifics
- Publish Workflow: Changes must be explicitly “Published” after saving
- Task Runners: Enabled by default, code nodes run isolated
- Internal User Management: Basic Auth is deprecated, create owner account on first access
Terraform Structure
terraform/
├── main.tf # Data Sources
├── versions.tf # Provider (azurerm >= 3.0)
├── variables.tf # Input variables
├── locals.tf # Tags, computed values
├── container.tf # Container Instances (2 containers)
├── postgres.tf # PostgreSQL Flexible Server
├── storage.tf # Storage Account, File Share, Backup
├── keyvault.tf # Key Vault with Secrets
├── secrets.tf # Password generation
├── security.tf # Defender, Budget
├── monitoring.tf # Log Analytics, Alerts
└── outputs.tf # URLs, Connection Strings
Cost Optimization Tips
- Burstable PostgreSQL instead of General Purpose (saves ~50%)
- LRS instead of GRS for Storage (saves ~50%)
- Log Retention 30 days instead of longer
- No Redis Cache - n8n works without it for small/medium workloads
- Budget Alerts to avoid surprises
Conclusion
Self-hosting n8n Community Edition on Azure provides a cost-effective alternative to the cloud version while maintaining full control over your automation workflows. The combination of Container Instances, PostgreSQL Flexible Server, and Azure Front Door offers a production-ready setup with proper security, monitoring, and backup capabilities.
The initial setup requires some effort, but the monthly savings compared to managed n8n plans make it worthwhile for teams with Azure expertise.