How I Put n8n Behind Traefik with Automatic HTTPS: A Real-World Traefik + n8n Setup
A practical, battle-tested guide to wiring n8n, Traefik, Docker networks, and Let’s Encrypt into a production-ready stack.

This all started with a pretty simple goal:
“Run n8n on my server and expose it cleanly at
https://n-test.mahdishadi.me.”
I could have just exposed n8n’s port directly on the server and called it a day.
But I wanted something more:
Clean domain-based routing
Proper HTTPS with Let’s Encrypt
A setup that scales nicely when I add more services
So I went for a cloud-native approach with Traefik in front and n8n behind it in Docker.
Of course, it didn’t “just work.” There were 404s, 308s, ACME errors, missing volumes, and plenty of docker exec + curl.
Here’s the full story of what I did, why I did it, and how I debugged the problems along the way.
Why Traefik? And Why Cloud-Native Matters Here
What is Traefik?
Traefik is an edge router / reverse proxy built for the cloud-native world.
Instead of manually editing a huge config file and defining all your backends, Traefik:
Discovers services automatically (via Docker, Kubernetes, Consul, etc.)
Configures routes based on labels or Ingress definitions
Handles Let’s Encrypt and automatic certificate renewal
Offers middlewares (redirects, auth, rate limiting, etc.)
Ships with a web dashboard and metrics integration
Why use it instead of just exposing n8n directly?
When you have more than one service, the “just expose a random port” approach gets ugly fast:
You end up with URLs like
server-ip:5678,server-ip:9000, etc.Managing HTTPS per service becomes painful.
Security and organization become a mess.
With Traefik:
You expose only Traefik to the internet (ports 80/443).
All internal services (like n8n) live on a private Docker network.
You route based on hostname (e.g.
n-test.mahdishadi.me→ n8n).SSL is handled centrally, automatically.
What does “cloud-native” buy us here?
Dynamic service discovery
New containers with the correct labels are picked up automatically—no manual configuration reloads are required.Configuration via labels
Routing logic lives alongside the service definition (indocker-compose.yml), not in a giant central config file.Automatic TLS
Traefik talks to Let’s Encrypt via ACME, requests and renews certs, and stores them in a file you mount.Better security & separation
Only Traefik is exposed. n8n is reachable only over an internal Docker network.
The Final Architecture
Here’s the architecture I aimed for:
Traefik:
Listens on ports
80and443for HTTP/HTTPSHas a dashboard on port
8080Talks to Docker and automatically discovers services
Gets Let’s Encrypt certs and stores them in
acme.json
n8n :
Runs in Docker on a private network
frontendDoes not expose its port directly to the host
Is accessible only via Traefik at
https://n-test.mahdishadi.me
Docker network:
- A shared network named
frontendthat both Traefik and n8n join
- A shared network named
Traefik Setup: EntryPoints, Docker Provider, and Let’s Encrypt
docker-compose for Traefik
services:
traefik:
image: traefik:v3.6
container_name: traefik-demo
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./config/traefik.yaml:/etc/traefik/traefik.yaml:ro
- ./letsencrypt:/letsencrypt
networks:
- frontend
restart: unless-stopped
networks:
frontend:
external: true
Key ideas:
The Docker socket lets Traefik read labels on your containers.
The config file is mounted as
traefik.yaml.The
./letsencryptdirectory is mounted into the container as/letsencryptfor cert storage.Traefik is on the
frontendnetwork so it can reach n8n.
traefik.yaml configuration
global:
checkNewVersion: false
sendAnonymousUsage: false
log:
level: DEBUG
api:
dashboard: true
insecure: true
entryPoints:
web:
address: ":80"
http:
redirections: # redirect HTTP -> HTTPS
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
providers:
docker:
endpoint: "unix://var/run/docker.sock"
exposedByDefault: false
network: frontend
certificatesResolvers:
letsencrypt:
acme:
email: "mahdishadi99@gmail.com"
storage: "/letsencrypt/acme.json"
httpChallenge:
entryPoint: "web"
Why this config?
exposedByDefault: false
Traefik does not automatically publish every container. Only containers withtraefik.enable=truebecome reachable. This is safer and more explicit.network: frontend
Traefik will use thefrontendDocker network when talking to containers. n8n is on the same network, so they can talk.entryPoints.web→websecureredirect
Everything on port 80 gets redirected to HTTPS on port 443. All public traffic ends up encrypted.certificatesResolvers.letsencryptwithhttpChallenge
Traefik uses the HTTP-01 challenge on the entrypointweb(port 80) to obtain certificates and store them at/letsencrypt/acme.json.
On the host side, I created the ACME storage file:
mkdir -p letsencrypt
touch letsencrypt/acme.json
chmod 600 letsencrypt/acme.json
n8n Setup: Labels and Environment
Here’s the n8n stack I ended up with:
version: "3.8"
networks:
frontend:
external: true
services:
n8n:
image: n8nio/n8n:latest
container_name: n8n
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.N8N.rule=Host(`n-test.mahdishadi.me`)"
- "traefik.http.routers.N8N.entrypoints=websecure"
- "traefik.http.routers.N8N.tls.certresolver=letsencrypt"
- "traefik.http.routers.N8N-http.rule=Host(`n-test.mahdishadi.me`)"
- "traefik.http.routers.N8N-http.entrypoints=web"
- "traefik.http.routers.N8N-http.middlewares=N8N-https-redirect"
- "traefik.http.middlewares.N8N-https-redirect.redirectscheme.scheme=https"
environment:
- N8N_HOST=n-test.mahdishadi.me
- N8N_PORT=5678
- N8N_PROTOCOL=https
- NODE_ENV=production
- WEBHOOK_URL=https://n-test.mahdishadi.me
- GENERIC_TIMEZONE=Asia/Tehran
- N8N_BASIC_AUTH_ACTIVE=true
- N8N_BASIC_AUTH_USER=admin
- N8N_BASIC_AUTH_PASSWORD=********
- N8N_USER_MANAGEMENT_DISABLED=true
- N8N_AUTH_DISABLE=true
- N8N_DIAGNOSTICS_ENABLED=false
volumes:
- n8ntest_data:/home/node/.n8n
- ./local-files:/files
networks:
- frontend
volumes:
n8ntest_data:
external: true
Why these Traefik labels?
traefik.enable=true
Required becauseexposedByDefault: false. Without this, Traefik ignores the container completely.Main HTTPS router:
- "traefik.http.routers.N8N.rule=Host(`n-test.mahdishadi.me`)" - "traefik.http.routers.N8N.entrypoints=websecure" - "traefik.http.routers.N8N.tls.certresolver=letsencrypt"Matches only requests with
Host:n-test.mahdishadi.me.Uses the
websecureentrypoint (port 443).Uses the
letsencryptresolver defined intraefik.yamlto get the certificate.
HTTP router + redirect middleware:
- "traefik.http.routers.N8N-http.rule=Host(`n-test.mahdishadi.me`)" - "traefik.http.routers.N8N-http.entrypoints=web" - "traefik.http.routers.N8N-http.middlewares=N8N-https-redirect" - "traefik.http.middlewares.N8N-https-redirect.redirectscheme.scheme=https"This router listens on
web(port 80) and applies theN8N-https-redirectmiddleware, which simply changes the scheme tohttps. That means:http://n-test.mahdishadi.me→ 301/308 →https://n-test.mahdishadi.me.
Why these n8n environment variables?
N8N_HOST,N8N_PORT,N8N_PROTOCOL
n8n must know how it is accessed from the outside so it can generate correct URLs.WEBHOOK_URL
Used for webhook URLs n8n exposes; it must match the external HTTPS URL.GENERIC_TIMEZONE=Asia/Tehran
So scheduled workflows match your local time.N8N_BASIC_AUTH_*
Adds an extra Basic Auth layer in front of the n8n UI. (For a public internet-facing instance, this is very helpful.)N8N_USER_MANAGEMENT_DISABLED,N8N_AUTH_DISABLE
Disables n8n’s internal user management/auth, relying instead on the Basic Auth above for this scenario.N8N_DIAGNOSTICS_ENABLED=false
Disables telemetry/diagnostics.
The Problems I Hit (and How They Were Fixed)
Problem 1: 404 from Traefik – No Router Matched
One of the first tests I ran was:
curl -v http://91.107.179.182/ -H 'Host: n-test.mahdishadi.me'
And I got:
HTTP/1.1 404 Not Found
404 page not found
This means:
The request did reach Traefik (good).
But Traefik didn’t find any router that matched
Host("n-test.mahdishadi.me")on that entrypoint (bad).
Possible causes I checked:
Wrong or missing labels
Docker provider is not working correctly
Service isn’t on the same network as Traefik
By fixing the Docker provider config (exposedByDefault, network: frontend) and ensuring both Traefik and n8n were attached to the frontend network, Traefik started picking up the routers correctly.
Problem 2: HTTP → HTTPS Worked (308), but HTTPS Still Failed
After adding the HTTP router and redirect middleware, I tested:
curl -I http://n-test.mahdishadi.me
Result:
HTTP/1.1 308 Permanent Redirect
Location: https://n-test.mahdishadi.me/
So the HTTP side looked great:
N8N-httprouter was active.Middleware
N8N-https-redirectdid its job.
But the next step was making sure HTTPS itself worked with a valid certificate. That’s where Let’s Encrypt and ACME came in.
Problem 3: Let’s Encrypt / ACME Errors – “HTTP challenge not enabled” / Resolver Skipped
I configured ACME in traefik.yaml and created letsencrypt/acme.json with permissions 600.
Everything looked correct… but Traefik logs / dashboard complained:
The HTTP challenge wasn’t enabled, or
The
letsencryptResolver was being skipped.
So I jumped into the container to see if the ACME storage path really existed:
docker exec -it traefik-demo sh
/ # ls /letsencrypt
ls: can't access '/letsencrypt': No such file or directory
Boom. There it was.
Root cause: the volume wasn’t mounted (old container, new compose)
Yes, my docker-compose.yml had:
volumes:
- ./letsencrypt:/letsencrypt
But I had not recreated the container after adding that. I had only restarted it.
Docker will not magically pick up new volumes on an existing container; you must recreate it.
The fix:
docker compose down
docker compose up -d
Then:
docker exec -it traefik-demo sh
/ # ls /letsencrypt
acme.json
/ # ls -l /letsencrypt/acme.json
-rw------- 1 root root 0 Nov 28 05:51 acme.json
Now Traefik could actually access /letsencrypt/acme.json, and the ACME resolver started working properly. Once the certificate was obtained, acme.json grew from 0 bytes to a real JSON file with cert data.
At that point, hitting:
curl -vk https://n-test.mahdishadi.me/
returned the n8n HTML with a valid Let’s Encrypt certificate, and browsers showed a secure connection.
Debugging Traefik + n8n: A Practical Checklist
This journey turned into a pretty solid debugging checklist:
1. Always start with curl + Host header
curl -v http://SERVER_IP/ -H 'Host: n-test.mahdishadi.me'
curl -vk https://n-test.mahdishadi.me/ -H 'Host: n-test.mahdishadi.me'
What errors mean:
404 → No matching Traefik router (rule/labels/entrypoint issue).
308 → HTTP redirect is working.
502 → Router exists, but backend (n8n) is unreachable (network/port/container down).
2. Use the Traefik dashboard
Visit:http://SERVER_IP:8080/dashboard/
Check:
Routers:
Do you see
N8NandN8N-http?Do they have
Rule = Host("n-test.mahdishadi.me")?Are the entrypoints
web/websecureas expected?
Services:
Is there a service backing
N8N?Is it green (healthy) or red (error)?
What internal IP/port is Traefik using?
3. Verify Docker networking
docker network inspect frontend
docker inspect traefik-demo | grep -A3 '"frontend"'
docker inspect n8n | grep -A3 '"frontend"'
Both containers must be attached to the same network (frontend) or Traefik can’t reach n8n.
4. Double-check ACME / Let’s Encrypt
On the host:
ls -l letsencrypt
# acme.json should be -rw------- (chmod 600)
Inside the container:
docker exec -it traefik-demo sh
ls -l /letsencrypt/acme.json
In logs:
docker logs -f traefik-demo | grep -i acme
Look for:
Certificates being requested / obtained
No “resolver skipped” or “challenge not enabled” errors
Why This Architecture Makes Sense for n8n
In the end, this setup gives you:
A single, secure entrypoint (Traefik on ports 80/443).
n8n hidden behind a Docker network (
frontend), not exposed directly to the internet.Automatic, renewable HTTPS via Let’s Encrypt.
Forced HTTPS (HTTP → HTTPS redirect).
Routing defined right next to the service (as Docker labels) instead of a giant reverse proxy config.
n8n correctly configured with its external URL and secured with Basic Auth.
And the best part: when you want to add another service—say, an API at api.mahdishadi.me—you just add another container with a few Traefik labels. Traefik discovers it automatically; no restarts, no big config edits..



