DEV Community

Cover image for Self-Hosted n8n on a Dedicated Server: The Production Docker Compose Guide
Felicia Grace for BytesRack

Posted on • Originally published at bytesrack.com

Self-Hosted n8n on a Dedicated Server: The Production Docker Compose Guide

A self-hosted n8n dedicated server setup runs n8n's workflow automation engine on hardware you fully control, paired with PostgreSQL for data storage, Redis for queue-based execution, and NGINX for encrypted traffic. It replaces per-task billing with a fixed monthly server cost and gives you unlimited workflow executions on the Community Edition.

This guide walks through the exact commands and configuration files needed to deploy n8n docker compose in a production-ready, scalable state.

Introduction: The Real Cost of Cloud Automation

If you've priced out Zapier or Make for a business running more than a handful of daily automations, you already know the task-based billing model gets expensive fast. Zapier's Professional plan starts around $29.99/month for 750 tasks, and its Team plan runs about $103.50/month for 2,000 pooled tasks. Overages are billed at roughly 1.25x your base task rate, so a busy month can push the bill well past the sticker price.

A self-hosted n8n instance on a dedicated server flips that math. You pay a flat monthly rate for the server, and every workflow execution is included—n8n's Community Edition is free and open-source, with no execution caps.

Feature Zapier Professional Zapier Team Self-Hosted n8n
Monthly cost ~$29.99 ~$103.50 ~$20–$50 (flat server cost)
Task limit 750 tasks 2,000 tasks Unlimited executions
Overage billing ~1.25x base rate ~1.25x base rate None — fixed cost
Data ownership Zapier's cloud Zapier's cloud Your infrastructure
Scaling model Buy a higher tier Buy a higher tier Add worker containers

The trade-off is obvious: self-hosting shifts the responsibility for uptime, security, and backups onto you. That's exactly what the rest of this guide is designed to handle properly.


Architecture Overview: Moving Past SQLite

What is n8n queue mode? It's an execution architecture where a main n8n process handles the UI, API, and webhook triggers, while separate worker containers pick up and run the actual workflow executions from a Redis-backed queue. This decouples "receiving a trigger" from "doing the work," which makes horizontal scaling possible.

Why the Default SQLite Setup Fails in Production

  • Concurrency issues: SQLite locks the database file during writes, so simultaneous webhook executions can queue up or fail outright.
  • No queue mode support: n8n's own documentation states that running a distributed setup with SQLite isn't supported—queue mode requires PostgreSQL.
  • Scaling limits: A single SQLite file can't be shared reliably across multiple worker containers.

The Production Stack

  • n8n (main process): Handles the editor UI, REST API, and incoming webhook requests.
  • PostgreSQL: Stores workflows, credentials, and execution data (n8n recommends Postgres 13+).
  • Redis: Acts as the message broker for queue mode.
  • n8n (worker processes): Dedicated containers that run workflow executions.
  • NGINX + Certbot: Terminates HTTPS traffic (mandatory for webhooks).

Prerequisites & Hardware Sizing

Before you start, you need:

  • A dedicated server running Ubuntu 22.04 LTS or 24.04 LTS with root or sudo access.
  • A registered domain name pointed at your server's IP address.
  • Basic terminal comfort.

Hardware Guidelines (NVMe storage recommended):

Use Case vCPU RAM Notes
Testing 1–2 2 GB Minimum only; unstable under load
Small Business 2–4 4 GB Reasonable starting point for this stack
Growing Team 4 8 GB Recommended for worker replicas
AI / High-volume 8+ 16 GB+ Needed for LLM chains or large payloads

Step 1: Preparing the Server and Installing Docker

Connect to your server via SSH, update the system, and install Docker.

# Update system packages
sudo apt update && sudo apt upgrade -y

# Install prerequisites
sudo apt install -y ca-certificates curl gnupg

