How to Self-Host n8n on a Dedicated Server for AI Workflows

Learn how to self-host n8n on a dedicated server for AI workflow automation. Get the complete Docker, PostgreSQL, and secure task runner setup guide.

n8n is an open-source workflow automation platform that connects apps, APIs, databases, and AI models through a visual, node-based editor. Unlike closed automation tools that charge per execution or cap your workflow count, self-hosting n8n on your own dedicated server gives you unlimited executions, full data control, and direct access to the underlying infrastructure your AI workflows need—persistent storage, GPU access for local models, and no third-party rate limits.

This guide walks through a complete, production-ready installation: Docker-based setup, PostgreSQL as the database, task runners running in isolated external mode (n8n's recommended production configuration since 2.0), a reverse proxy with a real SSL certificate, host firewall rules, the environment variables that actually matter, and the extra steps required if you plan to run AI-driven workflows (local LLMs, vector stores, LangChain-style agent nodes) rather than just simple app-to-app automations.

Tutorial Contents

Prerequisites

System Requirements

  • A dedicated or virtual server running Ubuntu 22.04 or 24.04 LTS (or another modern Linux distro).
  • Light workload: 2 vCPUs and 4 GB RAM.
  • AI Workflows: 4+ vCPUs and 8 GB+ RAM (especially for local embedding, external task runners, or high concurrent webhooks).

Network & Access Requirements

  • A registered domain name pointing an A record to your server's IP.
  • Root or sudo access via SSH, with key-based authentication (not password login).
  • Ports 80 and 443 open on your firewall for HTTP/HTTPS. No other port needs to be public.

1
Lock down the host firewall first

Before installing anything, set a default-deny firewall so nothing you spin up later is accidentally reachable from the internet:

BASH
sudo apt update
sudo apt install -y ufw

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose

This is a deliberate addition beyond just binding container ports to 127.0.0.1. Compose files get edited over time, and a firewall gives you a second layer of protection if a future change accidentally publishes a port (n8n on 5678, Ollama on 11434, the task broker on 5679) to 0.0.0.0.

2
Install Docker on the server

n8n's documentation recommends Docker for most self-hosting scenarios because it isolates n8n's runtime and dependencies from the host OS, and it makes upgrades and database management simpler.

BASH
sudo apt update
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.gpg

echo \
  "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 the install:

BASH
docker --version
docker compose version

Optionally, add your user to the docker group so you don't need sudo for every command:

BASH
sudo usermod -aG docker $USER
newgrp docker
Security Note
Membership in the docker group is effectively root-equivalent on this host — anyone in that group can mount the host filesystem into a container and read/write anything root can. Only add trusted admin accounts to it, and prefer sudo docker ... on shared or less-trusted boxes.

3
Quick test run (optional but recommended)

Before building the full production stack, confirm n8n runs correctly with a minimal container:

BASH
docker volume create n8n_data

docker run -it --rm \
  --name n8n \
  -p 127.0.0.1:5678:5678 \
  -e GENERIC_TIMEZONE="America/Detroit" \
  -e TZ="America/Detroit" \
  -e N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true \
  -v n8n_data:/home/node/.n8n \
  docker.n8n.io/n8nio/n8n

Replace America/Detroit with your own IANA timezone. TZ sets the container's system timezone (what commands like date return), while GENERIC_TIMEZONE controls the timezone n8n uses for schedule-based nodes.

To actually view the setup screen from your local machine, open an SSH tunnel: ssh -L 5678:localhost:5678 user@your-server-ip, then visit http://localhost:5678.

Stop the container with Ctrl+C — it self-removes because of --rm — and move on to the production setup below.

Production Stack & Reverse Proxy

4
Production setup with Docker Compose, PostgreSQL, and external task runners

For anything beyond testing, n8n should run behind Docker Compose with PostgreSQL instead of the default SQLite database. This handles concurrent writes perfectly for chained AI workflows. We will also run task runners in external mode for proper sandbox isolation.

Create a project directory and the environment file:

BASH
mkdir -p ~/n8n-stack && cd ~/n8n-stack
nano .env

Populate .env:

ENV
# Domain and protocol
DOMAIN_NAME=yourdomain.com
SUBDOMAIN=n8n
GENERIC_TIMEZONE=America/Detroit

# Postgres credentials — change these
POSTGRES_USER=n8n
POSTGRES_PASSWORD=change_this_to_a_strong_password
POSTGRES_DB=n8n

# n8n encryption key — generate with: openssl rand -hex 24
N8N_ENCRYPTION_KEY=replace_with_generated_key

# Task runner shared secret — generate with: openssl rand -hex 32
N8N_RUNNERS_AUTH_TOKEN=replace_with_generated_token

Generate the two secrets rather than leaving placeholders — the encryption key protects every stored credential, and the runner token authenticates the task-runner sidecar to n8n's task broker:

BASH
openssl rand -hex 24   # N8N_ENCRYPTION_KEY
openssl rand -hex 32   # N8N_RUNNERS_AUTH_TOKEN

Now create docker-compose.yml:

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

  n8n:
    image: docker.n8n.io/n8nio/n8n:2.34.3
    restart: unless-stopped
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      - N8N_HOST=${SUBDOMAIN}.${DOMAIN_NAME}
      - N8N_PORT=5678
      - N8N_PROTOCOL=https
      - NODE_ENV=production
      - WEBHOOK_URL=https://${SUBDOMAIN}.${DOMAIN_NAME}/
      - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
      - TZ=${GENERIC_TIMEZONE}
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=postgres
      - DB_POSTGRESDB_PORT=5432
      - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
      - DB_POSTGRESDB_USER=${POSTGRES_USER}
      - DB_POSTGRESDB_PASSWORD=${POSTGRES_PASSWORD}
      # Task runners — external mode (recommended for production)
      - N8N_RUNNERS_MODE=external
      - N8N_RUNNERS_BROKER_LISTEN_ADDRESS=0.0.0.0
      - N8N_RUNNERS_AUTH_TOKEN=${N8N_RUNNERS_AUTH_TOKEN}
      # Hardening
      - N8N_BLOCK_ENV_ACCESS_IN_NODE=true
      - N8N_RESTRICT_FILE_ACCESS_TO=/home/node/.n8n-files
    volumes:
      - n8n_data:/home/node/.n8n
      - n8n_files:/home/node/.n8n-files
    depends_on:
      postgres:
        condition: service_healthy

  task-runner:
    image: docker.n8n.io/n8nio/runners:2.34.3
    restart: unless-stopped
    environment:
      - N8N_RUNNERS_TASK_BROKER_URI=http://n8n:5679
      - N8N_RUNNERS_AUTH_TOKEN=${N8N_RUNNERS_AUTH_TOKEN}
    depends_on:
      - n8n

volumes:
  postgres_data:
  n8n_data:
  n8n_files:

Bring the stack up:

BASH
docker compose up -d
docker compose ps

All three containers should show as running/healthy within about a minute. Check docker compose logs n8n and docker compose logs task-runner to confirm the runner connected to the broker successfully.

5
Reverse proxy and SSL with Nginx and Let's Encrypt

With n8n bound only to localhost, install Nginx and Certbot on the host to terminate HTTPS and forward traffic to the container:

BASH
sudo apt install -y nginx certbot python3-certbot-nginx

Create an Nginx server block:

BASH
sudo nano /etc/nginx/sites-available/n8n
NGINX
server {
    listen 80;
    server_name n8n.yourdomain.com;

    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_cache_bypass $http_upgrade;
        client_max_body_size 50M;

        # Basic hardening headers
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    }
}

