Skip to main content
Back to blog

Automate your business with self-hosted n8n: a workflow guide

Ray MartínRay Martín
10 min read
Automate your business with self-hosted n8n: a workflow guide

n8n is an open-source (fair-code) workflow automation platform that lets you connect apps, APIs, and services without writing much code. Unlike Zapier or Make, where you pay per task executed, self-hosted n8n runs on your own infrastructure at a fixed cost: the server. That completely changes the economics when you have high-volume flows or sensitive data you don't want passing through third-party servers.

What is n8n and why self-host it?

n8n (pronounced "n-eight-n") is a node-based workflow engine. Each node represents an action: calling an API, transforming data, sending an email, writing to a database, running JavaScript code… Nodes are chained together visually in a drag-and-drop editor, and the engine executes them in order (or in parallel, depending on the flow).

The fair-code model means the source code is public and you can deploy it for free, but you can't use it as a competing SaaS to n8n Inc. For most businesses and freelancers, this is irrelevant: you simply install it and use it.

Why self-host instead of n8n Cloud (or Zapier/Make)?

  • Fixed cost: A $10–20/month VPS can run thousands of daily executions. With Zapier Business you pay ~$0.02–0.05 per task; at 50,000 tasks/month that's $1,000–2,500/month.
  • Full data control: Your credentials, customer data, and business logic never leave your infrastructure.
  • No artificial limits: n8n Cloud has execution limits per plan; self-hosted is only limited by your hardware.
  • Customization: You can create custom nodes in Node.js, modify timeouts, and integrate with internal services not accessible from the Internet.
  • Backups and disaster recovery: You have direct control over the database and configuration files.

The trade-off is maintenance: updates, backups, and monitoring are your responsibility. But with Docker and a solid docker-compose, the process is very manageable.

Installation with Docker

The fastest way to try n8n is with a single Docker command. For production, the recommended setup uses docker-compose with PostgreSQL as a persistent database.

Quick start (exploration only)

bash
# Start n8n with SQLite (for local development only)
docker run -it --rm   --name n8n   -p 5678:5678   -v ~/.n8n:/home/node/.n8n   n8nio/n8n:latest

# Open in browser
open http://localhost:5678

With SQLite, data is saved to ~/.n8n. Don't use this in production: SQLite doesn't support queue mode or multiple workers.

Production: docker-compose with PostgreSQL

For a real deployment you need PostgreSQL, properly configured environment variables, and persistent volumes. Here's a complete docker-compose.yml:

yaml
version: "3.8"

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

  n8n:
    image: n8nio/n8n:latest
    restart: unless-stopped
    ports:
      - "5678:5678"
    environment:
      # Database
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: postgres
      DB_POSTGRESDB_PORT: 5432
      DB_POSTGRESDB_DATABASE: n8n
      DB_POSTGRESDB_USER: n8n
      DB_POSTGRESDB_PASSWORD: ${POSTGRES_PASSWORD}

      # Public URL (required for webhooks)
      N8N_HOST: ${N8N_HOST}
      N8N_PORT: 5678
      N8N_PROTOCOL: https
      WEBHOOK_URL: https://${N8N_HOST}/

      # Credential encryption (do NOT change after initial setup!)
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}

      # General settings
      GENERIC_TIMEZONE: Europe/Madrid
      N8N_LOG_LEVEL: info
      N8N_METRICS: "true"

      # Basic auth (or use SSO in enterprise versions)
      N8N_BASIC_AUTH_ACTIVE: "true"
      N8N_BASIC_AUTH_USER: ${N8N_BASIC_AUTH_USER}
      N8N_BASIC_AUTH_PASSWORD: ${N8N_BASIC_AUTH_PASSWORD}
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      postgres:
        condition: service_healthy

volumes:
  postgres_data:
  n8n_data:

The corresponding .env file (never commit this):

bash
POSTGRES_PASSWORD=a_secure_password_here
N8N_HOST=n8n.yourdomain.com
N8N_ENCRYPTION_KEY=generate_with_openssl_rand_hex_32
N8N_BASIC_AUTH_USER=admin
N8N_BASIC_AUTH_PASSWORD=another_secure_password

To generate the N8N_ENCRYPTION_KEY:

bash
openssl rand -hex 32

Start the stack with:

bash
docker compose up -d
docker compose logs -f n8n  # follow logs in real time

Your first workflow

Once inside the editor, the most basic workflow has three nodes: a trigger, a transformation, and an output action. The editor is visual: you drag nodes from the side panel, connect them with arrows, and configure each one in the right panel.

The canonical "hello world" flow for understanding the concepts:

  1. Manual Trigger: Fires the flow manually from the editor. Perfect for testing without needing an external event.
  2. Set: The most-used node for transforming data. Define fields, rename properties, combine data from previous nodes.
  3. HTTP Request: Calls any REST API. Supports all HTTP methods, headers, OAuth2/Basic/API Key authentication, automatic pagination, and JSON/XML/HTML parsing.

Key concepts of n8n's execution model:

  • Items: n8n processes arrays of items. If your HTTP node receives 50 records, the next node processes them individually (by default) or in batch.
  • Branches: You can split the flow with the IF or Switch node to take different paths based on conditions.
  • Merge: The Merge node combines results from multiple branches back into a single flow.
  • Error handling: Each node has an error output; you can chain it to a node that sends alerts to Slack, email, etc.