# Add Docker's official GPG key
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL [https://download.docker.com/linux/ubuntu/gpg](https://download.docker.com/linux/ubuntu/gpg) | sudo gpg --deararm -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg

# Add the Docker repository
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] [https://download.docker.com/linux/ubuntu](https://download.docker.com/linux/ubuntu) \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# Install Docker Engine and Compose plugin
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

# Verify the installation
docker compose version

# Allow your user to run Docker without sudo (log out and back in after this)
sudo usermod -aG docker $USER
Enter fullscreen mode Exit fullscreen mode

Step 2: The Production Docker Compose File

Create a project directory and the configuration files.

mkdir -p ~/n8n-production && cd ~/n8n-production
Enter fullscreen mode Exit fullscreen mode

Create your .env file:

# ---- Domain & timezone ----
DOMAIN_NAME=n8n.yourdomain.com
GENERIC_TIMEZONE=UTC

# ---- PostgreSQL ----
POSTGRES_USER=n8n_admin
POSTGRES_PASSWORD=CHANGE_THIS_STRONG_PASSWORD
POSTGRES_DB=n8n
POSTGRES_NON_ROOT_USER=n8n_app
POSTGRES_NON_ROOT_PASSWORD=CHANGE_THIS_TOO

# ---- n8n security ----
# Generate with: openssl rand -hex 32
N8N_ENCRYPTION_KEY=REPLACE_WITH_GENERATED_64_CHAR_KEY

# ---- Execution data retention ----
EXECUTIONS_DATA_PRUNE=true
EXECUTIONS_DATA_MAX_AGE=336
Enter fullscreen mode Exit fullscreen mode

Create docker-compose.yml:

version: "3.8"

volumes:
  db_storage:
  n8n_storage:
  redis_storage:

x-shared-n8n: &shared-n8n
  image: docker.n8n.io/n8nio/n8n:latest
  restart: always
  environment:
    - DB_TYPE=postgresdb
    - DB_POSTGRESDB_HOST=postgres
    - DB_POSTGRESDB_PORT=5432
    - DB_POSTGRESDB_DATABASE=${POSTGRES_DB}
    - DB_POSTGRESDB_USER=${POSTGRES_NON_ROOT_USER}
    - DB_POSTGRESDB_PASSWORD=${POSTGRES_NON_ROOT_PASSWORD}
    - EXECUTIONS_MODE=queue
    - QUEUE_BULL_REDIS_HOST=redis
    - QUEUE_HEALTH_CHECK_ACTIVE=true
    - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
    - N8N_HOST=${DOMAIN_NAME}
    - N8N_PROTOCOL=https
    - N8N_PORT=5678
    - WEBHOOK_URL=https://${DOMAIN_NAME}/
    - GENERIC_TIMEZONE=${GENERIC_TIMEZONE}
    - TZ=${GENERIC_TIMEZONE}
    - EXECUTIONS_DATA_PRUNE=${EXECUTIONS_DATA_PRUNE}
    - EXECUTIONS_DATA_MAX_AGE=${EXECUTIONS_DATA_MAX_AGE}
  depends_on:
    postgres:
      condition: service_healthy
    redis:
      condition: service_healthy

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

  redis:
    image: redis:7-alpine
    restart: always
    volumes:
      - redis_storage:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 10

  n8n-main:
    <<: *shared-n8n
    container_name: n8n_main
    ports:
      - "127.0.0.1:5678:5678"
    volumes:
      - n8n_storage:/home/node/.n8n
    command: start

  n8n-worker:
    <<: *shared-n8n
    container_name: n8n_worker
    volumes:
      - n8n_storage:/home/node/.n8n
    command: worker
    deploy:
      replicas: 2
Enter fullscreen mode Exit fullscreen mode

Step 3: Securing with NGINX and SSL

Webhooks will silently fail without HTTPS. Install NGINX and Certbot on the host:

sudo apt install -y nginx certbot python3-certbot-nginx
Enter fullscreen mode Exit fullscreen mode

Create the NGINX server block:

sudo nano /etc/nginx/sites-available/n8n.yourdomain.com
Enter fullscreen mode Exit fullscreen mode

Paste the following config:

server {
    listen 80;
    server_name n8n.yourdomain.com;

    location / {
        proxy_pass [http://127.0.0.1:5678](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;
        proxy_send_timeout 300s;
    }
}
Enter fullscreen mode Exit fullscreen mode

Enable the site and generate the SSL certificate:

sudo ln -s /etc/nginx/sites-available/n8n.yourdomain.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d n8n.yourdomain.com
Enter fullscreen mode Exit fullscreen mode

Step 4: Booting Up

Bring the stack up:

docker compose up -d
docker compose ps
Enter fullscreen mode Exit fullscreen mode

Visit https://n8n.yourdomain.com to create your owner account and start building.

Step 5: Disaster Recovery and Backups

You need backups of two things: your PostgreSQL database and your .env file (which contains your irreplaceable N8N_ENCRYPTION_KEY).

Create a backup script:

sudo nano /opt/n8n-backup.sh
Enter fullscreen mode Exit fullscreen mode
#!/bin/bash
# n8n PostgreSQL + .env backup script

BACKUP_DIR="/opt/n8n-backups"
DATE=$(date +%F_%H%M)
PROJECT_DIR="/home/$(whoami)/n8n-production"

mkdir -p "$BACKUP_DIR"

# Dump the PostgreSQL database from inside the container
docker exec $(docker compose -f "$PROJECT_DIR/docker-compose.yml" ps -q postgres) \
  pg_dump -U n8n_admin -d n8n > "$BACKUP_DIR/n8n_db_$DATE.sql"

# Back up the .env file
cp "$PROJECT_DIR/.env" "$BACKUP_DIR/env_$DATE.bak"

# Remove backups older than 14 days
find "$BACKUP_DIR" -type f -mtime +14 -delete
Enter fullscreen mode Exit fullscreen mode

Make it executable and run it via cron daily at 3 AM:

sudo chmod +x /opt/n8n-backup.sh
crontab -e
Enter fullscreen mode Exit fullscreen mode

Add line:

0 3 * * * /opt/n8n-backup.sh
Enter fullscreen mode Exit fullscreen mode

Note: Always store a copy of your .env file off-server in a secure password manager.

Ready to deploy? Everything in this guide requires a server with predictable performance and zero noisy neighbors. Explore BytesRack Dedicated Servers to get a bare-metal machine sized perfectly for a production n8n stack with NVMe storage as standard.

Top comments (0)