The Upgrade/Connection headers are required because n8n's editor UI uses WebSocket connections for real-time execution updates. The raised client_max_body_size matters for AI workflows passing larger payloads (like document text) going into an embedding or summarization node.

Enable the site and get a certificate:

BASH
sudo ln -s /etc/nginx/sites-available/n8n /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

sudo certbot --nginx -d n8n.yourdomain.com

Visit https://n8n.yourdomain.com and you should land on n8n's setup screen to create the owner account. Enable two-factor authentication for this account immediately from the instance's Security settings.

Advanced Configuration for AI Tools

6
Environment variables worth knowing for AI workflows

Beyond the basics above, several environment variables matter specifically when n8n is running AI-oriented automations:

Variable Purpose
N8N_PAYLOAD_SIZE_MAX Raises the max request body size n8n itself will accept, in MB — relevant if workflows pass large documents or images to AI nodes.
EXECUTIONS_TIMEOUT Default execution timeout in seconds. Long-running AI chains (multi-step agent reasoning, large batch embedding jobs) can exceed the default and need this raised.
N8N_RUNNERS_MODE Already set to external above — isolates code execution from the main n8n process.
EXECUTIONS_DATA_PRUNE
EXECUTIONS_DATA_MAX_AGE
Controls automatic cleanup of execution history. AI workflows that log large model outputs on every run can bloat the database quickly without this.
N8N_METRICS Enables a Prometheus-compatible metrics endpoint, useful for monitoring resource usage if you're running local model inference alongside n8n.
N8N_BLOCK_ENV_ACCESS_IN_NODE Already set above — blocks Code node access to process.env.
N8N_RESTRICT_FILE_ACCESS_TO Already set above — confines file-system nodes to one directory.
NODES_EXCLUDE The ExecuteCommand and LocalFileTrigger nodes are disabled by default as of 2.0. Only re-enable specific nodes here if you have a real need.

