Configuring Traefik as a Modern Reverse Proxy for Odoo As modern web applications become increasingly containerized, managing traffic across multiple services, domains, and versions can quickly spiral out of control. That’s where Traefik, a dynamic reverse proxy and load balancer, becomes a game-changer. Over the past few weeks, I’ve been working on deploying multi-tenant Odoo environments (v15 to v18) using Docker. I needed a solution that could: Dynamically route traffic based on subdomains Handle HTTPS via Let’s Encrypt automatically Support longpolling and websocket traffic (used by Odoo's real-time features) Scale with minimal maintenance The answer? Traefik . What is Traefik? Traefik (pronounced traffic) is a modern reverse proxy and load balancer designed for microservices. It automatically discovers services via Docker, Kubernetes, Consul, and more, and configures itself on the fly. Unlike Nginx or HAProxy, Traefik is truly dynamic — it adapts to your containers and services as they spin up or down. It also integrates tightly with Let’s Encrypt for automatic TLS/SSL. Case: Odoo Instances and Clients Imagine this scenario: you manage multiple clients, each using different versions of Odoo (e.g., 15, 16, 17, 18). You need to ensure high availability and secure access for all of them. While Nginx is a solid reverse proxy, Traefik offers a more dynamic, automated approach that can significantly reduce configuration time and ongoing maintenance. So lets start with the basic configuration! Folder Structure Organize your project like this: project-root/ ├── traefik.yml ├── acme.json ← should exist and have chmod 600 └── dynamic/ ├── dynamic_routes.yml └── middlewares.yml Create acme.json before you start: touch acme.json chmod 600 acme.json traefik.yml entryPoints: web: address: ":80" http: redirections: entryPoint: to: websecure scheme: https websecure: address: ":443" http: tls: certResolver: letsencrypt options: default providers: docker: exposedByDefault: false network: odoo-router-net file: directory: /etc/traefik/dynamic/ watch: true certificatesResolvers: letsencrypt: acme: email: admin@example.com storage: /etc/traefik/acme.json httpChallenge: entryPoint: web Breakdown of traefik.yml This file defines the static configuration of Traefik — the part that sets up how Traefik listens for requests, discovers services, handles SSL certificates, and loads dynamic config files. entryPoints entryPoints: web: address: ":80" http: redirections: entryPoint: to: websecure scheme: https web listens on port 80 (HTTP). It automatically redirects all HTTP requests to HTTPS, ensuring secure connections by default. websecure: address: ":443" http: tls: certResolver: letsencrypt options: default websecure listens on port 443 (HTTPS). TLS is enabled using Let’s Encrypt and a default security profile. This is where secure traffic lands, including browser requests to your Odoo sites. providers providers: docker: exposedByDefault: false network: odoo-router-net Docker provider automatically detects containers and routes based on their labels. exposedByDefault: false means services must opt-in via labels, increasing security. network: odoo-router-net ensures Traefik can reach services in that Docker network. file: directory: /etc/traefik/dynamic/ watch: true The file provider loads routing rules and middleware from dynamic .yml files in /etc/traefik/dynamic/. watch: true makes Traefik reload config automatically when you edit those files (e.g. dynamic_routes.yml, middlewares.yml). certificatesResolvers: letsencrypt: acme: email: admin@example.com storage: /etc/traefik/acme.json httpChallenge: entryPoint: web Sets up Let's Encrypt integration. Automatically requests and renews SSL certificates for your domains. Certificates are saved to acme.json (should be chmod 600). Uses the HTTP challenge to verify domain ownership (Traefik listens on port 80 to complete the verification). TLS Options You reference options: default, so you should also define: tls: options: default: minVersion: VersionTLS12 cipherSuites: - TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 - TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305 This enforces modern, secure encryption protocols and disables older TLS versions (like TLS 1.0/1.1), reducing risk from outdated clients. dynamic/dynamic_routes.yml http: routers: client1-http: rule: "Host(`client1.example.com`)" service: odoo-v15-http entryPoints: - websecure tls: certResolver: letsencrypt middlewares: - secure-headers - rate-limit client1-longpolling: rule: "Host(`client1.example.com`) && PathPrefix(`/longpolling`)" service: odoo-v15-longpolling entryPoints: - websecure tls: certResolver: letsencrypt middlewares: - secure-headers - rate-limit services: odoo-v15-http: loadBalancer: servers: - url: "http://odoo15:8069" odoo-v15-longpolling: loadBalancer: servers: - url: "http://odoo15:8072" dynamic_routes.yml: Dynamic Routing Configuration This file tells Traefik how to match incoming requests to services running in your Docker environment. It defines: Routers: Match incoming requests based on domain and path Services: Define where those requests should be forwarded (to which container + port) Middlewares: Apply security headers, rate limits, etc. routers Section client1-http: rule: "Host(`client1.example.com`)" This router catches HTTPS requests to client1.example.com and forwards them to odoo-v15-http. client1-longpolling: rule: "Host(`client1.example.com`) && PathPrefix(`/longpolling`)" This router specifically handles Odoo’s real-time longpolling requests (used for chat, live updates). This ensures that /longpolling is routed to port 8072, which Odoo uses for push communication. entryPoints: - websecure Specifies that both routers accept HTTPS traffic. tls: certResolver: letsencrypt Tells Traefik to automatically issue and manage SSL certificates for this domain using Let's Encrypt. middlewares: - secure-headers - rate-limit Adds security headers and request throttling for each route. services Section odoo-v15-http: loadBalancer: servers: - url: "http://odoo15:8069" Routes the main UI/API traffic to Odoo’s port 8069. odoo-v15-longpolling: loadBalancer: servers: - url: "http://odoo15:8072" Routes longpolling (bus) requests to port 8072 — essential for features like live chat, POS sync, or notifications. Why Separate Routers? It improves performance and clarity by letting Traefik route longpolling traffic separately. Enables better control: you could assign different middleware, rate limits, or timeouts for /longpolling. http: middlewares: secure-headers: headers: browserXssFilter: true contentTypeNosniff: true frameDeny: true sslRedirect: true stsIncludeSubdomains: true stsPreload: true stsSeconds: 31536000 referrerPolicy: "strict-origin-when-cross-origin" customResponseHeaders: X-Robots-Tag: "none" X-Permitted-Cross-Domain-Policies: "none" rate-limit: rateLimit: average: 100 burst: 50 middlewares.yml: Adding Security amp; Traffic Control This file defines middleware rules that Traefik applies to incoming HTTP requests. Middleware allows you to add features like security headers, rate limiting, authentication, and more — without modifying your app code. secure-headers Middleware secure-headers: headers: browserXssFilter: true contentTypeNosniff: true frameDeny: true sslRedirect: true stsIncludeSubdomains: true stsPreload: true stsSeconds: 31536000 referrerPolicy: "strict-origin-when-cross-origin" customResponseHeaders: X-Robots-Tag: "none" X-Permitted-Cross-Domain-Policies: "none" This adds a suite of HTTP headers to improve security: XSS protection: Helps block malicious scripts Clickjacking prevention: Denies rendering inside iframes (frameDeny) Content sniffing protection: Prevents MIME-type confusion HSTS: Enforces HTTPS via Strict-Transport-Security Referrer policy: Controls what info gets sent in the Referer header These headers are especially important when exposing admin panels or user portals. rate-limit Middleware rate-limit: rateLimit: average: 100 burst: 50 This throttles requests: Allows 100 requests per second on average Bursts up to 50 extra requests during short spikes It’s a simple way to prevent abuse, reduce load, and mitigate brute-force or DDoS-style attempts. Applying Middleware In your dynamic_routes.yml, reference these like so: middlewares: - secure-headers - rate-limit This way, all requests go through your defined middleware before reaching Odoo. Running Traefik with Docker Compose Once you’ve defined your traefik.yml, dynamic_routes.yml, and middlewares.yml, the final step is to bring Traefik to life using Docker Compose. This step launches Traefik and connects it to your Docker network and dynamic config files. The docker-compose.yml for Traefik services: traefik: image: traefik:v3 container_name: traefik restart: unless-stopped command: - --configFile=/etc/traefik/traefik.yml ports: - "80:80" # HTTP - "443:443" # HTTPS volumes: - /var/run/docker.sock:/var/run/docker.sock - ./traefik.yml:/etc/traefik/traefik.yml:ro - ./acme.json:/etc/traefik/acme.json - ./dynamic:/etc/traefik/dynamic:ro networks: - odoo-router-net networks: odoo-router-net: external: true Running Traefik Once everything is in place, simply run: docker compose up -d This command: Starts Traefik Watches your Docker containers (if configured with labels) Loads routing rules and middleware from the /dynamic folder Automatically issues and renews HTTPS certificates via Let’s Encrypt Conclusions I've always enjoyed managing bare-metal servers and exploring ways to automate and streamline my workflow — whether that's writing scripts or using graphical tools when it makes sense. In that journey, I discovered Traefik as a powerful alternative to traditional proxies like Nginx. What stood out to me most is how maintainable and automation-friendly Traefik is. With dynamic routing, built-in Let's Encrypt integration, and seamless Docker support, it significantly reduces the manual overhead of managing reverse proxies in complex multi-service environments — like multi-version Odoo deployments.