FlowKit

Self-hosting n8n with a custom domain and HTTPS (Traefik or Caddy)

Published 22 July 2026 · 5 min read

Installing n8n self-hosted via Docker Compose takes half an hour. Making it reachable at https://n8n.yourdomain.com with a valid certificate, no security warning, and no broken webhook is the step that trips up newcomers most often — not because it's complicated, but because three pieces have to fit together exactly: the reverse proxy, DNS, and n8n's own environment variables. This guide covers the two most common options, Traefik and Caddy, and above all the n8n settings people consistently forget once the certificate is issued.

Why HTTPS isn't a cosmetic detail

On an n8n instance exposed to the internet, HTTPS protects two distinct things. First, your login credentials and the encrypted credentials that travel during every editing session. Second — and this is often overlooked — OAuth callbacks: Google, Notion, HubSpot, and Slack simply refuse to register a callback URL over http:// for a production application; without a valid certificate, part of your integrations will never be able to authenticate.

This is no longer a task reserved for technical teams. A landmark study on free-certificate adoption, Aertsen, Korczyński, Moura, Tajalizadehkhoob, and van den Berg (ANRW 2017), shows that Let's Encrypt democratized encryption by disproportionately covering the low-budget end of the hosting market — exactly the profile of a €10/month VPS running n8n. The certificate itself is no longer the problem; wiring it up correctly to the reverse proxy and the application remains a real source of errors: a more recent study surveying 96 experienced administrators, Mai, Schedler, Weippl, and Krombholz (HCII 2022), finds that more than half of analyzed sites remain misconfigured despite the availability of tools like Certbot — the difficulty has shifted from obtaining the certificate to correctly connecting it to the application it protects. That's exactly the step detailed below.

Traefik or Caddy: picking a reverse proxy

Both handle automatic Let's Encrypt certificate renewal with no manual intervention, but with a different philosophy:

  • Caddy aims for simplicity: a few lines in a Caddyfile are enough to expose n8n over HTTPS. Ideal if your server only hosts n8n, or one or two other services.
  • Traefik relies on automatic discovery via Docker labels: each container declares its own routing rules right in docker-compose.yml. The initial setup is more verbose, but it scales cleanly once several applications share the same server — useful if you're planning to add queue mode with Redis workers or other application services later on.

For a first single-service deployment, Caddy saves configuration time. For a server meant to grow, Traefik spares you from redoing everything later.

Setting up Traefik

A minimal docker-compose.yml puts n8n behind Traefik in three blocks:

services:
  traefik:
    image: "traefik:v3.1"
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.le.acme.httpchallenge=true"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
      - "--certificatesresolvers.le.acme.email=you@yourdomain.com"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - "./letsencrypt:/letsencrypt"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"

  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    environment:
      - N8N_HOST=n8n.yourdomain.com
      - N8N_PROTOCOL=https
      - N8N_PORT=5678
      - WEBHOOK_URL=https://n8n.yourdomain.com/
      - N8N_SECURE_COOKIE=true
    volumes:
      - n8n_data:/home/node/.n8n
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.n8n.rule=Host(`n8n.yourdomain.com`)"
      - "traefik.http.routers.n8n.entrypoints=websecure"
      - "traefik.http.routers.n8n.tls.certresolver=le"
      - "traefik.http.services.n8n.loadbalancer.server.port=5678"

volumes:
  n8n_data:

Traefik handles ACME validation (the HTTP-01 challenge on port 80) and then routes all incoming traffic to HTTPS. The detail most people miss: the labels alone aren't enough — the DNS for n8n.yourdomain.com also needs to point, via an A record, to the server's public IP before you run docker compose up -d, or the ACME challenge fails silently and Traefik keeps retrying without ever succeeding.

Setting up Caddy

Same result, in far fewer lines. A Caddyfile:

n8n.yourdomain.com {
    reverse_proxy n8n:5678
}

And the matching service in docker-compose.yml:

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

  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    environment:
      - N8N_HOST=n8n.yourdomain.com
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://n8n.yourdomain.com/
      - N8N_SECURE_COOKIE=true
    volumes:
      - n8n_data:/home/node/.n8n