7
Connecting n8n to AI tools

For a fully self-hosted AI stack (no external API calls), the common pattern is to run an inference server such as Ollama alongside n8n. You can simply add it to your docker-compose.yml:

YAML
  ollama:
    image: ollama/ollama
    restart: unless-stopped
    ports:
      - "127.0.0.1:11434:11434"
    volumes:
      - ollama_data:/root/.ollama
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities: [gpu]

Add ollama_data under volumes, and n8n's dedicated Ollama nodes can then reach it at http://ollama:11434 over the internal Docker network. GPU passthrough requires the NVIDIA Container Toolkit installed on the host.

8
Updating n8n

Because the stack uses Docker Compose with pinned versions, updates are deliberate. Edit docker-compose.yml to bump both the n8n and task-runner image tags to the same new version, then run:

BASH
cd ~/n8n-stack
docker compose pull
docker compose up -d

9
Backups

Two things need backing up: the PostgreSQL database and the n8n_data volume:

BASH
# Database dump
docker compose exec postgres pg_dump -U n8n n8n > n8n-backup-$(date +%F).sql

# Volume backup
docker run --rm -v n8n-stack_n8n_data:/data -v $(pwd):/backup alpine \
  tar czf /backup/n8n-data-$(date +%F).tar.gz -C /data .

Checklists & Troubleshooting

Security checklist before going live

  • Host firewall (ufw) default-denies incoming traffic; only SSH, 80, and 443 are open.
  • Every container port (n8n, postgres, task-runner, ollama) is bound to 127.0.0.1 or not published at all — never 0.0.0.0.
  • Task runners run in external mode with a unique, generated N8N_RUNNERS_AUTH_TOKEN.
  • N8N_BLOCK_ENV_ACCESS_IN_NODE=true and N8N_RESTRICT_FILE_ACCESS_TO are set.
  • N8N_ENCRYPTION_KEY is a strong, generated value, backed up separately from the server.
  • ExecuteCommand and LocalFileTrigger nodes stay disabled.
  • The owner account has 2FA enabled; teammates use scoped project roles.
  • SSH access uses key-based authentication only.
  • EXECUTIONS_DATA_PRUNE=true is active to prevent database bloat from AI model outputs.

Troubleshooting common issues

  • n8n container won't start: Check docker compose logs n8n. This is most often a database connection failure — confirm postgres is healthy first.
  • Task runner won't connect / Code nodes fail: Check docker compose logs task-runner. Confirm N8N_RUNNERS_AUTH_TOKEN matches exactly between n8n and the runner, and both images are on the identical version.
  • Webhooks return localhost: WEBHOOK_URL isn't set, or doesn't match N8N_HOST/N8N_PROTOCOL. Update the env block and restart.
  • Execution status never updates: Nginx is missing the WebSocket upgrade headers shown in Step 5.
  • SSL certificate renewal fails: Run sudo certbot renew --dry-run to test manually and check for DNS/firewall issues blocking port 80.

Summary & Build Your Automation Server

A production n8n install on a dedicated server comes down to six real components: Docker running the n8n, PostgreSQL, and task-runner containers, a host firewall restricting everything to SSH/80/443, a reverse proxy handling HTTPS, correctly set environment variables, a backup routine, and — for AI-specific workflows — a local inference service like Ollama sitting on the same Docker network so model calls never leave the server.

This setup scales from a single-user automation box to a multi-workflow AI backend simply by adjusting server resources, since the underlying architecture doesn't change.

Are you looking for the perfect bare-metal foundation for your heavy AI workloads? Explore our powerful, unmetered servers today.

Explore Dedicated Servers →

Discover Leo Servers High-Performance Locations

Leo Servers operates premium bare-metal environments worldwide, offering diverse hosting options. Check out our specialized offerings to choose the setup that best suits your intensive workload needs.