Secure Services with Traefik Proxy
Set up Traefik as a reverse proxy to route traffic, enable HTTPS, and manage multiple services easily.
Introduction
When running multiple services in a homelab or production environment, a reverse proxy is essential for centralizing traffic management, handling HTTPS certificates, and applying security policies. Traefik Proxy is a modern, cloud-native reverse proxy that makes this process efficient and flexible.
This guide demonstrates how to set up Traefik using configuration files (traefik.yml and dynamic.yml) instead of relying on Docker labels. Separating configuration into YAML files improves maintainability and makes it easier to add advanced security features.
Installation steps
Docker Compose Setup
In this step, we define the Traefik service using Docker Compose. The configuration mounts the static and dynamic files, as well as persistent volumes for certificates and logs. This ensures that Traefik is reproducible, secure, and does not lose state between restarts.
services:
traefik:
container_name: traefik
image: traefik:v3.4
restart: unless-stopped
environment:
- TZ=Europe/Madrid
ports:
- "80:80"
- "443:443"
security_opt:
- no-new-privileges:true
volumes:
- ./config/traefik.yml:/traefik.yml:ro
- ./config/dynamic.yml:/dynamic.yml:ro
- ./config/users.txt:/users.txt:ro
- ./letsencrypt:/letsencrypt
- ./traefik_logs:/var/lib/traefik
volumes:
letsencrypt:
driver: local
traefik_logs:
driver: localOnce this file is created, you can start Traefik by running:
docker compose up -dThis will download the Traefik image (if not already present), create the container, and attach the required volumes. The proxy will then be accessible on ports 80 (HTTP) and 443 (HTTPS). At this stage, Traefik is running but not yet configured to serve any specific service.
Static Configuration (traefik.yml)
The traefik.yml file defines the static configuration. These are settings Traefik reads only at startup, so they cannot be reloaded dynamically. Here we configure entry points, logging, metrics, certificate resolvers, and providers. The goal is to establish how Traefik should behave globally before it starts serving traffic.
Entry Points
We define two entry points: one for plain HTTP web that redirects all traffic to HTTPS, and one for secure traffic websecure.
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
websecure:
address: ":443"
http:
tls:
certResolver: productionLogging
Traefik produces two kinds of logs:
- Service logs (internal status, warnings, errors).
- Access logs (every request handled).
log:
level: INFO
filePath: /var/lib/traefik/traefik.log
format: common
accessLog:
filePath: /var/lib/traefik/access.log
format: commonMetrics
In a future tutorial, we’ll cover how to enable Prometheus metrics in Traefik. This provides visibility into routers, services, and traffic patterns.
metrics:
prometheus:
addEntryPointsLabels: true
addRoutersLabels: true
addServicesLabels: true
entryPoint: webProviders
Traefik loads dynamic configuration (routers, services, middlewares) from a separate file dynamic.yml. With watch: true, it reloads automatically when the file changes.
providers:
file:
filename: ./dynamic.yml
watch: trueCertificates Resolvers
We configure Let’s Encrypt resolvers for automatic TLS certificate management. The production resolver requests real certificates, while staging is useful for testing.
certificatesResolvers:
production:
acme:
email: [email protected]
storage: /letsencrypt/acme-production.json
caServer: "https://acme-v02.api.letsencrypt.org/directory"
httpChallenge:
entryPoint: web
staging:
acme:
email: [email protected]
storage: /letsencrypt/acme-staging.json
caServer: "https://acme-staging-v02.api.letsencrypt.org/directory"
httpChallenge:
entryPoint: webDynamic Configuration (dynamic.yml)
The dynamic.yml file defines the routing logic for Traefik. Unlike the static configuration, this file can be reloaded without restarting Traefik. It contains three main elements:
- Routers: Match incoming requests (e.g., based on hostname).
- Services: Define where the traffic should be forwarded (usually Docker containers).
- Middlewares: Apply transformations or restrictions before requests reach the service.
Routers and Services
A router inspects incoming traffic and decides which service should handle it. Each service points to one or more backend servers.
http:
routers:
example-app:
entryPoints:
- websecure
rule: "Host(`app.example.com`)"
service: example-app
middlewares: [auth, geoblock, torblock]
tls:
certResolver: production
services:
example-app:
loadBalancer:
servers:
- url: "http://backend:8080"In this example:
- Requests to https://app.example.com are routed to the container named backend on port 8080.
- The router enforces HTTPS via the websecure entry point.
- Middlewares auth and geoblock are applied before traffic reaches the backend.
Enabling Authentication
To restrict access with Basic Authentication, you must create a users.txt file that stores usernames and bcrypt-hashed passwords.
Generate the file using htpasswd (from the apache2-utils package):
htpasswd -nbB admin strongpassword > config/users.txtThis produces an entry like:
admin:$2y$05$zG6XyYx1rGc...Then, reference it in your middleware configuration:
http:
middlewares:
auth:
basicAuth:
usersFile: users.txtAny router that includes middlewares: [auth] will now prompt users for credentials before allowing access.
Security Plugins
Traefik can be extended with plugins that act as middlewares to filter or protect incoming requests. Plugin definitions live in the static configuration traefik.yml, while their usage and policies are applied in the dynamic configuration dynamic.yml.
In this setup, three plugins are particularly useful for hardening exposed services:
GeoBlock
Blocks or allows requests based on the client’s country of origin.
experimental:
plugins:
GeoBlock:
moduleName: github.com/PascalMinder/geoblock
version: v0.2.5http:
middlewares:
geoblock:
plugin:
GeoBlock:
api: "https://get.geojs.io/v1/ip/country/{ip}"
countries:
- US
- ES
allowLocalRequests: trueTorBlock
Denies requests originating from Tor exit nodes, preventing anonymous abuse.
experimental:
plugins:
torblock:
moduleName: github.com/jpxd/torblock
version: v0.1.1http:
middlewares:
torblock:
plugin:
torblock:
enabled: trueApply this middleware to routes that should not be accessible from Tor.
ModSecurity Plugin
Integrates Traefik with a ModSecurity Web Application Firewall (WAF). This adds deep request inspection and protection against common web attacks (SQL injection, XSS, etc.).
waf:
container_name: waf
image: owasp/modsecurity-crs:apache
environment:
- PARANOIA=1
- ANOMALY_INBOUND=10
- ANOMALY_OUTBOUND=5
- BACKEND=http://dummy
dummy:
container_name: dummy
image: traefik/whoamiexperimental:
plugins:
traefik-modsecurity-plugin:
moduleName: github.com/acouvreur/traefik-modsecurity-plugin
version: v1.3.0http:
middlewares:
modsecurity:
plugin:
traefik-modsecurity-plugin:
ModsecurityUrl: http://waf:8080/Here, http://waf:8080 points to a companion WAF container that processes and filters traffic.
Conclusion
By separating static (traefik.yml) and dynamic (dynamic.yml) configuration files, Traefik deployments become cleaner, easier to manage, and more secure.
docker-compose.ymlhandles container lifecycle.traefik.ymldefines entry points, TLS certificates, and the file provider.dynamic.ymlmanages routers, services, and middlewares.- Plugins such as GeoBlock, TorBlock, and ModSecurity further strengthen your reverse proxy setup.
This modular approach ensures that your Traefik setup can scale as you add more services, while keeping security and maintainability at the forefront.
Appendices
services:
traefik:
container_name: traefik
image: traefik:v3.4
restart: unless-stopped
environment:
- TZ=Europe/Madrid
ports:
- "80:80"
- "443:443"
security_opt:
- no-new-privileges:true
volumes:
- ./config/traefik.yml:/traefik.yml:ro
- ./config/dynamic.yml:/dynamic.yml:ro
- ./config/users.txt:/users.txt:ro
- ./letsencrypt:/letsencrypt
- ./traefik_logs:/var/lib/traefik
waf:
container_name: waf
image: owasp/modsecurity-crs:apache
environment:
- PARANOIA=1
- ANOMALY_INBOUND=10
- ANOMALY_OUTBOUND=5
- BACKEND=http://dummy
dummy:
container_name: dummy
image: traefik/whoami
volumes:
letsencrypt:
driver: local
traefik_logs:
driver: local