Aller au contenu
VPS et serveurs · 7 min

n8n Self-Hosted on a VPS: Docker Compose and HTTPS via Caddy

To self-host n8n, run it on a VPS with Docker Compose next to PostgreSQL and put Caddy in front: Caddy gets a Let's Encrypt certificate by itself, so webhooks and the editor work over HTTPS from the first start. The whole setup takes about 15 minutes on Ubuntu 24.04 or Debian 12.

What you need

  • A VPS. Pulse (2 vCPU, 2 GB RAM, 15 GB NVMe, $10/month) is the comfortable minimum for n8n with PostgreSQL. If you plan heavy workflows that process big JSON or files, take Drive.
  • A domain or subdomain (for example n8n.example.com) with an A record pointing to the server's IP. Caddy needs it to issue the certificate.
  • Root access over SSH (see Connecting over SSH) and Docker installed (see Docker on a VPS).

1. Firewall and Docker

apt update && apt install -y ufw
ufw allow 22/tcp && ufw allow 80/tcp && ufw allow 443/tcp && ufw allow 443/udp
ufw enable
curl -fsSL https://get.docker.com | sh

Only Caddy publishes ports. n8n and PostgreSQL stay inside the Docker network, because Docker's published ports bypass ufw.

2. Secrets

mkdir -p /opt/n8n && cd /opt/n8n
cat > .env <<EOF
DOMAIN=n8n.example.com
DB_PASSWORD=$(openssl rand -hex 16)
N8N_ENCRYPTION_KEY=$(openssl rand -hex 32)
EOF
chmod 600 .env

n8n encrypts stored credentials with N8N_ENCRYPTION_KEY. Lose it and every saved credential becomes unreadable, so back up .env together with the database.

3. docker-compose.yml

/opt/n8n/docker-compose.yml:

services:
  db:
    image: postgres:16
    restart: unless-stopped
    environment:
      POSTGRES_USER: n8n
      POSTGRES_PASSWORD: ${DB_PASSWORD}
      POSTGRES_DB: n8n
    volumes:
      - db:/var/lib/postgresql/data

  n8n:
    image: docker.n8n.io/n8nio/n8n:latest
    restart: unless-stopped
    depends_on: [db]
    environment:
      DB_TYPE: postgresdb
      DB_POSTGRESDB_HOST: db
      DB_POSTGRESDB_DATABASE: n8n
      DB_POSTGRESDB_USER: n8n
      DB_POSTGRESDB_PASSWORD: ${DB_PASSWORD}
      N8N_ENCRYPTION_KEY: ${N8N_ENCRYPTION_KEY}
      N8N_HOST: ${DOMAIN}
      N8N_PROTOCOL: https
      WEBHOOK_URL: https://${DOMAIN}/
      N8N_PROXY_HOPS: 1
      GENERIC_TIMEZONE: Europe/Berlin
      EXECUTIONS_DATA_PRUNE: "true"
      EXECUTIONS_DATA_MAX_AGE: 168
    volumes:
      - n8n:/home/node/.n8n

  caddy:
    image: caddy:2
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data

volumes:
  db:
  n8n:
  caddy_data:

Set GENERIC_TIMEZONE to your own zone: Cron and Schedule nodes use it.

4. Caddyfile

/opt/n8n/Caddyfile (replace the domain):

n8n.example.com {
    reverse_proxy n8n:5678
}

Caddy proxies WebSockets out of the box, so the editor's live updates work without extra settings.

5. Start

cd /opt/n8n
docker compose up -d
docker compose logs -f caddy n8n

Open https://n8n.example.com and create the owner account right away. Until you do, anyone who opens the page first can claim the instance.

Common mistakes

  • Webhook URLs show localhost:5678. WEBHOOK_URL is missing or wrong. Fix it in the compose file and run docker compose up -d.
  • No certificate. The A record doesn't point to the server yet, or ports 80/443 are closed. docker compose logs caddy shows the exact reason.
  • The disk fills up. Execution history lives in PostgreSQL. With 15 GB, keep pruning on (as above) and don't store binary files in executions.
  • Updates. docker compose pull && docker compose up -d. Read the n8n release notes before major versions.
  • Backups are on you. docker compose exec -T db pg_dump -U n8n n8n > n8n.sql plus the .env file, copied off the server on a schedule.

Want files and calendars on the same kind of setup? See Nextcloud on a VPS.

Order Pulse

n8n Self-Hosted on a VPS: Docker Compose and HTTPS via Caddy · dedisrv