Set up Traefik as a reverse proxy with Docker Compose for automatic SSL certificate management, service discovery, and load balancing across multiple backend services.
Prerequisites
- Root or sudo access
- Docker and Docker Compose installed
- Domain name pointing to your server
- Ports 80 and 443 open
What this solves
Traefik is a modern reverse proxy that automatically handles SSL certificates, service discovery, and load balancing. It eliminates manual certificate management by integrating with Let's Encrypt and can route traffic to multiple backend services based on domain names or paths.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest versions of Docker and dependencies.
sudo apt update && sudo apt upgrade -y
Install Docker and Docker Compose
Traefik runs as a Docker container, so you need Docker and Docker Compose installed on your system.
sudo apt install -y docker.io docker-compose-v2
sudo systemctl enable --now docker
sudo usermod -aG docker $USER
Create Traefik directory structure
Set up the directory structure for Traefik configuration files, SSL certificates, and logs.
mkdir -p ~/traefik/{config,data}
cd ~/traefik
touch data/acme.json
chmod 600 data/acme.json
Create Docker network
Create a dedicated network for Traefik to communicate with backend services.
docker network create traefik
Create Traefik static configuration
The static configuration defines how Traefik starts up, including API settings, certificate resolvers, and entry points.
global:
checkNewVersion: false
sendAnonymousUsage: false
serversTransport:
insecureSkipVerify: true
api:
dashboard: true
debug: 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
network: traefik
file:
filename: /config/dynamic.yml
watch: true
certificatesResolvers:
letsencrypt:
acme:
tlsChallenge: {}
email: admin@example.com
storage: /data/acme.json
caServer: https://acme-v02.api.letsencrypt.org/directory
Create dynamic configuration
Dynamic configuration handles middleware, routers, and services that can change without restarting Traefik.
http:
middlewares:
default-headers:
headers:
frameDeny: true
sslRedirect: true
browserXssFilter: true
contentTypeNosniff: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
customFrameOptionsValue: SAMEORIGIN
customRequestHeaders:
X-Forwarded-Proto: https
secure-headers:
headers:
accessControlAllowMethods:
- GET
- OPTIONS
- PUT
accessControlMaxAge: 100
hostsProxyHeaders:
- "X-Forwarded-Host"
referrerPolicy: "same-origin"
auth:
basicAuth:
users:
- "admin:$2y$10$2b2cu2Fw1ZfqmVnBKgxNj.e7wjHyBhgDw4Y2H9GKkJvUn2k4y7BL2"
tls:
options:
default:
sslVersions:
- "TLSv1.2"
- "TLSv1.3"
cipherSuites:
- "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
- "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305"
- "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
Create Docker Compose file
This compose file defines the Traefik service with proper volume mounts and network configuration.
version: '3.7'
services:
traefik:
image: traefik:v3.0
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- traefik
ports:
- "80:80"
- "443:443"
environment:
- CF_API_EMAIL=admin@example.com
- CF_DNS_API_TOKEN=your_cloudflare_token_here
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data/traefik.yml:/traefik.yml:ro
- ./data/acme.json:/data/acme.json
- ./config:/config:ro
labels:
- "traefik.enable=true"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.rule=Host(traefik.example.com)"
- "traefik.http.routers.traefik.tls=true"
- "traefik.http.routers.traefik.tls.certresolver=letsencrypt"
- "traefik.http.middlewares.traefik-auth.basicauth.users=admin:$$2y$$10$$2b2cu2Fw1ZfqmVnBKgxNj.e7wjHyBhgDw4Y2H9GKkJvUn2k4y7BL2"
- "traefik.http.routers.traefik.middlewares=traefik-auth"
- "traefik.http.routers.traefik.service=api@internal"
networks:
traefik:
external: true
Configure firewall rules
Open the necessary ports for HTTP and HTTPS traffic in your system firewall.
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload
Start Traefik
Launch Traefik using Docker Compose and verify it starts successfully.
cd ~/traefik
docker compose up -d
docker compose logs -f traefik
Deploy a test service
Create a simple test application to verify Traefik routing and SSL certificate generation work correctly.
version: '3.7'
services:
whoami:
image: traefik/whoami
container_name: whoami
restart: unless-stopped
networks:
- traefik
labels:
- "traefik.enable=true"
- "traefik.http.routers.whoami.entrypoints=websecure"
- "traefik.http.routers.whoami.rule=Host(test.example.com)"
- "traefik.http.routers.whoami.tls=true"
- "traefik.http.routers.whoami.tls.certresolver=letsencrypt"
- "traefik.http.middlewares.whoami.headers.customrequestheaders.X-Forwarded-Proto=https"
- "traefik.http.routers.whoami.middlewares=whoami"
networks:
traefik:
external: true
docker compose -f test-app.yml up -d
Configure advanced routing and load balancing
Set up path-based routing
Configure Traefik to route different paths to different services, useful for microservices architectures.
http:
routers:
api-router:
rule: "Host(app.example.com) && PathPrefix(/api)"
service: api-service
middlewares:
- strip-api-prefix
- secure-headers
tls:
certResolver: letsencrypt
web-router:
rule: "Host(app.example.com) && PathPrefix(/)"
service: web-service
priority: 1
middlewares:
- secure-headers
tls:
certResolver: letsencrypt
middlewares:
strip-api-prefix:
stripPrefix:
prefixes:
- "/api"
services:
api-service:
loadBalancer:
servers:
- url: "http://api-container:3000"
healthCheck:
path: "/health"
interval: "30s"
timeout: "5s"
web-service:
loadBalancer:
servers:
- url: "http://web-container:8080"
- url: "http://web-container-2:8080"
sticky:
cookie:
name: "traefik-session"
secure: true
httpOnly: true
Configure rate limiting middleware
Add rate limiting to protect your services from abuse and ensure fair resource usage.
http:
middlewares:
rate-limit:
rateLimit:
burst: 100
average: 50
period: "1m"
sourceCriterion:
ipStrategy:
depth: 1
api-rate-limit:
rateLimit:
burst: 20
average: 10
period: "1m"
compress:
compress:
excludedContentTypes:
- "text/event-stream"
cors:
headers:
accessControlAllowCredentials: true
accessControlAllowHeaders:
- "*"
accessControlAllowMethods:
- "GET"
- "POST"
- "PUT"
- "DELETE"
- "OPTIONS"
accessControlAllowOriginList:
- "https://example.com"
- "https://app.example.com"
accessControlMaxAge: 86400
Set up monitoring and observability
Enable metrics collection
Configure Traefik to expose Prometheus metrics for monitoring and alerting.
metrics:
prometheus:
addEntryPointsLabels: true
addServicesLabels: true
addRoutersLabels: true
buckets:
- 0.1
- 0.3
- 1.2
- 5.0
tracing:
jaeger:
samplingParam: 1.0
samplingType: const
localAgentHostPort: "jaeger:6831"
log:
level: INFO
filePath: "/data/traefik.log"
format: json
accessLog:
filePath: "/data/access.log"
format: json
bufferingSize: 100
filters:
statusCodes:
- "400-499"
- "500-599"
retryAttempts: true
minDuration: "10ms"
Add log rotation
Set up logrotate to manage Traefik log files and prevent disk space issues.
sudo tee /etc/logrotate.d/traefik > /dev/null << 'EOF'
/home/$USER/traefik/data/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
copytruncate
su $USER $USER
}
EOF
Verify your setup
Check that Traefik is running correctly and generating SSL certificates automatically.
docker compose ps
docker compose logs traefik | grep -i certificate
curl -I https://test.example.com
openssl s_client -connect test.example.com:443 -servername test.example.com < /dev/null 2>/dev/null | openssl x509 -noout -issuer -dates
Visit your Traefik dashboard at https://traefik.example.com and verify you can see your services listed. Check that SSL certificates are valid and automatically renewed.
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Certificate generation fails | Domain not pointing to server | Verify DNS records point to your server's public IP |
| Dashboard returns 404 | Wrong host rule or missing labels | Check docker-compose.yml labels match your domain |
| Backend service unreachable | Not on traefik network | Add service to traefik network: docker network connect traefik container-name |
| Rate limit not working | Client behind proxy | Configure ipStrategy depth to match proxy layers |
| Logs show permission denied | Wrong file ownership | chown -R $USER:$USER ~/traefik/data |
| SSL redirect loop | Conflicting redirect rules | Remove duplicate redirections from dynamic config |
Next steps
- Install and configure Podman for rootless containers - Alternative to Docker with enhanced security
- Set up Prometheus and Grafana monitoring stack with Docker - Monitor Traefik metrics and performance
- Configure Traefik with Consul service discovery - Dynamic service registration for large deployments
- Secure Docker containers with Traefik and Authelia - Add authentication and authorization layers
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Global variables
TRAEFIK_USER="traefik"
TRAEFIK_DIR="/opt/traefik"
EMAIL=""
DOMAIN=""
# Usage function
usage() {
cat << EOF
Usage: $0 -e EMAIL [-d DOMAIN]
Options:
-e EMAIL Email address for Let's Encrypt notifications (required)
-d DOMAIN Dashboard domain name (optional, defaults to no dashboard access)
-h Show this help message
Example:
$0 -e admin@example.com -d traefik.example.com
EOF
exit 1
}
# Logging functions
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warning() { echo -e "${YELLOW}[WARNING]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Cleanup function for rollback
cleanup() {
local exit_code=$?
if [ $exit_code -ne 0 ]; then
log_error "Installation failed. Cleaning up..."
docker-compose -f "${TRAEFIK_DIR}/docker-compose.yml" down 2>/dev/null || true
docker network rm traefik 2>/dev/null || true
systemctl stop docker 2>/dev/null || true
userdel -r "$TRAEFIK_USER" 2>/dev/null || true
rm -rf "$TRAEFIK_DIR" 2>/dev/null || true
fi
exit $exit_code
}
trap cleanup ERR
# Parse command line arguments
while getopts "e:d:h" opt; do
case $opt in
e) EMAIL="$OPTARG" ;;
d) DOMAIN="$OPTARG" ;;
h) usage ;;
*) usage ;;
esac
done
# Validate required arguments
if [[ -z "$EMAIL" ]]; then
log_error "Email address is required"
usage
fi
# Validate email format
if [[ ! "$EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
log_error "Invalid email format"
exit 1
fi
# Check if running as root
if [[ $EUID -eq 0 ]]; then
log_error "This script should not be run as root"
exit 1
fi
# Check sudo access
if ! sudo -n true 2>/dev/null; then
log_error "This script requires sudo access"
exit 1
fi
# Detect distribution
echo "[1/10] Detecting Linux distribution..."
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update && apt upgrade -y"
PKG_INSTALL="apt install -y"
DOCKER_PKG="docker.io docker-compose"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
DOCKER_PKG="docker docker-compose"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
DOCKER_PKG="docker docker-compose"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
log_success "Detected $PRETTY_NAME"
else
log_error "Cannot detect Linux distribution"
exit 1
fi
# Update system packages
echo "[2/10] Updating system packages..."
sudo $PKG_UPDATE
log_success "System updated"
# Install Docker and Docker Compose
echo "[3/10] Installing Docker and Docker Compose..."
sudo $PKG_INSTALL $DOCKER_PKG
sudo systemctl enable --now docker
log_success "Docker installed and started"
# Create traefik user
echo "[4/10] Creating Traefik user..."
sudo useradd -r -s /bin/false -d "$TRAEFIK_DIR" "$TRAEFIK_USER" 2>/dev/null || true
sudo usermod -aG docker "$TRAEFIK_USER"
log_success "Traefik user created"
# Create directory structure
echo "[5/10] Creating directory structure..."
sudo mkdir -p "${TRAEFIK_DIR}"/{config,data}
sudo touch "${TRAEFIK_DIR}/data/acme.json"
sudo chmod 600 "${TRAEFIK_DIR}/data/acme.json"
sudo chown -R "$TRAEFIK_USER:$TRAEFIK_USER" "$TRAEFIK_DIR"
log_success "Directory structure created"
# Create Docker network
echo "[6/10] Creating Docker network..."
sudo -u "$TRAEFIK_USER" docker network create traefik 2>/dev/null || true
log_success "Docker network created"
# Create static configuration
echo "[7/10] Creating Traefik static configuration..."
sudo tee "${TRAEFIK_DIR}/config/traefik.yml" > /dev/null << EOF
global:
checkNewVersion: false
sendAnonymousUsage: false
serversTransport:
insecureSkipVerify: true
api:
dashboard: true
debug: false
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
network: traefik
file:
filename: /config/dynamic.yml
watch: true
certificatesResolvers:
letsencrypt:
acme:
tlsChallenge: {}
email: ${EMAIL}
storage: /data/acme.json
caServer: https://acme-v02.api.letsencrypt.org/directory
EOF
log_success "Static configuration created"
# Create dynamic configuration
echo "[8/10] Creating Traefik dynamic configuration..."
DASHBOARD_CONFIG=""
if [[ -n "$DOMAIN" ]]; then
DASHBOARD_CONFIG="
api:
rule: \"Host(\\\`${DOMAIN}\\\`)\"
entryPoints:
- websecure
middlewares:
- auth
service: api@internal
tls:
certResolver: letsencrypt"
fi
sudo tee "${TRAEFIK_DIR}/config/dynamic.yml" > /dev/null << EOF
http:
middlewares:
default-headers:
headers:
frameDeny: true
sslRedirect: true
browserXssFilter: true
contentTypeNosniff: true
forceSTSHeader: true
stsIncludeSubdomains: true
stsPreload: true
stsSeconds: 31536000
customFrameOptionsValue: SAMEORIGIN
customRequestHeaders:
X-Forwarded-Proto: https
secure-headers:
headers:
accessControlAllowMethods:
- GET
- OPTIONS
- PUT
accessControlMaxAge: 100
hostsProxyHeaders:
- "X-Forwarded-Host"
referrerPolicy: "same-origin"
auth:
basicAuth:
users:
- "admin:\$2y\$10\$2b2cu2Fw1ZfqmVnBKgxNj.e7wjHyBhgDw4Y2H9GKkJvUn2k4y7BL2"
routers:${DASHBOARD_CONFIG}
tls:
options:
default:
sslVersions:
- "TLSv1.2"
- "TLSv1.3"
cipherSuites:
- "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
- "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305"
- "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
EOF
log_success "Dynamic configuration created"
# Create Docker Compose file
echo "[9/10] Creating Docker Compose file..."
sudo tee "${TRAEFIK_DIR}/docker-compose.yml" > /dev/null << EOF
version: '3.8'
services:
traefik:
image: traefik:v3.0
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
networks:
- traefik
ports:
- "80:80"
- "443:443"
volumes:
- /etc/localtime:/etc/localtime:ro
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./data:/data
- ./config:/config:ro
environment:
- TRAEFIK_LOG_LEVEL=INFO
command:
- --configfile=/config/traefik.yml
labels:
- "traefik.enable=true"
- "traefik.docker.network=traefik"
networks:
traefik:
external: true
EOF
# Set proper ownership
sudo chown -R "$TRAEFIK_USER:$TRAEFIK_USER" "$TRAEFIK_DIR"
sudo chmod 644 "${TRAEFIK_DIR}/config"/*.yml
sudo chmod 644 "${TRAEFIK_DIR}/docker-compose.yml"
log_success "Docker Compose file created"
# Start Traefik
echo "[10/10] Starting Traefik..."
cd "$TRAEFIK_DIR"
sudo -u "$TRAEFIK_USER" docker-compose up -d
log_success "Traefik started successfully"
# Verification
echo ""
log_info "Performing verification checks..."
sleep 5
if sudo -u "$TRAEFIK_USER" docker-compose ps | grep -q "Up"; then
log_success "✓ Traefik container is running"
else
log_error "✗ Traefik container is not running"
exit 1
fi
if docker network ls | grep -q traefik; then
log_success "✓ Traefik network exists"
else
log_error "✗ Traefik network not found"
exit 1
fi
if [[ -f "${TRAEFIK_DIR}/data/acme.json" ]] && [[ $(stat -c %a "${TRAEFIK_DIR}/data/acme.json") == "600" ]]; then
log_success "✓ ACME file has correct permissions"
else
log_error "✗ ACME file permissions incorrect"
exit 1
fi
echo ""
log_success "Traefik installation completed successfully!"
echo ""
log_info "Next steps:"
echo "1. Configure your firewall to allow ports 80 and 443"
echo "2. Point your domain DNS to this server's IP address"
if [[ -n "$DOMAIN" ]]; then
echo "3. Access the dashboard at https://${DOMAIN} (admin/admin123)"
echo " Change the password by generating a new hash with: htpasswd -nb admin newpassword"
fi
echo "4. Configure your applications with Traefik labels"
echo "5. Check logs with: docker-compose -f ${TRAEFIK_DIR}/docker-compose.yml logs -f"
Review the script before running. Execute with: bash install.sh