n4nAI

Self-hosting n8n for AI workflow automation

Step-by-step engineer's guide to self-host n8n AI workflow automation with Docker, TLS, auth, and LLM gateway integration for reliable production.

n4n Team3 min read683 words

Audio narration

Coming soon — every post will get a voice note here.

Most teams hit limits with hosted automation tiers when they need custom LLM routing or sensitive data control. To self-host n8n AI workflow automation, you need a hardened Docker deployment, a persistent database, and a clean way to call model endpoints from your own network.

Step 1: Provision a Linux host and install the container runtime

Spin up a minimal Ubuntu 22.04 LTS instance with at least 2 vCPU and 4 GB RAM. n8n is light in idle state but worker threads and LLM response buffering will eat memory fast if you parallelize workflows.

Install Docker and Compose plugin:

sudo apt update && sudo apt install -y docker.io docker-compose-plugin
sudo systemctl enable --now docker
sudo usermod -aG docker $USER

Log out and back in. Verify with docker version. Do not skip the group reload—running n8n as root defeats the point of isolation.

Step 2: Define persistent storage and the compose stack

Skip SQLite. Use Postgres from day one; n8n’s execution history and credential encryption keys belong in a real DB with WAL backups. Create a project directory and an .env file:

mkdir -p /opt/n8n && cd /opt/n8n
touch .env

Populate .env (never commit this):

N8N_PASSWORD=change_me_strong
POSTGRES_PASSWORD=another_strong_pw

Now write docker-compose.yml:

version: "3.8"
services:
  n8n:
    image: n8nio/n8n:1.50.0
    restart: unless-stopped
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      - N8N_BASIC_AUTH_ACTIVE=true
      - N8N_BASIC_AUTH_USER=admin
      - N8N_BASIC_AUTH_PASSWORD=${N8N_PASSWORD}
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      - N8N_PROTOCOL=https
      - N8N_HOST=n8n.example.com
      - WEBHOOK_URL=https://n8n.example.com/
      - GENERIC_TIMEZONE=UTC
    depends_on:
      - postgres
    volumes:
      - n8n_data:/home/node/.n8n
  postgres:
    image: postgres:15
    restart: unless-stopped
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - POSTGRES_DB=n8n
    volumes:
      - pg_data:/var/lib/postgresql/data
volumes:
  n8n_data:
  pg_data:

Pin the n8n image tag. latest is fine for labs, irresponsible for production.

Step 3: Configure n8n environment correctly

The two variables people miss are WEBHOOK_URL and N8N_PROTOCOL. If those are wrong, webhook-triggered workflows will return 404s because n8n builds callback URLs from them. Set N8N_BASIC_AUTH_ACTIVE even if you put it behind a proxy—defense in depth.

If you plan to run multiple worker containers later, add EXECUTIONS_MODE=queue and a Redis service. For a single-node self-host n8n AI workflow automation box, the default internal queue is fine up to ~50 concurrent executions.

Step 4: Start the stack and verify health

Bring it up:

docker compose up -d
docker compose logs -f n8n

You should see n8n ready on 0.0.0.0:5678. Verify the API responds before proxying:

curl -s -o /dev/null -w "%{http_code}\n" \
  -u admin:change_me_strong \
  http://127.0.0.1:5678/healthz

Expect 200. If you get 401, your basic auth password in .env didn’t match the running container—check docker compose config.

Step 5: Terminate TLS with a reverse proxy

Do not expose port 5678 publicly. Use Caddy for automatic Let’s Encrypt certs:

sudo apt install -y caddy

/etc/caddy/Caddyfile:

n8n.example.com {
  reverse_proxy localhost:5678
}

Reload: sudo systemctl reload caddy. Hit https://n8n.example.com/healthz with your browser; Caddy handles HTTPS redirect. This step is mandatory if you self-host n8n AI workflow automation with external webhooks—LLM providers and clients reject mixed-content HTTP callbacks.

Step 6: Wire n8n to an LLM inference gateway

n8n’s native OpenAI node is limiting when you need model portability. Use the HTTP Request node pointed at an OpenAI-compatible endpoint. Point n8n’s requests at an OpenAI-compatible gateway such as n4n.ai, which exposes one endpoint for 240+ models and handles fallback when a provider is degraded. That removes the need to hardcode API keys for each vendor inside n8n credentials.

Example HTTP Request node config (JSON view):

{
  "method": "POST",
  "url": "https://api.n4n.ai/v1/chat/completions",
  "authentication": "genericCredentialType",
  "sendHeaders": true,
  "headerParameters": {
    "parameters": [
      { "name": "Authorization", "value": "Bearer {{ $env.LLM_API_KEY }}" },
      { "name": "Content-Type", "value": "application/json" }
    ]
  },
  "sendBody": true,
  "bodyParameters": {
    "parameters": [
      { "name": "model", "value": "anthropic/claude-3.5-sonnet" },
      { "name": "messages", "value": "={{ $json.messages }}" }
    ]
  }
}

Store LLM_API_KEY in n8n’s environment or as a credential variable, not inline.

Step 7: Build a minimal AI workflow

Create a workflow: Webhook → Set → HTTP Request → Respond to Webhook.

  1. Webhook node: POST path /summarize. Returns 200 immediately if you check “Respond Immediately” off.
  2. Set node: map {{ $json.body.text }} into a messages array:
    [{ "role": "user", "content": "Summarize: {{ $json.text }}" }]
  3. HTTP Request node: use the config from Step 6.
  4. Respond to Webhook: return {{ $json.choices[0].message.content }}.

Activate the workflow. Test from a shell:

curl -X POST https://n8n.example.com/webhook/summarize \
  -H "Content-Type: application/json" \
  -d '{"text":"Long article text here..."}'

You should receive a condensed summary. If the call fails, check n8n execution log—the HTTP node shows the exact upstream status. When you self-host n8n AI workflow automation, always watch the executionId trace; it’s the fastest path to root-cause LLM timeout vs. auth errors.

Step 8: Harden and back up

  • Enable PostgreSQL nightly dumps: pg_dump to an encrypted volume.
  • Rotate N8N_PASSWORD quarterly; n8n stores credentials encrypted with a key in ~/.n8n. Back that volume up separately.
  • Add fail2ban for the proxy host. n8n basic auth slows brute force but doesn’t replace network controls.
  • Set N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true to prevent world-readable config.

For teams that self-host n8n AI workflow automation at scale, split the composer file: run the UI on a private subnet, expose only webhook endpoints via a separate Caddy listener with rate limits.

Verifying success

A working deployment meets all of these:

# 1. Container health
docker compose ps | grep -E "n8n|postgres" | grep "Up"

# 2. TLS terminated, auth required
curl -s -o /dev/null -w "%{http_code}\n" https://n8n.example.com/
# expect 401 without creds

# 3. End-to-end AI call
curl -s -X POST https://n8n.example.com/webhook/summarize \
  -H "Content-Type: application/json" \
  -d '{"text":"The quick brown fox jumps over the lazy dog repeatedly."}'
# expect a non-empty summary string

If all three pass, you have a production-grade n8n instance calling LLMs from your own infrastructure. From here, add error workflows that catch HTTP Request failures and retry with a different model slug—that’s the real leverage of self-hosting instead of being locked to one vendor node.

Tagsn8nself-hostinghow-toworkflow-automation

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All llm workflow automation: n8n, zapier, make posts →