Self-host n8n on a VPS with HTTPS

Run n8n in Docker on your own server, behind Caddy with a free HTTPS certificate, with its data in a volume you can back up and an easy way to update.

To self-host n8n, run its official Docker image with Docker Compose, keep its port on localhost, and put Caddy in front to serve it over HTTPS on your own domain. It takes about fifteen minutes on a fresh Ubuntu 24.04 or Debian 12 server with 2 GB of memory or more.

HTTPS isn't optional here. n8n stores the passwords and API keys your workflows use, and webhooks from other services expect an https:// address.

What you need

  • A server running Ubuntu 24.04 or Debian 12 or 13. Commands below are run as root.
  • A domain or subdomain you can point at it, like n8n.example.com.
  • Docker with the Compose plugin. If it isn't installed, Install Docker takes five minutes.

Point the domain at the server

At your DNS provider, add an A record for n8n.example.com with your server's IPv4 address, for example 203.0.113.10. Add an AAAA record for its IPv6 address too, once IPv6 works on the server. Caddy needs DNS in place to get a certificate, so do this first.

Check it from the server:

bash
getent hosts n8n.example.com

Open the web ports

Caddy needs ports 80 and 443. With ufw:

bash
ufw allow 80/tcp
ufw allow 443/tcp

n8n's own port, 5678, stays closed. Docker can open ports behind ufw's back (more on that in Install Docker), which is why the Compose file below binds it to 127.0.0.1 only.

Run n8n with Docker Compose

Make a directory for it:

bash
mkdir -p /opt/n8n
cd /opt/n8n

Create /opt/n8n/compose.yaml, with your own domain and time zone:

yaml
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_PORT=5678
      - N8N_PROTOCOL=https
      - N8N_PROXY_HOPS=1
      - NODE_ENV=production
      - N8N_WEBHOOK_URL=https://n8n.example.com/
      - WEBHOOK_URL=https://n8n.example.com/
      - GENERIC_TIMEZONE=Europe/London
      - TZ=Europe/London
    volumes:
      - n8n_data:/home/node/.n8n

volumes:
  n8n_data:

What the settings do:

  • N8N_HOST and N8N_PROTOCOL tell n8n its public address, so the links it shows you are right.
  • N8N_WEBHOOK_URL is the address it gives other services for webhooks. Without it, they get http://localhost:5678/..., which nobody outside can reach. Versions of n8n before 2.35 only read the older name, WEBHOOK_URL, so we set both; newer ones accept it too but log a deprecation warning, which you can ignore or silence by dropping that line.
  • N8N_PROXY_HOPS=1 says one proxy, Caddy, sits in front, so n8n trusts the visitor address Caddy passes on.
  • GENERIC_TIMEZONE is the time zone for schedule triggers. Use a name from timedatectl list-timezones.
  • The n8n_data volume holds everything: the database, settings, and the encryption key for your saved credentials.

Start it:

bash
docker compose up -d
docker compose logs -f

Wait for a line saying the editor is accessible, then press Ctrl+C. curl -I http://127.0.0.1:5678 should get an answer.

Put Caddy in front for HTTPS

Caddy gets and renews a Let's Encrypt certificate on its own. Install it from Caddy's repository:

bash
apt install -y debian-keyring debian-archive-keyring apt-transport-https curl gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' > /etc/apt/sources.list.d/caddy-stable.list
apt update
apt install -y caddy

Replace everything in /etc/caddy/Caddyfile with:

text
n8n.example.com {
    reverse_proxy 127.0.0.1:5678
}

Then:

bash
systemctl reload caddy
journalctl -u caddy -n 30

The log should show a certificate being obtained. Caddy passes websockets through without extra settings, which the n8n editor needs.

If you already run nginx on this server, use it instead of Caddy: proxy to 127.0.0.1:5678, and add the Upgrade and Connection headers for websockets. Serve a site with nginx and HTTPS covers the certificate.

Create the owner account straight away

Open https://n8n.example.com. The first person to load the page sets up the owner account, so do it now, with a strong password. Then turn on two-factor login under your user's Settings.

Back it up

Everything that matters is in the n8n_data volume. Compose names it after the directory, so it's n8n_n8n_data. To take a copy:

bash
cd /opt/n8n
docker compose stop
docker run --rm -v n8n_n8n_data:/data -v /root:/backup alpine tar czf /backup/n8n-$(date +%F).tgz -C /data .
docker compose start

The encryption key is in that archive (in the config file). Without it, a restored n8n can't decrypt your saved credentials, so never back up the database without it. It's also worth exporting your workflows to a file from time to time, one you can keep in Git:

bash
docker compose exec n8n n8n export:workflow --all --output=/home/node/.n8n/workflows.json

Copy the archives off the server. Backups you should run shows how to do it nightly.

Update

n8n releases often. Back up first, then:

bash
cd /opt/n8n
docker compose pull
docker compose up -d
docker image prune -f

Read the release notes before a major version jump. To choose when you upgrade, pin an exact version on the image, like docker.n8n.io/n8nio/n8n:2.44.3, instead of the default latest (n8n doesn't publish a tag for a whole major version), and change it when you're ready.

When it goes wrong

  • The browser shows a certificate warning or Caddy can't get one: DNS isn't pointing at the server yet, or port 80 is closed. Check getent hosts n8n.example.com and ufw status.
  • Webhooks show localhost URLs: N8N_WEBHOOK_URL is missing or the container wasn't recreated. Run docker compose up -d after any change to compose.yaml.
  • "Connection lost" in the editor: websockets aren't getting through. Caddy handles them; with nginx, check the Upgrade headers.
  • Credentials can't be decrypted after a restore: the config file with the encryption key wasn't restored with the rest.

Doing it on VPSNine

The n8n one-click app installs Docker and runs n8n with its data in a named volume on the first boot. You still add your domain and HTTPS, as above, before storing credentials in it.

n8n needs at least the Standard plan, with 4 GB of memory, so it has room alongside Docker and your workflows' data. The plans page has the specs. If you want to run n8n next to other apps with a web UI to manage them, Coolify can deploy it for you.

Be first when orders open

Join the waitlist and we'll send you one email when VPSNine launches. Pick a plan if you already know which one you want, and we'll size the first servers around it.

  • One email, on launch day
  • Monthly, or a year up front in Bitcoin
  • 14-day money-back guarantee