The editor saves every execution with its input and output per node. This makes debugging trivial: click any node in a past execution and see exactly what data went in and came out.

Webhooks: triggering flows from outside

Webhooks are the way to integrate n8n with the outside world: when Stripe confirms a payment, when GitHub opens an issue, when a form is submitted, when your own app emits an event… n8n's Webhook node exposes a unique URL that any service can call via HTTP.

Each webhook has two URLs:

  • Test URL (/webhook-test/{id}): only active while you have the flow open in the editor and click "Listen for Test Event". Perfect for testing.
  • Production URL (/webhook/{id}): active when the flow is saved and activated. This is what you configure in external services.

Example of calling a webhook from curl:

bash
# Trigger a webhook with JSON data
curl -X POST https://n8n.yourdomain.com/webhook/abc123-def456-ghi789   -H "Content-Type: application/json"   -H "X-Secret-Token: my_secret_token"   -d '{
    "event": "payment_confirmed",
    "order_id": "ORD-2026-001",
    "amount": 149.99,
    "customer": {
      "email": "customer@example.com",
      "name": "Ana Garcia"
    }
  }'

# Expected response (if the workflow is active)
# {"message":"Workflow was started"}

In the Webhook node you can configure:

  • HTTP Method: GET, POST, PUT, PATCH, DELETE.
  • Authentication: None, Basic Auth, Header Auth (to verify the secret token from the example above).
  • Response Mode: "On Received" (responds immediately with 200 and processes in the background) or "Last Node" (waits for the flow to complete and returns the result).
  • Binary data: Can receive files if you configure the appropriate Content-Type.

Credentials and security

n8n has an encrypted credential system. When you configure a Gmail, Slack, database, or external API node, the credentials are stored encrypted in the database using the N8N_ENCRYPTION_KEY. This means:

  • Credentials never appear in plain text in logs or execution data.
  • You can share workflows between environments without exposing tokens: the workflow references credentials by name, not by value.
  • If someone accesses your database, they can't read the credentials without the encryption key.

Fundamental security rules:

  • Generate the N8N_ENCRYPTION_KEY before first startup and store it in a secrets manager (1Password, Vault, AWS Secrets Manager). If you lose it or change it, all stored credentials become unusable.
  • Don't hardcode credentials in nodes: Always use the credential system. Values hardcoded in a "Set" or "HTTP Request" node do appear in execution data.
  • Restrict IP access to the n8n instance if possible. The editor should not be publicly accessible.
  • Use environment variables for configuration secrets (DB passwords, N8N_ENCRYPTION_KEY). Never put them directly in docker-compose.yml — use an .env file excluded from version control.

To access environment variables inside workflows, n8n exposes the $env.VARIABLE_NAME function in expressions. This is useful for passing dynamic configuration without hardcoding it in nodes.

Taking it to production

The docker-compose from the previous section is a good starting point, but for real production there are several critical areas you need to cover.

HTTPS and reverse proxy

n8n should sit behind a reverse proxy that handles TLS. Without HTTPS, webhooks from many services (Stripe, GitHub, etc.) won't work, and your credentials would travel in plain text.

The most common solution with Docker is Traefik or Caddy. Caddy is particularly simple because it handles Let's Encrypt certificates automatically:

yaml
# Add to docker-compose.yml
  caddy:
    image: caddy:2-alpine
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
      - caddy_config:/config
bash
# Caddyfile
n8n.yourdomain.com {
    reverse_proxy n8n:5678
}

PostgreSQL backups

n8n stores workflows, encrypted credentials, execution history, and users in PostgreSQL. Regular backups are essential:

bash
# Manual database backup
docker exec n8n-postgres-1 pg_dump -U n8n n8n | gzip > n8n_backup_$(date +%Y%m%d_%H%M%S).sql.gz

# Restore from backup
gunzip -c n8n_backup_20260615_120000.sql.gz | docker exec -i n8n-postgres-1 psql -U n8n n8n

Queue mode: scaling to multiple workers

By default n8n executes flows in the same process as the editor (regular mode). For high workloads, queue mode separates the main process (editor + API) from workers that execute flows, using Redis as a message queue.

In queue mode you can scale horizontally by adding more workers without touching the main process. Activation requires adding Redis to the stack and setting EXECUTIONS_MODE=queue, plus an additional worker service with n8n worker as the command. The complete setup guide is in the PDF.

The complete guide

This post covers the foundation for getting started with self-hosted n8n. The PDF guide goes deeper into everything you need for a robust deployment and the workflows that actually save time:

  • Hardened docker-compose: complete security configuration, resource limits, healthchecks, restart policies, and documented environment variables.
  • Queue mode with Redis step by step: architecture, worker configuration, horizontal scaling, and queue monitoring.
  • 10 reusable workflows: Stripe-to-Slack notifications, CRM contact sync, automatic invoice generation, uptime alerts, form processing with validation, and more — ready to import.
  • Automated backups: cron script for nightly PostgreSQL dumps with rotation and upload to S3/R2.
  • Troubleshooting: the 10 most common problems (credentials broken after key change, webhooks not arriving, stuck executions, memory issues) with step-by-step solutions.
Share:

Related articles