The Problem Nobody Explains Cleanly
If you self-host n8n, three things will eat a day of your life:
- ●Getting a valid SSL certificate without touching it manually
- ●Routing subdomain traffic to the n8n container without opening it to the public internet
- ●Making sure the whole thing survives a reboot and renews automatically
Most tutorials hand-wave through it with "use Nginx with certbot." Others push you toward Cloudflare Tunnel, which works but adds a dependency. Almost none show the exact Traefik label pattern that makes the Let's Encrypt TLS challenge actually work with n8n on a small VPS.
This is that guide.
What We Built
A production n8n instance on a $7/month Hostinger KVM 2 VPS (2 vCPU, 8GB RAM) running:
- ●n8n as a Docker container, version-pinned, listening on localhost only
- ●Traefik as the reverse proxy handling SSL termination and HTTP-to-HTTPS redirects
- ●Let's Encrypt certificates issued automatically via TLS challenge
- ●Security middlewares for HSTS, XSS protection, MIME type sniffing, and SSL redirect
- ●SMTP configured for n8n error notifications and user invites
The whole stack runs as Docker Compose. Three files (one compose, one env, one Traefik dynamic config). No Nginx. No certbot cron jobs. No manual cert renewal.
The cost breakdown:
| Component | Monthly cost |
|---|---|
| VPS (Hostinger KVM 2) | $7 intro / ~$15 renewal |
| Domain (year divided by 12) | ~$1 |
| Let's Encrypt SSL | $0 |
| n8n community edition | $0 |
| Traefik | $0 |
| Total | ~$8-16/month |
Why Not Just Use n8n Cloud?
n8n Cloud is great. It starts at around $24/month for the Starter plan, handles infrastructure for you, and runs identical workflows. If you value time over control, it's the right answer.
Self-hosting is the right answer when:
- ●You need unlimited executions without tiered pricing
- ●Your workflows touch systems behind a firewall
- ●You have compliance requirements around data residency
- ●You want to run other services on the same server
- ●You want the flexibility to install custom nodes and plugins that require native dependencies
We chose self-hosted because the VPS hosting our n8n instance already had spare capacity for the other services we run. Paying a flat fee for unlimited executions while the server was sitting idle made the math obvious.
Why Traefik Over Nginx or Caddy
Traefik, Nginx, and Caddy all do the same job: accept HTTPS traffic, terminate SSL, route to internal containers.
Nginx is the industry standard. It's battle-tested and fast. The downside is SSL automation — you typically run certbot on the host, generate certs into a local directory, and mount them into the container. It works, but it's more moving parts.
Caddy is the easiest. Write a Caddyfile with your domain and it handles SSL automatically. The tradeoff is less flexibility for advanced routing or multi-container setups.
Traefik is what we chose because:
- ●It discovers services automatically via Docker labels. Add a label to n8n's container and Traefik routes traffic to it. No config file editing.
- ●It handles Let's Encrypt natively with the TLS challenge. Zero cron jobs, zero external scripts.
- ●It supports both Docker labels and a file-based dynamic config for routing to containers on other Docker networks or external services.
- ●It ships a dashboard (optional) for inspecting routes and middleware.
If you only run n8n, Caddy is simpler. If you plan to run multiple services on the same VPS with different routing rules, Traefik is the right long-term answer.
The Architecture
Traefik listens on ports 80 and 443 on the host. Port 80 redirects everything to 443. Port 443 terminates SSL and routes by Host header to internal containers. n8n itself binds to localhost port 5678 on the host, so even if your firewall misconfigures, n8n is not exposed to the public internet directly. The only entry point from the outside world is Traefik on 443.
The Key Traefik Labels on n8n
This is the part every other tutorial gets cagey about. Here are the labels that actually matter, attached to the n8n service in your docker-compose.yml:
services:
n8n:
image: docker.n8n.io/n8nio/n8n:2.16.1
restart: always
ports:
- "127.0.0.1:5678:5678"
labels:
- traefik.enable=true
- traefik.http.routers.n8n.rule=Host(n8n.example.com)
- traefik.http.routers.n8n.tls=true
- traefik.http.routers.n8n.entrypoints=web,websecure
- traefik.http.routers.n8n.tls.certresolver=mytlschallenge
- traefik.http.services.n8n.loadbalancer.server.port=5678What each line does:
- ●traefik.enable=true — opt this container in. Traefik is configured with exposedByDefault=false so nothing routes unless you explicitly enable it.
- ●rule=Host(...) — match requests with that Host header. Multiple services on one VPS use different subdomains.
- ●tls=true + certresolver=mytlschallenge — enable SSL using the Let's Encrypt resolver defined in the Traefik command args (next section).
- ●entrypoints=web,websecure — listen on both HTTP and HTTPS. Traefik's global redirect config upgrades HTTP to HTTPS automatically.
- ●loadbalancer.server.port=5678 — Traefik connects to the container on port 5678 over the internal Docker network, regardless of what's exposed on the host.
Note: the actual Host rule uses backticks around the domain in the YAML file, Host(backtick-domain-backtick). That's the Traefik rule syntax.
Why TLS Challenge Over HTTP Challenge
Let's Encrypt offers two challenge types for issuing certificates:
- ●HTTP-01 places a file at a well-known ACME path and Let's Encrypt fetches it. Works, but requires port 80 to be fully open and the ACME path to not be blocked by your proxy rules.
- ●TLS-ALPN-01 (the TLS challenge) happens over the TLS handshake on port 443. No HTTP serving required for validation.
Traefik supports both. We use the TLS challenge because:
- ●Fewer moving parts — no ACME path routing on port 80.
- ●Port 80 can redirect everything to HTTPS without carve-outs.
- ●Works cleanly behind certain CDN/proxy setups where the HTTP challenge path is harder to expose.
Get the Weekly IT + AI Roundup
What changed this week in NinjaOne, ServiceNow, CrowdStrike, and AI. One email, every Monday.
No spam, unsubscribe anytime. Privacy Policy
The Traefik command line args that enable it:
command:
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--entrypoints.web.address=:80"
- "--entrypoints.web.http.redirections.entryPoint.to=websecure"
- "--entrypoints.web.http.redirections.entrypoint.scheme=https"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.mytlschallenge.acme.tlschallenge=true"
- "--certificatesresolvers.mytlschallenge.acme.email=SSL_EMAIL_ENV_VAR"
- "--certificatesresolvers.mytlschallenge.acme.storage=/letsencrypt/acme.json"The resolver name mytlschallenge matches the certresolver label on the n8n router. You can name it anything — just keep them consistent.
Security Middlewares That Actually Matter
Running n8n behind SSL is table stakes. Running it with proper security headers is where most self-hosted deployments fall short. Our n8n service adds these middlewares:
labels:
- traefik.http.middlewares.n8n.headers.SSLRedirect=true
- traefik.http.middlewares.n8n.headers.STSSeconds=315360000
- traefik.http.middlewares.n8n.headers.STSIncludeSubdomains=true
- traefik.http.middlewares.n8n.headers.STSPreload=true
- traefik.http.middlewares.n8n.headers.forceSTSHeader=true
- traefik.http.middlewares.n8n.headers.browserXSSFilter=true
- traefik.http.middlewares.n8n.headers.contentTypeNosniff=true
- traefik.http.routers.n8n.middlewares=n8n@dockerIn plain English:
- ●HSTS (STSSeconds=315360000) tells browsers to never use HTTP for this domain for 10 years. Prevents SSL stripping attacks.
- ●STSPreload opts your domain into the HSTS preload list. Worth doing once n8n is stable.
- ●forceSTSHeader ensures the header is sent even on plain HTTP responses.
- ●browserXSSFilter enables legacy XSS protection.
- ●contentTypeNosniff prevents MIME sniffing attacks.
- ●SSLRedirect forces HTTPS. The entrypoint-level redirect already handles most traffic, but this is belt-and-suspenders.
Real Gotchas We Hit
1. Traefik needs the Docker socket mounted
Mount it as read-only (/var/run/docker.sock:/var/run/docker.sock:ro). Without this, Traefik cannot see container labels and will route nothing.
2. The ACME storage file needs correct permissions
Let's Encrypt stores issued certs in acme.json. If its permissions are too open on first start, Traefik will refuse to use it. We mount it into a named Docker volume to sidestep host permission issues.
3. WEBHOOK_URL matters for production n8n
n8n uses the WEBHOOK_URL environment variable to tell external services where to POST webhook callbacks. Set it to your full HTTPS URL (https://n8n.example.com/) — not the internal Docker network name.
4. Ports 80 and 443 must be free on the host
If you have Apache or Nginx already running on the host, stop them before starting Traefik. Do not try to run Traefik behind another proxy. Container conflict at the port level is one of the top three reasons this setup fails.
5. DNS must propagate before the first cert request
If you run docker compose up -d the moment after adding a DNS A record, the TLS challenge will fail because Let's Encrypt cannot resolve the domain yet. Wait 5 minutes. Check with dig n8n.example.com from the VPS first.
Pricing Reality
We said $7/month. Here is the honest disclosure:
- ●$7/mo is the Hostinger KVM 2 introductory price on a 24-month commitment
- ●Renewal jumps to about $15/mo after the intro period
- ●Comparable tiers exist at Contabo, Hetzner, DigitalOcean, and Vultr in the $5-15/mo range
- ●Let's Encrypt certificates are free, but you do need a domain (~$12/year)
$7/month is the realistic starting cost. Budget $15-20/month for sustained operation once the promo ends.
Who This Is For
Solo operators running their own automations for side projects or internal tools.
Small agencies running n8n workflows for client work where Cloud pricing stops making sense around 5-10 client workflows.
Home lab enthusiasts already running Docker on a VPS who want to add n8n alongside other services on the same reverse proxy.
Technical founders who want to prototype automation heavy products without committing to SaaS pricing before the business is proven.
If you do not have a domain yet, do not have Docker experience, or are allergic to terminal work, n8n Cloud is a better starting point. You can always migrate later.
The Bigger Picture
Self-hosted n8n is one node in a bigger pattern: running a small handful of compatible services on one VPS behind a single reverse proxy. Once Traefik is configured, adding a second service is 6-8 additional labels on a new container. No additional SSL setup. No additional port forwarding. The incremental cost per service is near zero.
That compound effect is why self-hosting wins long-term for anyone running more than a single app.
Get Started
n8n is open source (Sustainable Use License) and runs in Docker on any Linux VPS.
Want the full deployment walkthrough? Our n8n Administration Pro Track covers Cloud vs self-hosted deployment, the complete docker-compose.yml with PostgreSQL and queue mode, Windows Server PowerShell integration, alternate SSL setups including Cloudflare Tunnel, scaling patterns, and backup strategies. Six modules, end-to-end.
Not sure which AI tools and automation stack fit your workflow? Take the free quiz and get matched in 2 minutes.