Configuring Traefik Reverse Proxy for Container Deployments
What You’ll Need
- A cloud server like a Hetzner VPS or DigitalOcean running Ubuntu 24.04 LTS
- A registered domain name managed via Namecheap or your preferred DNS provider
- Docker Engine and Docker Compose installed on your host machine
- An orchestration or workflow tool like n8n Cloud or self-hosted container applications ready for deployment
Table of Contents
- Setting Up Docker and Traefik Static Configuration
- Dynamic Routing for Container Microservices
- Advanced Middleware: Rate Limiting, TLS, and Webhook Retries
- Getting Started
Setting Up Docker and Traefik Static Configuration
When I manage multi-container environments, relying on legacy web servers like Nginx or Apache often creates configuration friction. Standard reverse proxies require you to manually write site configs, expose internal ports, reload daemon processes, and explicitly execute Certbot scripts every time a new container spins up.
Traefik changes this model entirely. It acts as an edge router that listens directly to your container engine socket. As soon as you deploy a Docker container with specific labels attached, Traefik detects the event, generates SSL certificates through Let’s Encrypt, configures dynamic routing rules, and attaches middleware automatically without dropping a single active HTTP connection.
To get started with Traefik v3, I divide configuration into two distinct categories: static configuration and dynamic configuration. Static configuration defines entry points, backend logging level, provider integrations, and ACME challenge mechanisms. This is loaded when Traefik boots up. Dynamic configuration defines the actual routes, services, tls settings, and middleware pipeline attached to your containers, which Traefik updates in real time based on active runtime states.
First, spin up your server instance on a high-performance host like a Hetzner VPS. Ensure your domain DNS A records hosted on Namecheap point directly to your public server IP address.
Log into your server via SSH and build a isolated system directory structure for your Traefik core platform stack:
mkdir -p /opt/traefik/config
cd /opt/traefik
touch config/dynamic.yml
touch acme.json
chmod 600 acme.json
The acme.json file stores retrieved SSL/TLS certificates generated by Let’s Encrypt. Strict file permissions (600) are mandatory; Traefik will refuse to start if this file is globally readable.
Next, create the main static configuration file /opt/traefik/config/traefik.yml using your preferred text editor:
global:
checkNewVersion: false
sendAnonymousUsage: false
log:
level: INFO
format: json
accessLog:
format: json
filters:
statusCodes:
- "200-299"
- "400-599"
api:
dashboard: true
insecure: false
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: ":443"
http:
tls:
certResolver: letsencrypt
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
watch: true
file:
filename: "/etc/traefik/config/dynamic.yml"
watch: true
certificatesResolvers:
letsencrypt:
acme:
email: "admin@example.com"
storage: "/acme.json"
httpChallenge:
entryPoint: web
Now, create the primary orchestration stack file /opt/traefik/docker-compose.yml. This runs Traefik inside its own isolated Docker container while binding ports 80 and 443 to your public network interface:
services:
traefik:
image: traefik:v3.1
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
ports:
- "80:80"
- "443:443"
networks:
- proxy
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- /opt/traefik/config/traefik.yml:/etc/traefik/traefik.yml:ro
- /opt/traefik/config/dynamic.yml:/etc/traefik/config/dynamic.yml:ro
- /opt/traefik/acme.json:/acme.json
labels:
- "traefik.enable=true"
- "traefik.http.routers.dashboard.rule=Host(`traefik.example.com`)"
- "traefik.http.routers.dashboard.entrypoints=websecure"
- "traefik.http.routers.dashboard.service=api@internal"
- "traefik.http.routers.dashboard.tls.certresolver=letsencrypt"
- "traefik.http.routers.dashboard.middlewares=auth"
- "traefik.http.middlewares.auth.basicauth.users=admin:$$apr1$$q8312z1x$$Y3O4V902S9.qL9W.aP2n10"
networks:
proxy:
external: true
Create the required bridge network before launching the deployment stack:
docker network create proxy
docker compose up -d
💡 Fast-Track Your Project: Don’t want to configure this yourself? I build custom n8n pipelines and bots. Message me with code SYS3-HUGO.
Dynamic Routing for Container Microservices
With Traefik active on the external proxy network, you can launch application microservices without ever editing static files or restarting web servers again. Traefik inspects metadata declared inside container labels to dynamically map incoming hostnames to backend container endpoints.
When I run complex backend systems, I frequently rely on distinct service layers. For instance, when building distributed Python task schedulers with Dramatiq, having a robust reverse proxy routing client requests directly to async API instances while isolating task broker stores (like Redis or RabbitMQ) from the public internet is essential.
Let’s look at a multi-service docker-compose.yml file illustrating how to expose an API task manager alongside an internal web UI while keeping internal worker stores hidden:
services:
task-api:
image: python:3.11-slim
container_name: task-api
restart: always
command: uvicorn main:app --host 0.0.0.0 --port 8000
environment:
- REDIS_URL=redis://redis-backend:6379/0
networks:
- proxy
- internal-mesh
labels:
- "traefik.enable=true"
- "traefik.http.routers.taskapi.rule=Host(`api.example.com`)"
- "traefik.http.routers.taskapi.entrypoints=websecure"
- "traefik.http.routers.taskapi.tls.certresolver=letsencrypt"
- "traefik.http.services.taskapi.loadbalancer.server.port=8000"
redis-backend:
image: redis:7-alpine
container_name: redis-backend
restart: always
networks:
- internal-mesh
labels:
- "traefik.enable=false"
networks:
proxy:
external: true
internal-mesh:
driver: bridge
Notice how redis-backend only exists on internal-mesh and sets traefik.enable=false. It is entirely inaccessible from the outside world. Traefik routes incoming public traffic meant for api.example.com directly to port 8000 on task-api over the shared proxy network.
Similarly, if you are deploying Appsmith on budget Hetzner Cloud VPS, putting your low-code apps and operational dashboards behind Traefik guarantees automated SSL certificate generation, domain routing, and seamless load balancing without managing raw server blocks.
Here is a full manifest showing how to attach an Appsmith web interface to Traefik using custom dynamic router parameters:
services:
appsmith:
image: appsmith/appsmith-ce:latest
container_name: appsmith-ui
restart: unless-stopped
ports:
- "8080:80"
environment:
- APPSMITH_SERVER_PORT=8080
networks:
- proxy
volumes:
- /opt/appsmith/stacks:/appsmith-stacks
labels:
- "traefik.enable=true"
- "traefik.http.routers.appsmith.rule=Host(`dashboard.example.com`)"
- "traefik.http.routers.appsmith.entrypoints=websecure"
- "traefik.http.routers.appsmith.tls.certresolver=letsencrypt"
- "traefik.http.services.appsmith.loadbalancer.server.port=8080"
- "traefik.http.routers.appsmith.middlewares=sec-headers@file"
networks:
proxy:
external: true
By leveraging traefik.http.services.<service_name>.loadbalancer.server.port, you explicitly inform Traefik which port to target inside the container network payload, eliminating ambiguity when containers expose multiple standard ports (like 80, 443, and 8080 simultaneously).
Advanced Middleware: Rate Limiting, TLS, and Webhook Retries
Middlewares allow you to transform requests before they hit your backend services. You can chain multiple middleware modules together to handle security, compression, rate-limiting, and error handling at the proxy layer instead of clogging your application runtime code.
I maintain critical middleware patterns inside Traefik’s dynamic file provider (/opt/traefik/config/dynamic.yml). This decoupled design lets me update system-wide global policies without editing container deployment files.
Edit /opt/traefik/config/dynamic.yml and add the following complete configuration:
http:
middlewares:
sec-headers:
headers:
frameDeny: true
sslRedirect: true
browserXssFilter: true
contentTypeNosniff: true
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: true
customFrameOptionsValue: "SAMEORIGIN"
api-rate-limit:
rateLimit:
average: 100
burst: 50
period: 1s
backend-retry:
retry:
attempts: 4
initialInterval: 100ms
webhook-auth:
basicAuth:
users:
- "webhookuser:$apr1$c91p8x0a$4P3nK92lS81j.qL9W.aP2n"
tls:
options:
default:
minVersion: "VersionTLS12"
cipherSuites:
- "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256"
- "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
- "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384"
- "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
- "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305"
- "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305"
The backend-retry middleware is an essential piece of network resilience infrastructure. If a target application container is temporarily unresponsive during a rolling update, Traefik automatically resends the HTTP request up to four times using an incremental delay before returning a 502 Bad Gateway response to the end user.
While network-level retries in Traefik help salvage transient HTTP 502 or 503 errors during container lifecycle events, high-volume transactional integrations require comprehensive error strategies. I recommend pairing Traefik retry policies with handling webhook retries using exponential backoff strategies inside your application code to handle persistent multi-minute service outages safely.
To apply dynamic middlewares to any container, simply reference the middleware name in your container labels:
labels:
- "traefik.enable=true"
- "traefik.http.routers.webhook-service.rule=Host(`hooks.example.com`)"
- "traefik.http.routers.webhook-service.entrypoints=websecure"
- "traefik.http.routers.webhook-service.tls.certresolver=letsencrypt"
- "traefik.http.routers.webhook-service.middlewares=sec-headers@file,api-rate-limit@file,backend-retry@file"
- "traefik.http.services.webhook-service.loadbalancer.server.port=5000"
The suffix @file explicitly tells Traefik to look for those middleware definitions in the dynamic configuration file rather than inspecting the container’s Docker labels. You can chain comma-separated middlewares sequentially; Traefik executes them in the exact order specified from left to right.
Getting Started
Building an automated, resilient, and secure container infrastructure starts with selecting robust host infrastructure and reliable routing components. You can deploy this entire Traefik stack in under ten minutes by provisioning a cloud server on Hetzner VPS or DigitalOcean, mapping your custom domains via Namecheap, and orchestrating automated workflow applications like n8n Cloud right behind your proxy.
Outsource Your Automation
Don’t have time? I build production n8n workflows, WhatsApp bots, and fully automated YouTube Shorts pipelines. Hire me on Fiverr, mention SYS3-HUGO for priority. Or DM at chasebot.online.
Want to automate this yourself?
Start with n8n Cloud (free tier available) or self-host on a Hetzner VPS for full control.
Want this engine running on your own VPS?
This blog publishes itself — daily, unattended, on free API tiers. The full engine, Hugo theme, and setup guide are available as System 3.
Get System 3