Initech
KB-21 Print

Self-Host n8n on a VPS

Quick answer

n8n runs well in Docker on a small Linux VPS. The short version: install Docker, create a compose file with a persistent volume, put Caddy in front of it for automatic HTTPS, and open your domain. The sections below walk through each step on Ubuntu, and the same commands work on Debian with one URL change.

What you need

  • A Linux VPS running Ubuntu or Debian. For personal automation a small instance with 1 to 2 GB of RAM is comfortable; sizing notes are at the end.
  • A domain or subdomain, for example n8n.example.com, with an A record pointing at your server IP. Webhook nodes hand out URLs based on this domain, so do not skip it.
  • SSH access as a user with sudo.

Step 1: Install Docker

Install Docker Engine and the compose plugin from Docker's official repository:

sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
echo "deb [signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin

On Debian, replace "ubuntu" with "debian" in the two download URLs.

Step 2: Create the compose file

Make a working directory and a compose file:

mkdir -p ~/n8n && cd ~/n8n
nano docker-compose.yml

Paste the following, replacing n8n.example.com with your domain:

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      - N8N_HOST=n8n.example.com
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.example.com/
      - GENERIC_TIMEZONE=UTC
    volumes:
      - n8n_data:/home/node/.n8n

volumes:
  n8n_data:

Two details in that file do the heavy lifting. The named volume n8n_data holds your workflows, credentials and the encryption key, so everything survives updates and container rebuilds. And the port mapping binds to 127.0.0.1, which keeps n8n unreachable from the internet except through the reverse proxy you are about to add.

Start it:

docker compose up -d

Step 3: HTTPS with Caddy

Caddy is the least-effort reverse proxy for a single app because it obtains and renews the Let's Encrypt certificate by itself. On recent Ubuntu and Debian releases it is in the standard repositories:

sudo apt install -y caddy

If your release does not package it, use Caddy's official repository instead. Then edit /etc/caddy/Caddyfile so it contains only:

n8n.example.com {
    reverse_proxy 127.0.0.1:5678
}

Reload Caddy and you are live:

sudo systemctl reload caddy

Prefer nginx? The equivalent is a server block proxying to 127.0.0.1:5678 plus a certificate from certbot. It works exactly as well; it is simply more moving parts to maintain.

Step 4: First login and users

Open https://n8n.example.com in a browser. Current n8n versions ship user management out of the box: the first visit asks you to create the owner account, and additional users can be invited from the settings screen afterwards. Older tutorials mention basic-auth environment variables; those predate built-in user management and are no longer the recommended path.

Environment variables that matter

  • N8N_HOST and WEBHOOK_URL: must match the public domain. If they do not, webhook and OAuth callback URLs point at the wrong place and external services cannot reach your workflows.
  • N8N_PROTOCOL: set to https when running behind a TLS proxy so generated links use the right scheme.
  • GENERIC_TIMEZONE: the timezone schedule triggers fire in. Set it to your own zone if cron-style workflows should follow local time.

Updating safely

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

Because the data lives in the n8n_data volume rather than in the container, pulling a new image and recreating the container keeps every workflow and credential. It is still sensible to export important workflows, or snapshot the server, before a major version jump.

Common gotchas

  • Webhooks return 404 or show the wrong URL: WEBHOOK_URL does not match the domain in front of the proxy. Fix the variable and recreate the container.
  • Port 5678 is unreachable from outside: intentional, and correct. All traffic should flow through the proxy on 443.
  • Certificate never issues: Caddy cannot obtain a certificate until the DNS A record actually resolves to the server, so check propagation first.
  • Deleted the volume, lost credentials: the encryption key inside the volume protects stored credentials. Without it they are unrecoverable, which is a good reason to keep independent backups.

How big a server does n8n need?

For personal automation and modest schedules, 1 to 2 GB of RAM is plenty. Memory use grows with concurrent executions and with nodes that pull large payloads into memory, so if you process big files or run many workflows on tight timers, move up a tier and watch real usage rather than guessing. CPU is rarely the first bottleneck for typical automation workloads.

Backing up your n8n instance

Everything that matters lives in the n8n_data volume, which makes backups short work. Stop the stack briefly and archive the volume:

cd ~/n8n && docker compose stop
docker run --rm -v n8n_data:/data -v $(pwd):/backup alpine tar czf /backup/n8n-backup.tar.gz -C /data .
docker compose start

Copy the archive off the server on a schedule, because a backup that lives next to the thing it protects disappears with it. Workflows can also be exported individually from the editor as JSON, which is handy for version control, but the volume archive is the one that also carries credentials and the encryption key, and it is what you would restore onto a fresh server.

Was this answer helpful?
Related Articles