volumes:
  caddy_data:
  n8n_data:

Caddy automatically detects that a valid domain name is declared and handles certificate issuance and renewal on its own — no ACME section to write by hand.

The n8n variables people consistently forget

The reverse proxy terminates HTTPS on the public side, but internally, n8n only sees plain HTTP traffic on port 5678. Without the right variables, the application keeps behaving as if it were still on HTTP:

  • N8N_PROTOCOL=https and N8N_HOST: tell n8n which public protocol and domain to use when generating its own URLs (the interface, OAuth callbacks).
  • WEBHOOK_URL: set this explicitly the moment a reverse proxy is involved, otherwise n8n may display or register incorrect webhook URLs with third-party services — our guide to securing n8n webhooks actually assumes this public URL is already correct before layering on authentication and signature validation.
  • N8N_SECURE_COOKIE=true: forces the session cookie to be sent only over HTTPS. Leave it at false only during initial plain-HTTP testing, never in production.

Verifying it actually works

Three checks are enough after the first startup:

  1. Open https://n8n.yourdomain.com in the browser: the padlock should appear with no warning, and the certificate authority should be Let's Encrypt.
  2. Create a test workflow with a Webhook node and check that the production URL n8n displays starts with https:// and the right domain, not a raw IP or leftover http://.
  3. Test an OAuth connection (Google, Notion…) end to end: this is the most reliable check, since an OAuth provider refuses to complete if there's the slightest mismatch between the declared domain and the one actually being served.

Common pitfalls

  • DNS not propagated yet: starting the proxy before the A record has propagated (sometimes a few minutes, sometimes hours) fails ACME validation. Check with dig n8n.yourdomain.com before starting the containers.
  • Let's Encrypt rate limits: several failed attempts in quick succession can trigger a temporary per-domain limit. Fix the root cause (DNS, firewall) before restarting rather than retrying in a loop.
  • Ports 80/443 filtered: a cloud security group (AWS, OVH, Scaleway) that only allows SSH by default silently blocks the HTTP-01 challenge. Check this first if the certificate never gets issued.
  • WEBHOOK_URL left stale after a migration: changing domain or server without updating this variable leaves active workflows pointing at the old URL — third-party integrations keep calling an address that no longer answers.

Going further

This HTTPS foundation is the technical prerequisite for any serious self-hosting setup, especially when data locality is the main argument — the case detailed in our self-hosted vs. cloud n8n comparison and in the logging workflows of the Compliance & Audit Pack (€149), built for an end-to-end controlled deployment. Once the domain and certificate are in place, also make sure to back up your workflows outside the container: our guide to backing up and versioning workflows with Git covers exactly that next step.

FAQ

Frequently asked questions

Traefik or Caddy: which one should I pick for n8n?

Caddy is the better fit if you're only running one or two services: a five-line Caddyfile is enough, and the certificate renews itself with no extra configuration. Traefik pulls ahead once you're exposing several applications behind the same server (n8n, a dashboard, an internal API) thanks to automatic discovery via Docker labels — the initial setup is more verbose, but each new service only adds a few lines to docker-compose.yml.

Why do my n8n webhooks return an http:// URL when the site is served over https://?

Because N8N_PROTOCOL or WEBHOOK_URL aren't set correctly on the n8n container. The reverse proxy does terminate HTTPS on the public side, but n8n only sees plain HTTP traffic internally, and keeps generating its URLs on that basis until WEBHOOK_URL explicitly tells it the full public https:// address.

Do I need a VPS with a static IP to use Let's Encrypt?

You need, at minimum, a domain (or subdomain) whose DNS A or AAAA record points to your server's public IP, reachable on ports 80 and 443 at validation time. A standard VPS works fine; the usual blocker isn't the IP itself but a firewall or cloud security group still filtering those two ports.

What happens if automatic certificate renewal fails?

Traefik and Caddy both retry renewal well before expiry (Let's Encrypt issues 90-day certificates and attempts renewal once 30 days remain). A silent failure almost always traces back to port 80 becoming unreachable in the meantime (a firewall change) or DNS that moved without updating the proxy — watch the proxy container's logs, not just n8n's.

Bundle FlowKit Complet

€269