n8n on a VPS: Self-Hosted Workflow Automation with 400+ Integrations and No-Code Flows

n8n on a VPS: Self-Hosted Workflow Automation with 400+ Integrations and No-Code Flows

n8n is an open-source workflow automation platform with 400+ integrations — Slack, GitHub, Notion, Google Sheets, Postgres, Stripe, Mailgun, OpenAI, and hundreds more — connected through a visual node-based editor. n8n Cloud costs $20–$50/month. Self-hosting on a VPS provides unlimited executions and workflows, custom code nodes, and private data processing at infrastructure cost.

n8n vs Zapier vs Make (Integromat)

  • n8n (self-hosted): Unlimited executions, code nodes for custom logic, complex branching, self-managed. Developer-friendly.
  • Zapier: Easiest to use, 5,000+ apps, $20–$100/month, limited executions per plan
  • Make (Integromat): More powerful than Zapier, visual data mapping, $9–$29/month
  • Choose n8n: Developer teams, sensitive data that shouldn’t leave your infrastructure, complex workflows with custom code, or cost-consciousness at high execution volumes

Step 1: Docker Compose with PostgreSQL

<code">mkdir -p /opt/n8n && cd /opt/n8n
nano docker-compose.yml
<code">version: '3.8'

services:
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: always
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      - N8N_HOST=n8n.yourdomain.com
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.yourdomain.com/
      - N8N_BASIC_AUTH_ACTIVE=false    # Using n8n's own auth instead
      - N8N_USER_MANAGEMENT_JWT_SECRET=${JWT_SECRET}
      # Database — PostgreSQL (recommended for production)
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_HOST=n8n-postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      # Timezone
      - GENERIC_TIMEZONE=UTC
      - TZ=UTC
      # Execution settings
      - EXECUTIONS_PROCESS=main
      - EXECUTIONS_DATA_SAVE_ON_ERROR=all
      - EXECUTIONS_DATA_SAVE_ON_SUCCESS=none   # Don't save all — saves disk
      - EXECUTIONS_DATA_MAX_AGE=168             # Hours (7 days)
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      n8n-postgres:
        condition: service_healthy

  n8n-postgres:
    image: postgres:16-alpine
    container_name: n8n-postgres
    restart: always
    environment:
      POSTGRES_DB: n8n
      POSTGRES_USER: n8n
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - n8n_db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U n8n"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  n8n_data:
  n8n_db:
<code">cat > .env << 'EOF'
POSTGRES_PASSWORD=StrongN8nDbPassword!
JWT_SECRET=$(openssl rand -hex 32)
EOF
chmod 600 .env

docker compose up -d
docker compose logs -f n8n

Step 2: Nginx Reverse Proxy

<code">sudo nano /etc/nginx/sites-available/n8n
<code">server {
    listen 80;
    server_name n8n.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name n8n.yourdomain.com;

    ssl_certificate /etc/letsencrypt/live/n8n.yourdomain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/n8n.yourdomain.com/privkey.pem;

    client_max_body_size 50M;

    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;
        proxy_read_timeout 300s;
    }
}
<code">sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
sudo certbot --nginx -d n8n.yourdomain.com
sudo systemctl reload nginx

Step 3: Initial Setup

  1. Visit https://n8n.yourdomain.com
  2. Create owner account (first user)
  3. Settings → Users → invite team members
  4. Credentials → store API keys (GitHub token, Slack bot token, OpenAI key, etc.)

Step 4: Build Example Workflows

Workflow 1: GitHub Issue → Slack Notification

<code"># Nodes:
# 1. Webhook trigger (receives GitHub webhook on issue events)
# 2. IF node: filter for "opened" or "labeled:urgent" issues
# 3. Slack node: post message to #engineering channel
# 4. Notion node: create database entry for tracking

# GitHub Webhook URL: https://n8n.yourdomain.com/webhook/github-issues
# Add in GitHub repo → Settings → Webhooks → Payload URL

Workflow 2: Daily Database Report → Email

<code"># Nodes:
# 1. Schedule trigger: cron "0 9 * * 1-5" (9 AM weekdays)
# 2. PostgreSQL node: run query "SELECT count(*) as new_users FROM users WHERE created_at > NOW()-INTERVAL '1 day'"
# 3. Code node: format the data into a readable string
# 4. Gmail/Mailgun node: send report email to team

Workflow 3: AI-Powered Slack Bot

<code"># Nodes:
# 1. Slack trigger: listens for @mentions or DMs to the bot
# 2. HTTP Request node: POST to OpenAI API with message as prompt
# 3. Slack node: reply with AI response in the same thread

# In Slack App settings → Event Subscriptions → Request URL:
# https://n8n.yourdomain.com/webhook/slack-bot

Step 5: Custom Code Nodes

n8n’s Code node executes JavaScript with access to all input data:

<code"># Example Code node — transform data
const items = $input.all();

return items.map(item => {
    const data = item.json;
    return {
        json: {
            id: data.id,
            email: data.email.toLowerCase(),
            full_name: `${data.first_name} ${data.last_name}`.trim(),
            created_date: new Date(data.created_at).toISOString().split('T')[0],
            is_premium: data.plan === 'pro' || data.plan === 'enterprise',
        }
    };
});

Step 6: Webhooks for External Triggers

<code"># n8n generates webhook URLs automatically when you add a Webhook node
# Format: https://n8n.yourdomain.com/webhook/YOUR-UUID

# Test your webhook:
curl -X POST https://n8n.yourdomain.com/webhook/YOUR-UUID \
  -H "Content-Type: application/json" \
  -d '{"event": "test", "user_id": 123}'

# For production webhooks, use the "Production URL" (always active)
# Test URL only works when workflow editor is open

Step 7: Environment Variables for Credentials

<code"># Store sensitive values in environment variables instead of n8n's credential store
# docker-compose.yml environment:
- N8N_EXTERNAL_HOOK_FILES=/home/node/.n8n/hooks.js

# Reference in workflow expressions:
# {{ $env.OPENAI_API_KEY }}
# {{ $env.STRIPE_SECRET }}

# Add to .env file:
echo "OPENAI_API_KEY=sk-..." >> .env
echo "STRIPE_SECRET=sk_live_..." >> .env

Getting Started

n8n needs 500 MB–1 GB RAM for the server and PostgreSQL combined. A 2 GB Ubuntu VPS at VPS.DO runs n8n with room for moderate workflow volumes. For high-throughput automations (thousands of executions per day), scale to 4 GB and consider a separate PostgreSQL instance. The EXECUTIONS_DATA_SAVE_ON_SUCCESS=none setting significantly reduces disk and database usage for high-volume workflows.

Conclusion

Self-hosted n8n provides Zapier-like automation with 400+ integrations, unlimited executions, custom JavaScript code nodes, and private data processing — at VPS cost. For developer teams automating internal workflows, the code node capability separates n8n from purely no-code tools: you can write real JavaScript for complex data transformation, conditional logic, and API calls that don’t have a dedicated n8n node. Sensitive data (customer PII, API keys, internal metrics) never leaves your infrastructure.

Fast • Reliable • Affordable VPS - DO It Now!

Get top VPS hosting with VPS.DO’s fast, low-cost plans. Try risk-free with our 7-day no-questions-asked refund and start today!