Self Hosting n8n with Docker and Traefik/LetsEncrypt (for https)

R - Oct 13 - - Dev Community

Setting up n8n in Docker, without https, is relatively simple. That's covered here. However, some things (e.g. oauth2 connection to Google Drive) are difficult or impossible to do in n8n unless it is accessed using https (SSL). There are many ways to do that, including configuring n8n itself with a dedicated server certificate, but running Traefik as a reverse proxy, in front of n8n, provides automated integration with LetsEncrypt to obtain and renew a "trusted by default" server certificate, and just let n8n run without https.

Prerequisites

  • n8n running in Docker, preferably started using docker-compose
  • Access to start a Traefik container in Docker
  • A registered (top level) domain name with access to DNS configuration.

Goal

Set up Traefik, in Docker, such that it handles requests for a subdomain, like myn8n.example.com and forwards (reverse-proxies) that traffic to n8n, also running in Docker.

High Level Steps

  1. Register/activate API access to domain registrar
    • How to do this varies depending upon which registrar is used.
    • The list of supported registrars, and links to specific instructions can be found here
  2. Get Traefik running in a container
    • This is documented here.
    • Part of this example setup configures Traefik to automatically fetch a server certificate, for https, from LetsEncrypt
    • Note: The example docker compose shows env-variables for a DNS/registrar provider named ovh. This part will be different with each different registrar provider. The Traefik plugin specifics for each registrar (like which ENV vars are required) can be found along with the API-access instructions from the previous step.
      • Unless you are actually using ovh as your registrar, change this: - "--certificatesresolvers.myresolver.acme.dnschallenge.provider=ovh" from ovh to the one matching your actual registrar, e.g. ionos, godaddy, namecheap, etc. etc.
      • The environment variables will also be different. For instance, if you are using GoDaddy you would include environment variables GODADDY_API_KEY and GODADDY_API_SECRET instead of all the ones starting with OVH_
  3. Configure DNS so that a domain/subdomain name maps to the Traefik server (from the web browser's perspective)
    • If this cannot be done in the public DNS, it will still work with host entries in the /etc/hosts file on one machine, a hostname/DNS feature on a router, or an "internal-network" DNS like pihole.
    • Note that it is probably not a good idea to configure the public DNS at your registrar to resolve an internal-only (i.e. private/LAN) Traefik server IP address. Also, securing Traefik for public access is outside the scope of this article.
  4. Add configuration (labels) to the n8n container to tell Traefik to handle the https access.
    • The example whoami service shown in the Traefik docs is ok for checking to be sure the reverse proxy is working, but it doesn't make the importance of the "labels" section exactly clear.
    • In the n8n container, there would be corresponding labels like these:
  labels:
      - "traefik.enable=true"
      - "traefik.http.routers.n8n.rule=Host(`n8n.example.com`)"
      - "traefik.http.routers.n8n.entrypoints=websecure"
      - "traefik.http.routers.n8n.tls.certresolver=myresolver"
Enter fullscreen mode Exit fullscreen mode

Background Info, How Stuff Works, etc.

Supplemental Note on Docker Networks

When I set up n8n and Postgres in Docker, I put both of the containers in their own docker network for isolation from other stuff running in docker, so there was a network defined in n8n's docker-compose.yaml like:

  networks:
    n8nstack:
Enter fullscreen mode Exit fullscreen mode

And each container "joined" the network like:

    services:
      n8n:
        # ...
        networks:
          - "n8nstack"
      postgres:
        # ...
        networks:
          - "n8nstack"
Enter fullscreen mode Exit fullscreen mode

Traefik was running on the default docker network, not the n8nstack network, so the reverse-proxy wasn't working. To fix this, Traefik was similarly configured with its own (external) docker network, created by docker command like...

docker network create traefikproxy
Enter fullscreen mode Exit fullscreen mode

and, in Traefik's docker-compose.yaml...

  networks:
    traefikproxy:
      external: true
Enter fullscreen mode Exit fullscreen mode

with each container referencing the network like:

  services:
    traefik:
      # ...
      networks:
        - "traefikproxy"
    whoami:
      # ...
      networks:
        - "traefikproxy"
Enter fullscreen mode Exit fullscreen mode

Somewhat obviously, the n8n docker-compose would need to be updated so the n8n service would ALSO join the traefikproxy network with:

  networks:
    n8nstack:
    traefikproxy:
      external: true
Enter fullscreen mode Exit fullscreen mode

and the n8n service (not others) ALSO joins the traefik network with:

    services:
      n8n:
        # ...
        networks:
          - "n8nstack"
          - "traefikproxy"
Enter fullscreen mode Exit fullscreen mode

The part that was not as obvious was that the Traefik-related labels in the n8n container/service would now need an additional label to tell Traefik which docker network to use, like

labels:
  - "traefik.enable=true"
  - "traefik.docker.network=traefikproxy"
  # ...
Enter fullscreen mode Exit fullscreen mode
  • Note: I think this could also have been reversed such that the traefik service joined the n8nstack network, and any other stack's network too, but it seemed better to me (more encapsulated) to go the other direction.

The Use Case that Made this Necessary

The reason I got motivated to set up n8n behind a reverse proxy, with https/SSL enabled, is that there seems to be no other way to get through the oauth2 sequence for Google API access, which is required for the Google Drive node. Google wants a redirect URL on the oauth2 credentials that maps to a real, public domain name, and uses https. There may be ways to configure n8n such that it generates an https redirect url (possibly using WEBHOOK_URL) but without actually loading the n8n UI using the https endpoint, it seemed like some part of the whole oauth2 sequence would always skip the track and fail to work properly. Setting up Traefik to deal with the SSL certificate hassles was ultimately the best option.

Quick Overview of Traefik

Traefik is (primarily) a reverse proxy service that accepts and routes web traffic (from browsers and other http(s) clients) to one or more "back end services." In this context, n8n is a "back end service."

  • Note: If the notion of a reverse proxy isn't familiar, reading up on that would probably help.

Traefik + LetsEncrypt - Certificate Automation

This is a simplified overview of how this works...

  1. Traefik is configured with an API key for the registrar that controls DNS for the top level domain and its subdomains.
  2. Traefik is configured to accept https/443 requests on the domain, one or more of its subdomains, or both.
  3. When required (i.e. when the SSL server certificate for a given host name is missing or expired), Traefik automatically sends a request to LetsEncrypt to fetch a server certificate.
  4. LetsEncrypt responds to Traefik with a "DNS Challenge", which is basically a request to create an arbitrary DNS record, temporarily, in order to prove "ownership" of the domain.
  5. Traefik uses the registrar API key (and a corresponding "provider" plugin) to make the DNS change, and then sends a message back to LetsEncrypt saying, "Check DNS again now."
  6. LetsEncrypt checks DNS and, assuming the "challenge" entry/record is found, returns the server certificate for the requested domain/subdomain.
  7. Traefik stores the certificate (for use on the proxy server) and cleans up the temporary DNS records (again using the registrar API functions).

Related n8n Details

N8N_EDITOR_BASE_URL

When n8n is reached through a reverse-proxy like Traefik (or a kubernetes load balancer, or a rule on a dedicated nginx web server, or any number of other things like that), there is an environment-variable (or n8n config) setting named N8N_EDITOR_BASE_URL to tell n8n, internally, what to use (in the UI and elsewhere) as the "external" (browser context) base URL. It may be necessary to set this in the n8n config when using Traefik.

  • Currently, (around n8n v1.59.0), the credentials dialog for a Google API related credential, and, therefore, the redirect URL sent to Google for oauth2, appears to formulate the redirect URL based on either:
    1. how the UI itself is loaded into the browser, or
    2. N8N_EDITOR_BASE_URL, if it is specified in the environment or config.

WEBHOOK_URL

Another override that might be necessary when n8n is cloistered behind Traefik, is the WEBHOOK_URL config/environment-variable.

  • This is used for... big surprise... webhook related things like $execution.resumeUrl
  • It does not appear that WEBHOOK_URL has an influence over the oauth2 redirect URL, although I think that may have been suggested in a few places. Perhaps WEBHOOK_URL also served the purpose of N8N_EDITOR_BASE_URL in a previous version of n8n.

Links

. .
Terabox Video Player