How to Install n8n on Ubuntu 24.04 VPS: Self-Hosted Workflow Automation (Zapier Alternative)
Workflow automation has become the glue that holds modern business operations together. Marketing teams sync leads between forms and CRMs. DevOps teams chain alerts into ticket systems. Ecommerce shops reconcile orders, invoices, and shipping labels across a dozen SaaS tools. For years, the default answer has been Zapier or Make (formerly Integromat) -- polished, expensive, and hosted entirely on someone else's infrastructure.
n8n is the self-hosted alternative. It is open-source, source-available under a fair-code license, and runs anywhere you can run Docker. In this tutorial, you will install n8n on a fresh Ubuntu 24.04 VPS using Docker Compose with a production-grade stack: a Postgres database, Redis for queue mode, an Nginx reverse proxy with a free Let's Encrypt TLS certificate, and basic-auth protection. By the end, you will have a public https://n8n.yourdomain.com URL running your first workflow, ready to replace a handful of Zapier tasks without paying per execution.
Want a fast, low-cost VPS for n8n? The CloudCore Starter plan gives you 4 vCPU, 8 GB RAM, and 200 GB NVMe -- more than enough headroom for tens of thousands of monthly workflow executions. Deploy in minutes, keep your automations private, and never pay a per-task bill again.
Table of Contents
What is n8n?
n8n -- pronounced "n-eight-n", short for "nodemation" -- is an open-source workflow automation platform. You build automations visually by dragging nodes onto a canvas and connecting them with lines. Each node represents an action: read a row from a Google Sheet, send a Slack message, query a Postgres database, call an arbitrary HTTP API, or run a block of JavaScript or Python. Workflows fire on triggers -- cron schedules, incoming webhooks, new emails, file uploads, database changes, or manual clicks.
The platform ships with more than 400 built-in integrations covering the SaaS stack most businesses rely on: Slack, Discord, Gmail, Google Drive, Notion, Airtable, HubSpot, Salesforce, Stripe, Shopify, WooCommerce, GitHub, GitLab, Jira, Trello, Asana, Mailchimp, ActiveCampaign, AWS, GCP, Azure, Postgres, MySQL, MongoDB, Redis, and many more. When a native node does not exist, the generic HTTP Request node and Code node let you talk to any REST or GraphQL API and transform the response in JavaScript or Python.
n8n is source-available under the Sustainable Use License. That means you can self-host it for free for internal business purposes forever. You cannot resell it as a hosted SaaS competitor to n8n Cloud, but for the vast majority of teams that simply want to run workflows, the license is effectively as permissive as MIT. The project has raised tens of millions in venture capital, ships stable releases roughly every two weeks, and has a thriving community with thousands of shared workflow templates.
Why Self-Host n8n Instead of Using Zapier or Make?
Zapier and Make are excellent products, but their business model charges per "task" or "operation" -- every single action a workflow takes. At modest scale this gets expensive fast. A workflow that syncs 5,000 contacts per month from a form to a CRM and sends a Slack notification uses 10,000+ tasks. On Zapier's Professional plan that is around $50/month; scale to 100,000 tasks and you are north of $300/month.
Self-hosting n8n on your own VPS costs a flat monthly fee regardless of volume. But price is only half the story.
- Unlimited executions for a flat price -- Run 1,000 or 10,000,000 workflow executions per month. Your VPS bill stays the same.
- Your data stays on your server -- Customer PII, API keys, internal documents, and CRM exports never leave infrastructure you control. Huge wins for GDPR, HIPAA, SOC 2, and any compliance program that flags third-party data processors.
- No vendor lock-in -- Export any workflow as a JSON file. Move it to another n8n instance in seconds. No proprietary flow format.
- Run code without sandboxes -- The Code node runs arbitrary JavaScript or Python. Install npm packages. Hit internal APIs on your private network. Mount files. None of this is possible on Zapier.
- Self-hosted AI, databases, and private APIs -- Connect n8n to a local Ollama instance, an internal Postgres, or a VPN-only Jira without exposing them to the internet.
- Community nodes -- The registry has thousands of third-party nodes you can install in a single click. If someone built it, you can use it.
Cost Comparison: Zapier vs. Make vs. Self-Hosted n8n
| Scenario | Zapier Professional | Make Core | Self-Hosted n8n (VPS) |
|---|---|---|---|
| Monthly cost at 10k tasks | ~$50/mo | ~$16/mo | EUR 7.99/mo (flat) |
| Monthly cost at 100k tasks | ~$300/mo | ~$50/mo | EUR 7.99/mo (flat) |
| Monthly cost at 1M tasks | ~$1,500/mo | ~$300/mo | EUR 19.99/mo (flat) |
| Execution timeout | 30 sec (paid 10 min) | 40 min | Unlimited |
| Custom code | Limited JS | Limited JS | Full JS + Python + npm |
| Data residency | US/EU (theirs) | EU (theirs) | Your VPS |
| Workflow versioning | Paid tier only | Built-in | Git-native |
| Community nodes | Not supported | Not supported | 900+ available |
Prerequisites
Before you begin, make sure you have:
- A VPS running Ubuntu 24.04 LTS with root or sudo access
- SSH access to the server
- A domain name (for example
yourdomain.com) with DNS you can edit -- we will create ann8n.yourdomain.comsubdomain - Ports 80 and 443 open in your firewall and on your VPS provider's dashboard
- At least 2 GB RAM (4 GB+ recommended for queue mode with Redis)
- At least 20 GB free disk space for Docker images, Postgres data, and workflow execution history
Recommended Plan: CloudCore Starter>
For a production n8n instance handling a few hundred thousand executions per month comfortably, we recommend the CloudCore Starter plan:>
- 4 vCPU cores
- 8 GB RAM
- 200 GB NVMe SSD
- Unmetered bandwidth
- Free daily snapshots>
That gives you headroom for n8n, Postgres, Redis, Nginx, and a few side projects on the same server. For higher volumes or multiple worker processes, upgrade to CloudCore Professional at any time.
Connect to your server via SSH to get started:
ssh root@your-server-ipStep 1: Update the System and Install Docker
Start by updating your package index and upgrading installed packages:
sudo apt update && sudo apt upgrade -yInstall Docker and Docker Compose. We will use the official Docker repository so we get the latest stable release with the Compose plugin bundled in.
sudo apt install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpgecho "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
Verify both Docker and the Compose plugin are installed:
docker --version
docker compose versionExpected output:
Docker version 27.3.1, build ce12230
Docker Compose version v2.29.7If Docker is brand new to you, our full Install Docker on Ubuntu tutorial walks through post-install hardening, the docker group, and log rotation in more detail.
Step 2: Point Your Domain at the VPS
n8n needs a real HTTPS URL for webhooks to work reliably. Many SaaS tools (Stripe, GitHub, Shopify, etc.) refuse to send webhooks to HTTP endpoints or IP addresses.
In your DNS provider (Cloudflare, Namecheap, Gandi, Route 53, etc.), create an A record:
| Type | Name | Value | TTL |
|---|---|---|---|
| A | n8n | your-vps-public-ip | Auto / 300 |
dig +short n8n.yourdomain.comYou should see your VPS IP address. If not, wait a couple of minutes and try again before continuing -- the Let's Encrypt step depends on this DNS record being live.
Step 3: Create the n8n Project Directory and .env File
All configuration will live in a single directory under /opt/n8n. This keeps backups and migrations simple.
sudo mkdir -p /opt/n8n
cd /opt/n8nGenerate a strong encryption key. n8n uses this to encrypt credentials stored in the database -- if you lose this key, you lose access to every saved credential, so we will back it up later.
openssl rand -hex 32Copy the output -- a 64-character hex string -- and keep it in a password manager now.
Create the .env file:
sudo nano /opt/n8n/.envPaste the following, replacing the placeholder values with your own:
# Domain / URL configuration
N8N_HOST=n8n.yourdomain.com
N8N_PROTOCOL=https
N8N_PORT=5678
WEBHOOK_URL=https://n8n.yourdomain.com/
GENERIC_TIMEZONE=UTCBasic auth (first-line defence before you set up the owner account)
N8N_BASIC_AUTH_ACTIVE=true
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=change-me-to-a-long-random-passwordCredential encryption (DO NOT lose this)
N8N_ENCRYPTION_KEY=paste-your-64-char-hex-key-herePostgres
DB_TYPE=postgresdb
DB_POSTGRESDB_HOST=postgres
DB_POSTGRESDB_PORT=5432
DB_POSTGRESDB_DATABASE=n8n
DB_POSTGRESDB_USER=n8n
DB_POSTGRESDB_PASSWORD=another-long-random-passwordQueue mode (Redis)
EXECUTIONS_MODE=queue
QUEUE_BULL_REDIS_HOST=redis
QUEUE_BULL_REDIS_PORT=6379Keep execution history manageable
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=336 # hours (= 14 days)
EXECUTIONS_DATA_SAVE_ON_ERROR=all
EXECUTIONS_DATA_SAVE_ON_SUCCESS=all
EXECUTIONS_DATA_SAVE_MANUAL_EXECUTIONS=trueMisc
N8N_LOG_LEVEL=info
N8N_DIAGNOSTICS_ENABLED=false
N8N_VERSION_NOTIFICATIONS_ENABLED=trueSave (Ctrl+O, Enter) and exit (Ctrl+X).
Tighten permissions so other users on the box cannot read your secrets:
sudo chmod 600 /opt/n8n/.envStep 4: Write the Docker Compose File (n8n + Postgres + Redis)
Create docker-compose.yml in the same directory:
sudo nano /opt/n8n/docker-compose.ymlPaste the following:
services: postgres: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_USER: ${DB_POSTGRESDB_USER} POSTGRES_PASSWORD: ${DB_POSTGRESDB_PASSWORD} POSTGRES_DB: ${DB_POSTGRESDB_DATABASE} volumes: - postgres_data:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U ${DB_POSTGRESDB_USER} -d ${DB_POSTGRESDB_DATABASE}"] interval: 10s timeout: 5s retries: 5redis: image: redis:7-alpine restart: unless-stopped command: ["redis-server", "--appendonly", "yes"] volumes: - redis_data:/data healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 10s timeout: 5s retries: 5
n8n: image: n8nio/n8n:latest restart: unless-stopped depends_on: postgres: condition: service_healthy redis: condition: service_healthy ports: - "127.0.0.1:5678:5678" environment: - N8N_HOST=${N8N_HOST} - N8N_PROTOCOL=${N8N_PROTOCOL} - N8N_PORT=${N8N_PORT} - WEBHOOK_URL=${WEBHOOK_URL} - GENERIC_TIMEZONE=${GENERIC_TIMEZONE} - N8N_BASIC_AUTH_ACTIVE=${N8N_BASIC_AUTH_ACTIVE} - N8N_BASIC_AUTH_USER=${N8N_BASIC_AUTH_USER} - N8N_BASIC_AUTH_PASSWORD=${N8N_BASIC_AUTH_PASSWORD} - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY} - DB_TYPE=${DB_TYPE} - DB_POSTGRESDB_HOST=${DB_POSTGRESDB_HOST} - DB_POSTGRESDB_PORT=${DB_POSTGRESDB_PORT} - DB_POSTGRESDB_DATABASE=${DB_POSTGRESDB_DATABASE} - DB_POSTGRESDB_USER=${DB_POSTGRESDB_USER} - DB_POSTGRESDB_PASSWORD=${DB_POSTGRESDB_PASSWORD} - EXECUTIONS_MODE=${EXECUTIONS_MODE} - QUEUE_BULL_REDIS_HOST=${QUEUE_BULL_REDIS_HOST} - QUEUE_BULL_REDIS_PORT=${QUEUE_BULL_REDIS_PORT} - EXECUTIONS_DATA_PRUNE=${EXECUTIONS_DATA_PRUNE} - EXECUTIONS_DATA_MAX_AGE=${EXECUTIONS_DATA_MAX_AGE} - EXECUTIONS_DATA_SAVE_ON_ERROR=${EXECUTIONS_DATA_SAVE_ON_ERROR} - EXECUTIONS_DATA_SAVE_ON_SUCCESS=${EXECUTIONS_DATA_SAVE_ON_SUCCESS} - N8N_LOG_LEVEL=${N8N_LOG_LEVEL} - N8N_DIAGNOSTICS_ENABLED=${N8N_DIAGNOSTICS_ENABLED} volumes: - n8n_data:/home/node/.n8n - ./files:/files
n8n-worker: image: n8nio/n8n:latest restart: unless-stopped depends_on: - n8n command: worker environment: - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY} - DB_TYPE=${DB_TYPE} - DB_POSTGRESDB_HOST=${DB_POSTGRESDB_HOST} - DB_POSTGRESDB_PORT=${DB_POSTGRESDB_PORT} - DB_POSTGRESDB_DATABASE=${DB_POSTGRESDB_DATABASE} - DB_POSTGRESDB_USER=${DB_POSTGRESDB_USER} - DB_POSTGRESDB_PASSWORD=${DB_POSTGRESDB_PASSWORD} - EXECUTIONS_MODE=${EXECUTIONS_MODE} - QUEUE_BULL_REDIS_HOST=${QUEUE_BULL_REDIS_HOST} - QUEUE_BULL_REDIS_PORT=${QUEUE_BULL_REDIS_PORT} - GENERIC_TIMEZONE=${GENERIC_TIMEZONE} volumes: - n8n_data:/home/node/.n8n - ./files:/files
volumes: postgres_data: redis_data: n8n_data:
A few design notes on this compose file:
- Queue mode is on.
EXECUTIONS_MODE=queuemakes the main n8n container enqueue jobs into Redis; the separaten8n-workercontainer picks them up. This prevents long-running workflows from blocking the UI and webhook responses, and lets you scale horizontally by adding moren8n-workerreplicas later. - n8n listens only on localhost. The
127.0.0.1:5678:5678binding means n8n is not exposed to the public internet directly. Nginx will terminate TLS and proxy to it in the next step. - Postgres and Redis have no host ports. They are reachable only on the internal Docker network, which is exactly what we want.
- Named volumes (
postgres_data,redis_data,n8n_data) survivedocker compose down. A./filesbind mount is mapped into both n8n and the worker so workflows can read/write shared files.
Step 5: Start the Stack
Pull the images and start everything in detached mode:
cd /opt/n8n
sudo docker compose up -dExpected output (abbreviated):
[+] Running 4/4
✔ Network n8n_default Created
✔ Container n8n-postgres-1 Healthy
✔ Container n8n-redis-1 Healthy
✔ Container n8n-n8n-1 Started
✔ Container n8n-n8n-worker-1 StartedCheck container status:
sudo docker compose psAll four services (postgres, redis, n8n, n8n-worker) should report running or healthy. Tail the logs to confirm n8n finished booting:
sudo docker compose logs -f n8nLook for a line like:
Editor is now accessible via:
http://localhost:5678Press Ctrl+C to stop tailing. n8n is running, but it is only reachable on the VPS itself. Next we expose it to the internet through Nginx.
Step 6: Configure Nginx Reverse Proxy + TLS
Install Nginx and Certbot:
sudo apt install -y nginx certbot python3-certbot-nginxCreate the site configuration:
sudo nano /etc/nginx/sites-available/n8nPaste:
server { listen 80; listen [::]:80; server_name n8n.yourdomain.com;# Allow Let's Encrypt HTTP-01 challenge to pass through location /.well-known/acme-challenge/ { root /var/www/html; }
# Redirect everything else to HTTPS location / { return 301 https://$host$request_uri; } }
server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name n8n.yourdomain.com;
# Certbot will fill these in automatically ssl_certificate /etc/letsencrypt/live/n8n.yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/n8n.yourdomain.com/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# Security headers add_header X-Content-Type-Options nosniff; add_header X-Frame-Options SAMEORIGIN; add_header Referrer-Policy strict-origin-when-cross-origin;
# Allow large webhook payloads (file uploads etc.) client_max_body_size 50m;
location / { proxy_pass http://127.0.0.1:5678; proxy_http_version 1.1;
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 for the editor UI proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade";
# Long-running workflows can stream responses for a while proxy_read_timeout 3600s; proxy_send_timeout 3600s; proxy_buffering off; } }
Enable the site, remove the default, and let Certbot handle the certificate:
sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo certbot --nginx -d n8n.yourdomain.comCertbot will prompt for an email, ask you to agree to the ToS, ask about HTTP-to-HTTPS redirect (choose "2: Redirect"), obtain a certificate, and rewrite the Nginx config with the correct paths. Reload Nginx:
sudo nginx -t && sudo systemctl reload nginxCertbot installs a renewal timer automatically (sudo systemctl list-timers | grep certbot). Your cert will rotate every 60-90 days with zero intervention.
Open https://n8n.yourdomain.com in a browser. You should be prompted for the basic-auth credentials you set in .env (user admin, whatever password you chose).
Step 7: First Login and Basic-Auth
After passing basic-auth, n8n presents the Owner setup screen. This creates the first real user account stored inside n8n's own auth system.
A note on layered auth: the basic-auth variables (N8N_BASIC_AUTH_*) provide a first line of defence at the HTTP level, and the n8n owner/user system provides application-level auth and role-based access control. You can disable basic-auth once you have configured the owner account plus additional users, but many admins keep it on as belt-and-braces protection against bots hitting the login page.
Step 8: Build Your First Workflow with a Webhook Trigger
Let's build a minimal workflow: a webhook receives a JSON payload, the workflow formats a message, then posts it to an HTTP endpoint (swap the endpoint for a real Slack node later).
POST
- Path: order-created (n8n will build a full URL from this)
- Respond: Immediately
https://n8n.yourdomain.com/webhook/order-created.Build message. Add a single string field:text
- Value: ={{ "New order #" + $json.body.order_id + " from " + $json.body.customer.email }}
Build message to a new HTTP Request node (or a Slack node if you want to wire up real credentials later). Point the HTTP Request at https://httpbin.org/post, method POST, body {{ $json }}. This just echoes the payload -- perfect for a first test.First webhook demo.Test the webhook with curl from your laptop:
curl -X POST https://n8n.yourdomain.com/webhook-test/order-created \
-H "Content-Type: application/json" \
-d '{"order_id":"1001","customer":{"email":"[email protected]"}}'Back in the n8n editor, click Executions in the left sidebar. You should see the test run, click in, and inspect the data flowing through each node.
Once the flow behaves as expected, flip the Active toggle in the top right. n8n now listens on the production URL (/webhook/order-created, no -test). Wire that URL into Stripe, Shopify, Typeform, or any tool that sends webhooks, and your automation is live.
Step 9: Store Credentials Safely
Every service integration -- Slack, Gmail, Postgres, AWS, OpenAI -- needs credentials. n8n stores them in the database encrypted with the N8N_ENCRYPTION_KEY you generated in Step 3.
To add one:
http:// callback URLs.Credentials are now available in every workflow. You can reuse a single Slack credential across dozens of nodes without re-entering the token.
Two security reminders:
- Rotate keys regularly. If a VPS user account is compromised, assume any non-rotated credential is burned.
- Back up
N8N_ENCRYPTION_KEYseparately from the database. The encrypted credential blobs in Postgres are useless without this key. Losing it forces you to re-create every saved credential from scratch.
Step 10: Install Community Nodes
The n8n community node registry has hundreds of third-party nodes: OpenAI assistants, Discord slash commands, Supabase storage, ClickUp, SerpApi, and plenty of niche SaaS integrations.
Because our Docker image runs as the non-root node user, the cleanest install path is via the settings UI:
n8n-nodes-puppeteer.n8n downloads the package, restarts the Node runtime inside the container, and the new node appears in the node picker on the canvas.
To install community nodes at image-build time instead (useful for Infrastructure-as-Code setups), create a small custom Dockerfile:
FROM n8nio/n8n:latest
USER root
RUN cd /usr/local/lib/node_modules/n8n && \
npm install --omit=dev n8n-nodes-puppeteer n8n-nodes-supabase
USER nodePoint your docker-compose.yml at this image via a build: block and rebuild with docker compose up -d --build.
Upgrading n8n
n8n ships a new minor release roughly every two weeks. Staying on :latest works, but for production you should pin a specific tag and upgrade deliberately after reading the release notes.
Pin the version in docker-compose.yml:
n8n:
image: n8nio/n8n:1.74.0
n8n-worker:
image: n8nio/n8n:1.74.0Upgrade procedure:
cd /opt/n8n1. Back up the database first (see next section)
2. Pull the new version and recreate
sudo sed -i 's|n8nio/n8n:1.74.0|n8nio/n8n:1.76.3|g' docker-compose.yml
sudo docker compose pull
sudo docker compose up -d3. Tail logs; watch for "Editor is now accessible" after any DB migration
sudo docker compose logs -f n8nn8n runs any pending database migrations automatically on start. If a migration fails, n8n refuses to boot -- roll back by restoring the Postgres dump from the previous step and pinning the previous tag.
Backing Up n8n (Postgres + Encryption Key)
A complete n8n backup has three parts:
/opt/n8n directory -- your .env, docker-compose.yml, and any files under ./files.Daily Postgres Dump
Create /opt/n8n/backup.sh:
sudo nano /opt/n8n/backup.shPaste:
#!/usr/bin/env bash set -euo pipefailSTAMP=$(date +%F_%H%M) BACKUP_DIR=/opt/n8n/backups mkdir -p "$BACKUP_DIR"
Dump the database from the running postgres container
docker compose -f /opt/n8n/docker-compose.yml exec -T postgres \ pg_dump -U n8n -d n8n --format=custom \ > "$BACKUP_DIR/n8n_${STAMP}.pgdump"Keep 14 daily dumps, delete older ones
find "$BACKUP_DIR" -name "n8n_*.pgdump" -mtime +14 -delete
echo "Backup complete: $BACKUP_DIR/n8n_${STAMP}.pgdump"
Make it executable and schedule it:
sudo chmod +x /opt/n8n/backup.sh
sudo crontab -eAppend:
0 3 * /opt/n8n/backup.sh >> /var/log/n8n-backup.log 2>&1At 03:00 daily, Postgres is dumped to /opt/n8n/backups/. For real disaster recovery, sync that folder off-server -- rclone to S3/B2/Wasabi, restic to another VPS, or the snapshot feature of your VPS provider.
Restoring a Backup
On a fresh Ubuntu 24.04 VPS, restore these three things:
/opt/n8n/.env (including the original N8N_ENCRYPTION_KEY) and docker-compose.yml.docker compose up -d.# Stop n8n, keep postgres running
docker compose stop n8n n8n-workerDrop and recreate the empty database
docker compose exec -T postgres psql -U n8n -d postgres -c "DROP DATABASE IF EXISTS n8n;"
docker compose exec -T postgres psql -U n8n -d postgres -c "CREATE DATABASE n8n OWNER n8n;"Restore
cat /opt/n8n/backups/n8n_2026-04-16_0300.pgdump | \
docker compose exec -T postgres pg_restore -U n8n -d n8n --no-ownerRestart n8n
docker compose start n8n n8n-workerLog in: every workflow, user, and credential is back.
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| Editor loads but shows "This site can't be reached" after a moment | WebSocket blocked by Nginx | Ensure proxy_set_header Upgrade $http_upgrade; and proxy_set_header Connection "upgrade"; are in your Nginx config. Reload Nginx. |
Webhook returns 404 not-registered | Workflow not activated, or using the test URL after it expired | Toggle the workflow to Active. Use the Production URL (/webhook/...), not the test URL (/webhook-test/...). |
Error: No key found. Please create encryption key in logs | Missing or truncated N8N_ENCRYPTION_KEY | Check that .env has the full 64-char hex key and the compose file passes N8N_ENCRYPTION_KEY to both n8n and n8n-worker. |
| Workflows queue up but never execute | Worker container not running or not reading the Redis queue | docker compose ps; check n8n-worker logs. Verify EXECUTIONS_MODE=queue and QUEUE_BULL_REDIS_HOST=redis are set on the worker. |
| Let's Encrypt fails with "Timeout during connect" | Port 80 blocked by firewall or provider | Open TCP 80 and 443 on the VPS firewall (sudo ufw allow 80; sudo ufw allow 443) and in the provider control panel. |
getaddrinfo ENOTFOUND postgres in n8n logs | Postgres container not yet healthy when n8n started | The depends_on: condition: service_healthy should prevent this; if it still happens, bump the Postgres healthcheck retries to 10. |
| Memory climbs to 100% and OOM kills n8n | Execution data retention too long | Lower EXECUTIONS_DATA_MAX_AGE from 336 hours. For high-volume setups set EXECUTIONS_DATA_SAVE_ON_SUCCESS=none to only keep failures. |
| Basic-auth prompt keeps looping in browser | Password has special chars that .env reads as variable | Wrap the password in single quotes in .env, or escape $ as $$. |
Viewing Logs
# All services
sudo docker compose -f /opt/n8n/docker-compose.yml logs -fJust n8n
sudo docker compose -f /opt/n8n/docker-compose.yml logs -f n8nWorker
sudo docker compose -f /opt/n8n/docker-compose.yml logs -f n8n-workerPostgres
sudo docker compose -f /opt/n8n/docker-compose.yml logs -f postgresFAQ
How many workflows can a single CloudCore Starter VPS handle?
It depends almost entirely on what each workflow does. A simple webhook-to-Slack flow takes a few hundred milliseconds of CPU. A workflow that hits three APIs, processes JSON in a Code node, and writes to Postgres might take 1-2 seconds. On a 4 vCPU / 8 GB plan, most teams comfortably handle 200,000+ executions per month with the queue-mode setup in this guide, and scale by adding n8n-worker replicas before scaling the VPS itself.
Do I actually need queue mode and Redis for a small deployment?
Not strictly. For a handful of workflows with short runtimes, main-mode (no Redis, no worker container) works fine and uses about 400 MB less RAM. But queue mode costs almost nothing on modern hardware, avoids a whole class of issues where a long-running workflow blocks webhook responses, and lets you scale horizontally by adding more worker containers later without any config change. We recommend queue mode from day one on any production deployment.
Can I run n8n behind Cloudflare?
Yes, and many users do. Enable Full (Strict) SSL mode in Cloudflare so the edge-to-origin connection is also encrypted. Cloudflare's default WebSocket support is on, which keeps the editor UI responsive. One caveat: set N8N_PROXY_HOPS=1 in your .env so n8n correctly interprets the X-Forwarded-For chain (Cloudflare -> Nginx -> n8n) for rate limiting and audit logs.
How does n8n compare to Node-RED and Huginn?
All three are self-hosted workflow automation tools, but they target different audiences.
Node-RED originated at IBM for wiring IoT devices together. It is JavaScript-first, lightweight, and ships with strong support for MQTT, serial devices, Modbus, and hardware protocols. Best for home automation, industrial IoT, and maker projects.
Huginn is a Ruby-on-Rails agent system modelled on "tiny programs that check websites and events and act on your behalf". It is ideal for web scraping, RSS processing, and personal automation, with deep support for building custom agents.
n8n targets business SaaS integration. Its 400+ native nodes for Gmail, Slack, Salesforce, HubSpot, Stripe, Notion, etc. are far richer than either Node-RED or Huginn out of the box. If your automations are "when X happens in SaaS tool A, do Y in SaaS tool B", n8n is the best fit. If they are "when a sensor reports a temperature above 30C, cut power to this relay", Node-RED wins.
Is n8n really free forever?
For self-hosted internal business use, yes. n8n's Sustainable Use License permits unlimited internal use (employees, contractors, internal tooling) at no cost. The only thing it prohibits is offering n8n itself as a hosted service to third parties in competition with n8n Cloud. Everything covered in this tutorial -- running automations for your own company -- is 100% free forever on your own VPS.
Next Steps
Your n8n instance is installed, TLS-secured, running in queue mode, and backed up. Here are recommended directions to go from here:
- Wire up real integrations -- Connect Stripe, Shopify, HubSpot, or Gmail. Check the official integrations catalog and start replacing one Zapier task at a time. Every Zap you migrate is a line-item off your SaaS bill.
- Import workflow templates -- The n8n template library has 1,500+ pre-built workflows. Click Import in the n8n editor, paste a template URL, and adapt the nodes to your credentials.
- Add a second worker for scale -- Duplicate the
n8n-workerservice indocker-compose.yml(name itn8n-worker-2), thendocker compose up -d. Redis fans jobs out automatically across all workers. Repeat until CPU stops being the bottleneck.
- Pair n8n with a self-hosted AI stack -- Point n8n's OpenAI-compatible node at a local Ollama instance to run chat, summarisation, and classification flows with zero per-token cost.
- Add monitoring -- Scrape n8n's Prometheus metrics (set
N8N_METRICS=true) into Grafana to track executions per minute, workflow error rate, and queue depth. Alert on queue depth growing faster than the worker can drain it.
- Secure with 2FA and SSO -- The n8n owner account supports TOTP-based 2FA out of the box. For teams, enable SAML SSO in Settings -> SSO to delegate auth to Okta, Google Workspace, or Azure AD.
- Read the official docs for deep dives -- The n8n documentation covers environment variables, expression syntax, the Code node, SSO, and the dozens of configuration options not touched in this tutorial.
Deploy n8n on a Fast, Quiet VPS>
Our CloudCore Starter plan gives you 4 vCPU, 8 GB RAM, and 200 GB NVMe -- tuned for Docker workloads like n8n, Postgres, and Redis side by side. Daily snapshots are free, bandwidth is unmetered, and our support team actually runs these stacks in production.>
Launch Your VPS Now -- Plans from EUR 7.99/month.