Traefik’s automatic TLS and Docker service discovery make it the
most popular reverse proxy in the homelab space. But automatic HTTPS
is only the first step. Without security headers, rate limiting, and
access controls at the proxy layer, every exposed service is one
misconfiguration away from leaking server info, getting hammered by
a scraper, or serving content over HTTP.
Traefik middlewares are the mechanism for applying HTTP-level
policies between the edge and your upstream services. Every security
header, rate-limit rule, authentication check, and URL rewrite is a
discrete middleware object that you attach to a router.
This guide covers the middlewares every homelab should deploy:
security headers for an A+ rating, rate limiting to protect against
abuse, IP whitelisting for admin panels, basic auth for staging
environments, forced HTTPS, compression, and the art of chaining
them together. All configs work with Docker labels or dynamic YAML
files.
How Traefik Middlewares Work#
A middleware intercepts the request before it reaches your
application or the response before it leaves the proxy. Traefik
processes middlewares in the order they are listed on the router.
Two ways to define middlewares:
1. Docker labels — inline on the container:
1
2
3
4
5
6
7
|
# docker-compose.yml
services:
myapp:
image: nginx
labels:
- "traefik.http.routers.myapp.rule=Host(`app.example.com`)"
- "traefik.http.routers.myapp.middlewares=secHeaders@docker"
|
The @docker provider references a middleware defined with the
traefik.http.middlewares.<name> label prefix on the same or another
container.
2. Dynamic YAML file — reusable across all services:
1
2
3
4
5
6
7
8
|
# /opt/traefik/dynamic/middlewares.yml
http:
middlewares:
secHeaders:
headers:
browserXssFilter: true
contentTypeNosniff: true
frameDeny: true
|
Reference it with @file provider:
1
2
|
labels:
- "traefik.http.routers.myapp.middlewares=secHeaders@file"
|
The YAML approach is cleaner for shared middlewares — define once,
use everywhere. The label approach is fine for per-service overrides.
Security headers tell browsers how to behave when rendering your
content. Missing headers are the most common reason for low security
scores on tools like Mozilla Observatory and securityheaders.com.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
# /opt/traefik/dynamic/middlewares.yml
http:
middlewares:
secHeaders:
headers:
# XSS Protection
browserXssFilter: true
contentTypeNosniff: true
# Clickjacking protection
frameDeny: true
customFrameOptionsValue: "SAMEORIGIN"
# HSTS — force HTTPS for 1 year
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: true
# Referrer policy
referrerPolicy: "strict-origin-when-cross-origin"
# Permissions policy — restrict browser features
permissionsPolicy: |
camera=(), microphone=(), geolocation=(),
payment=(), usb=(), magnetometer=(), accelerometer=()
# Content Security Policy
contentSecurityPolicy: >
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' data:;
object-src 'none';
base-uri 'none';
form-action 'self';
frame-ancestors 'self';
upgrade-insecure-requests;
# Strip server fingerprinting headers
customResponseHeaders:
server: ""
x-powered-by: ""
|
What each header does:
| Header |
Effect |
browserXssFilter |
Enables browser’s reflected XSS filter (X-XSS-Protection: 1; mode=block) |
contentTypeNosniff |
Prevents MIME type sniffing (X-Content-Type-Options: nosniff) |
frameDeny |
Blocks all iframe embedding (X-Frame-Options: DENY) |
stsSeconds: 31536000 |
HSTS for 1 year — browser remembers HTTPS-only |
stsPreload: true |
Eligible for browser HSTS preload lists |
referrerPolicy |
Controls Referer header sent with requests |
permissionsPolicy |
Disables unnecessary browser APIs (camera, mic, etc.) |
contentSecurityPolicy |
Whitelists allowed content sources |
customResponseHeaders |
Strips Server and X-Powered-By headers |
Apply to All Routers — Default Middleware#
Repeat this on every router label is tedious. Instead, define a
default middleware per entrypoint:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# traefik.yml static config
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
http:
middlewares:
- secHeaders@file
- rateLimit@file
|
Every request hitting the HTTPS entrypoint automatically gets the
security headers and rate limiting without per-router labels.
CSP Tuning for Your Homelab#
The CSP above is restrictive — it blocks inline scripts and external
resources by default. Most homelab apps need adjustments:
For Grafana:
1
2
3
4
5
6
7
8
9
10
11
|
contentSecurityPolicy: >
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https://*.grafana.net https://*.grafana.com;
font-src 'self' data:;
object-src 'none';
base-uri 'none';
form-action 'self';
frame-ancestors 'self';
connect-src 'self' wss://your-domain.com;
|
For Vaultwarden:
1
2
3
4
5
6
7
8
9
10
11
|
contentSecurityPolicy: >
default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
font-src 'self' data:;
object-src 'none';
base-uri 'none';
form-action 'self';
frame-ancestors 'none';
upgrade-insecure-requests;
|
For services that need WebSockets:
1
2
3
4
5
6
7
8
9
|
contentSecurityPolicy: >
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
font-src 'self' data:;
connect-src 'self' wss:;
frame-ancestors 'self';
upgrade-insecure-requests;
|
Test your CSP with the browser console open — blocked resources show
as console errors with the directive that blocked them.
2. Rate Limiting Middleware — Protect Against Abuse#
Rate limiting prevents a single client from overwhelming your
services. Traefik’s rate limiter uses a token bucket algorithm.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
# /opt/traefik/dynamic/middlewares.yml
http:
middlewares:
rateLimit:
rateLimit:
# 100 requests per second average
average: 100
# Burst up to 200 requests
burst: 200
# Source extraction — use client IP
sourceCriterion:
ipStrategy:
depth: 1
|
Per-service rate limits for specific use cases:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
rateLimitStrict:
rateLimit:
average: 20
burst: 40
sourceCriterion:
requestHeader:
name: "X-Forwarded-For"
# Use the real client IP when behind Cloudflare
rateLimitAuth:
rateLimit:
average: 5
burst: 10
period: 1m # 5 requests per minute, 10 burst
sourceCriterion:
ipStrategy:
depth: 1
rateLimitApi:
rateLimit:
average: 30
burst: 60
sourceCriterion:
requestHost: true # Rate limit per hostname
|
Apply Rate Limits by Service Type#
1
2
3
4
5
6
7
8
9
10
11
|
# For a public-facing service
labels:
- "traefik.http.routers.myapp.middlewares=secHeaders@file,rateLimit@file"
# For an admin panel — strict rate limiting
labels:
- "traefik.http.routers.admin.middlewares=secHeaders@file,rateLimitAuth@file"
# For an API endpoint
labels:
- "traefik.http.routers.api.middlewares=secHeaders@file,rateLimitApi@file"
|
Rate Limit + CrowdSec (From Last Week’s Post)#
Combine rate limiting with CrowdSec for layered protection:
1
2
|
labels:
- "traefik.http.routers.myapp.middlewares=crowdsec-bouncer@file,rateLimit@file,secHeaders@file"
|
Order matters: CrowdSec blocks malicious IPs first (at the
application edge), rate limiting prevents bursts from getting through
to your app, and security headers protect the browser. If CrowdSec
misses a burst, the rate limiter catches it.
3. IP Whitelisting — Admin Panels and Internal Services#
Never expose admin interfaces, dashboards, or management UIs to the
internet. Whitelist them to your home IP or VPN subnet.
1
2
3
4
5
6
7
8
9
10
|
# /opt/traefik/dynamic/middlewares.yml
http:
middlewares:
internalOnly:
ipWhiteList:
sourceRange:
- "10.0.0.0/8"
- "192.168.0.0/16"
- "172.16.0.0/12"
- "YOUR_HOME_PUBLIC_IP/32" # Your static IP if you access remotely
|
Apply to sensitive services:
1
2
3
4
5
6
7
8
9
|
# Proxmox web interface via Traefik
labels:
- "traefik.http.routers.proxmox.rule=Host(`proxmox.example.com`)"
- "traefik.http.routers.proxmox.middlewares=secHeaders@file,internalOnly@file"
# Portainer
labels:
- "traefik.http.routers.portainer.rule=Host(`portainer.example.com`)"
- "traefik.http.routers.portainer.middlewares=secHeaders@file,internalOnly@file"
|
VPN-Aware Whitelisting#
If you use WireGuard or Tailscale for remote access, whitelist the
VPN subnet:
1
2
3
4
5
6
|
vpnOnly:
ipWhiteList:
sourceRange:
- "10.0.0.0/8"
- "100.64.0.0/10" # Tailscale / Tailscale IP range
- "10.100.0.0/16" # WireGuard subnet
|
When combined with Cloudflare Tunnel (which replaces the visitor IP
with Cloudflare IPs), you need the X-Forwarded-For strategy:
1
2
3
4
5
6
7
|
cloudflareInternal:
ipWhiteList:
sourceRange:
- "10.0.0.0/8"
- "192.168.0.0/16"
ipStrategy:
depth: 2 # Cloudflare adds one hop — look one deeper
|
4. Basic Auth Middleware — Staging and Private Services#
Basic authentication adds an HTTP login prompt to any service without
application-level changes. Invaluable for staging environments,
development dashboards, or services that lack their own auth.
1
2
3
4
5
6
7
8
9
|
# /opt/traefik/dynamic/middlewares.yml
http:
middlewares:
stagingAuth:
basicAuth:
realm: "Staging Environment"
users:
- "admin:$2y$10$..." # bcrypt hash
- "reader:$2y$10$..." # read-only user
|
Generate the bcrypt hash:
1
2
3
4
5
6
7
8
9
|
# Install htpasswd if needed
sudo apt install -y apache2-utils
# Generate bcrypt hash (interactive — prompts for password)
htpasswd -nB admin | cut -d: -f2
# Or one-liner
htpasswd -nB admin 2>/dev/null
# Enter password when prompted, copy the hash after the colon
|
With Docker labels (inline users):
1
2
3
4
5
6
7
|
services:
staging:
image: nginx
labels:
- "traefik.http.routers.staging.rule=Host(`staging.example.com`)"
- "traefik.http.routers.staging.middlewares=stagingAuth@docker"
- "traefik.http.middlewares.stagingAuth.basicauth.users=admin:$$2y$$10$$..."
|
Important: In Docker labels, escape $ as $$ — Docker Compose
interprets $ as variable substitution, so a $2y$10$... hash
becomes $$2y$$10$$... in the label.
Multi-User Auth for Teams#
1
2
3
4
|
teamAuth:
basicAuth:
realm: "Team Dashboard"
usersFile: "/etc/traefik/auth/users.txt"
|
1
2
3
4
|
# /etc/traefik/auth/users.txt (one user per line)
admin:$2y$10$abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmno
dev1:$2y$10$bcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnop
ops:$2y$10$cdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopq
|
Mount the file:
1
2
|
volumes:
- /etc/traefik/auth:/etc/traefik/auth:ro
|
5. Redirect Scheme — Force HTTPS#
HTTP→HTTPS redirect should be the first middleware in every chain.
Traefik handles this at the entrypoint level — cleaner than a
middleware.
1
2
3
4
5
6
7
8
9
10
|
# traefik.yml static config — the simplest approach
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true
|
Alternative — middleware approach (for selective redirects):
1
2
3
4
5
6
7
8
|
# /opt/traefik/dynamic/middlewares.yml
http:
middlewares:
redirectHttps:
redirectScheme:
scheme: https
port: 443
permanent: true
|
Apply to routers that should only accept HTTPS:
1
2
3
4
5
6
7
8
9
|
labels:
- "traefik.http.routers.myapp-http.rule=Host(`app.example.com`)"
- "traefik.http.routers.myapp-http.middlewares=redirectHttps@file"
- "traefik.http.routers.myapp-http.entrypoints=web"
- "traefik.http.routers.myapp.rule=Host(`app.example.com`)"
- "traefik.http.routers.myapp.middlewares=secHeaders@file"
- "traefik.http.routers.myapp.entrypoints=websecure"
- "traefik.http.routers.myapp.tls.certresolver=letsencrypt"
|
6. Compression Middleware — Reduce Bandwidth#
Gzip or Brotli compression reduces transfer size by 60-80% for text
content. Traefik handles compression transparently.
1
2
3
4
5
6
7
8
9
10
11
12
|
# /opt/traefik/dynamic/middlewares.yml
http:
middlewares:
compress:
compress:
minContentLength: 256 # Don't compress tiny responses
excludedContentTypes:
- "image/png"
- "image/jpeg"
- "image/webp"
- "video/mp4"
- "application/zip"
|
Add to the default entrypoint middleware chain:
1
2
3
4
5
6
7
8
|
entryPoints:
websecure:
address: ":443"
http:
middlewares:
- compress@file
- secHeaders@file
- rateLimit@file
|
Compression is safe for most content but should be skipped for
already-compressed types (images, videos, archives). The
excludedContentTypes list prevents double-compression.
7. Custom Error Pages Middleware#
Replace Traefik’s generic 404/503 pages with branded or
informational pages.
1
2
3
4
5
6
7
8
9
10
|
# /opt/traefik/dynamic/middlewares.yml
http:
middlewares:
errorPages:
errors:
status:
- "404"
- "500-599"
service: errorPagesService
query: "/{status}.html"
|
Define the error page service:
1
2
3
4
5
6
7
|
# /opt/traefik/dynamic/services.yml
http:
services:
errorPagesService:
loadBalancer:
servers:
- url: "http://host:8083" # Static file server
|
Or use a simple Docker container serving custom HTML:
1
2
3
4
5
6
7
8
9
|
services:
error-pages:
image: tarampampam/error-pages:latest
environment:
- TEMPLATE=app-down
labels:
- "traefik.enable=true"
- "traefik.http.routers.error-pages.rule=Host(`errors.local`)"
- "traefik.http.routers.error-pages.internal=true"
|
8. Chaining Middlewares — Putting It All Together#
The real power of Traefik middlewares is chaining. A single router
can pass through multiple middlewares in sequence.
Default Chain for All Services#
1
2
3
4
5
6
7
8
9
|
# traefik.yml
entryPoints:
websecure:
address: ":443"
http:
middlewares:
- compress@file # 1. Compress response
- secHeaders@file # 2. Add security headers
- rateLimit@file # 3. Rate limit traffic
|
Every service behind this entrypoint gets all three automatically.
Per-Service Overrides#
Add or override middlewares for specific services:
1
2
3
4
5
6
7
8
9
10
11
|
# Public service — everything from default chain, plus nothing extra
labels:
- "traefik.http.routers.public.middlewares="
# Admin service — default chain + IP whitelist
labels:
- "traefik.http.routers.admin.middlewares=internalOnly@file"
# Staging — default chain + basic auth (added before default chain)
labels:
- "traefik.http.routers.staging.middlewares=stagingAuth@file"
|
How Middleware Order Works#
Traefik processes middlewares in the exact order listed:
1
2
|
labels:
- "traefik.http.routers.myapp.middlewares=crowdsec-bouncer@file,rateLimitAuth@file,stagingAuth@file,internalOnly@file"
|
Flow:
- CrowdSec blocks known malicious IPs
- Rate limiter caps requests to 5/min for auth-heavy routes
- Basic auth challenges the user for credentials
- IP whitelist checks if the IP is in the allowed range
- Default entrypoint chain adds compression + security headers
If any middleware rejects the request, subsequent middlewares never
run. The 403 from the IP whitelist is returned directly.
Docker Compose Example — Complete Service with All Middlewares#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
# /opt/services/webapp/docker-compose.yml
services:
webapp:
image: nginx:alpine
volumes:
- ./html:/usr/share/nginx/html:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.webapp.rule=Host(`app.example.com`)"
- "traefik.http.routers.webapp.entrypoints=websecure"
- "traefik.http.routers.webapp.tls=true"
- "traefik.http.routers.webapp.tls.certresolver=letsencrypt"
- "traefik.http.routers.webapp.middlewares=compress@file,secHeaders@file,rateLimit@file"
networks:
- traefik
admin:
image: nginx:alpine
volumes:
- ./admin:/usr/share/nginx/html:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.admin.rule=Host(`admin.example.com`)"
- "traefik.http.routers.admin.entrypoints=websecure"
- "traefik.http.routers.admin.tls=true"
- "traefik.http.routers.admin.tls.certresolver=letsencrypt"
- "traefik.http.routers.admin.middlewares=compress@file,secHeaders@file,rateLimitAuth@file,internalOnly@file"
networks:
- traefik
networks:
traefik:
external: true
|
9. Testing Your Middleware Configuration#
Validate middleware headers and behavior before relying on them.
1
2
3
4
5
6
7
8
9
10
11
12
|
# Check security headers
curl -sI https://app.example.com | grep -iE '^(HTTP|X-|Strict|Content-Security|Referrer|Permissions)'
# Expected output:
# HTTP/2 200
# content-security-policy: default-src 'self'; ...
# strict-transport-security: max-age=31536000; includeSubDomains; preload
# x-content-type-options: nosniff
# x-frame-options: DENY
# x-xss-protection: 1; mode=block
# referrer-policy: strict-origin-when-cross-origin
# permissions-policy: camera=(), microphone=(), ...
|
Test Rate Limiting#
1
2
3
4
5
6
|
# Rapid-fire requests — expect 429 after burst is exhausted
for i in $(seq 1 50); do
curl -s -o /dev/null -w "%{http_code} " http://localhost:8080/
done
# Output: 200 200 ... 200 429 429 429 ...
|
Test IP Whitelist#
1
2
3
|
# From a non-whitelisted IP (use a different machine/proxy)
curl -s -o /dev/null -w "%{http_code}" https://admin.example.com
# Expected: 403
|
Test Basic Auth#
1
2
3
4
5
6
7
8
9
10
11
|
# Without credentials
curl -s -o /dev/null -w "%{http_code}" https://staging.example.com
# Expected: 401
# With correct credentials
curl -s -u admin:password -o /dev/null -w "%{http_code}" https://staging.example.com
# Expected: 200
# With wrong password
curl -s -u admin:wrong -o /dev/null -w "%{http_code}" https://staging.example.com
# Expected: 401
|
Full Security Scan#
1
2
3
4
5
6
7
8
|
# Install Mozilla Observatory CLI
pip install observatory-cli
# Run scan
observatory --host app.example.com
# Or check manually with their web tool:
# https://developer.mozilla.org/en-US/observatory/
|
A properly configured setup should score A or A+ with no missing
headers.
10. Common Problems and Fixes#
HSTS only applies to HTTPS responses. Traefik’s entrypoint-level
redirect happens before the middleware chain. Keep stsSeconds on
the websecure entrypoint only — don’t add it to web.
“My WebSocket connection is broken”#
Rate limiting can interfere with WebSocket upgrade requests:
1
2
3
4
5
|
# Exclude WebSocket routers from rate limiting
labels:
- "traefik.http.routers.websocket.middlewares=secHeaders@file"
- "traefik.http.routers.websocket.rule=Host(`ws.example.com`)"
- "traefik.http.routers.websocket.middlewares=secHeaders@file"
|
“CSP is blocking my dashboard scripts”#
Open the browser console. The error message tells you exactly which
directive and source is blocked. Add the needed source to the
appropriate CSP directive. Most homelab apps need 'unsafe-inline'
and 'unsafe-eval' in script-src.
“Rate limiting is blocking legitimate traffic”#
Increase average and burst values, or switch to per-client source
criterion. Traffic from a reverse proxy (Cloudflare Tunnel) may all
appear from one source IP — use X-Forwarded-For header extraction:
1
2
3
4
5
6
7
|
rateLimit:
rateLimit:
average: 200
burst: 400
sourceCriterion:
requestHeader:
name: "X-Forwarded-For"
|
“Basic auth password hash has $ in it”#
In Docker Compose labels, every $ must be doubled:
1
2
3
4
5
|
# Wrong — Docker Compose eats the $:
"traefik.http.middlewares.auth.basicauth.users=admin:$2y$10$hash..."
# Correct — double the $:
"traefik.http.middlewares.auth.basicauth.users=admin:$$2y$$10$$hash..."
|
Putting It All Together — A Hardened Homelab Proxy#
A complete, hardened Traefik configuration for a homelab:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
# /opt/traefik/traefik.yml
entryPoints:
web:
address: ":80"
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
http:
middlewares:
- compress@file
- secHeaders@file
- rateLimit@file
providers:
docker:
endpoint: "unix:///var/run/docker.sock"
exposedByDefault: false
file:
directory: /etc/traefik/dynamic
watch: true
certificatesResolvers:
letsencrypt:
acme:
email: [email protected]
storage: /letsencrypt/acme.json
httpChallenge:
entryPoint: web
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
# /opt/traefik/dynamic/middlewares.yml
http:
middlewares:
secHeaders:
headers:
browserXssFilter: true
contentTypeNosniff: true
frameDeny: true
stsSeconds: 31536000
stsIncludeSubdomains: true
stsPreload: true
referrerPolicy: "strict-origin-when-cross-origin"
permissionsPolicy: |
camera=(), microphone=(), geolocation=(),
payment=(), usb=(), magnetometer=(), accelerometer=()
contentSecurityPolicy: >
default-src 'self';
script-src 'self' 'unsafe-inline' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
font-src 'self' data:;
object-src 'none';
base-uri 'none';
form-action 'self';
frame-ancestors 'self';
upgrade-insecure-requests;
customResponseHeaders:
server: ""
x-powered-by: ""
rateLimit:
rateLimit:
average: 100
burst: 200
sourceCriterion:
ipStrategy:
depth: 1
compress:
compress:
minContentLength: 256
excludedContentTypes:
- image/png
- image/jpeg
- image/webp
- video/mp4
- application/zip
internalOnly:
ipWhiteList:
sourceRange:
- "10.0.0.0/8"
- "192.168.0.0/16"
- "172.16.0.0/12"
stagingAuth:
basicAuth:
realm: "Staging"
users:
- "admin:$2y$10$hash..."
|
Reference these middlewares in your service labels:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
# Public service — default chain handles headers, rate limit, compression
labels:
- "traefik.http.routers.webapp.rule=Host(`app.example.com`)"
- "traefik.http.routers.webapp.tls=true"
# Admin service — restricted by IP
labels:
- "traefik.http.routers.admin.rule=Host(`admin.example.com`)"
- "traefik.http.routers.admin.tls=true"
- "traefik.http.routers.admin.middlewares=internalOnly@file"
# Staging — requires basic auth
labels:
- "traefik.http.routers.staging.rule=Host(`staging.example.com`)"
- "traefik.http.routers.staging.tls=true"
- "traefik.http.routers.staging.middlewares=stagingAuth@file"
|
Summary#
Traefik middlewares are the difference between a reverse proxy that
merely forwards traffic and one that actively defends your services.
The five middlewares in this guide cover the essential security
layers:
-
Security headers (secHeaders) — CSP, HSTS, XSS protection,
frame denial, server header stripping. The foundation of an A+
security score.
-
Rate limiting (rateLimit) — Token-bucket algorithm prevents
any single client from overwhelming your services. Tune the
average/burst per endpoint.
-
IP whitelisting (internalOnly) — Keep admin panels, Proxmox,
Portainer, and dashboards accessible only from your trusted
subnets and VPN ranges.
-
Basic auth (stagingAuth) — Add HTTP authentication to any
service without modifying the application. Essential for staging
and dev environments.
-
Compression (compress) — Brotli/gzip reduces bandwidth by
60-80% for text content. Exclude already-compressed types.
Chain them in the right order, apply defaults at the entrypoint
level, and override per-service when needed. The result is a reverse
proxy that scores A+ on security audits, protects against abuse, and
keeps internal services invisible to the public internet — all
without touching a single application configuration.