# Simple Traefik Wildcard Templated Setup

> Source: <https://gist.github.com/potat-dev/6b3d3db03dc4172ecd03af9e6a3fa4d9>
> Published: 2026-05-22 22:16:14+00:00

/root/traefik
├── .env
├── compose.yml
├── services
│ ├── common
│ │ ├── headers.yml
│ │ └── middlewares.yml
│ ├── photos.yml
│ ├── server.yml
│ └── vault.yml
├── template.yml
└── traefik.yml
Environment variables
# Primary domain
DOMAIN=potat.dev
# Email for LE expiration notices
ACME_EMAIL=user@example.com
# Cloudflare API token
CF_DNS_API_TOKEN=cfut_abc123
# Timeouts for strict ISP CF_HTTP_TIMEOUT=120 CF_POLLING_INTERVAL=10
# CF_HTTP_TIMEOUT=120
# CF_POLLING_INTERVAL=10
# CF_PROPAGATION_TIMEOUT=300
Token must have the following permissions:
- Zone / Zone / Read
- Zone / DNS / Edit
If you are getting failed to create TXT record
or context deadline exceeded
error - uncomment timeouts section
Source: https://go-acme.github.io/lego/dns/cloudflare
Docker Compose configuration
services:
traefik:
image: traefik:v3.7
container_name: traefik
restart: unless-stopped
security_opt:
- no-new-privileges:true
env_file:
- .env
command:
- "--entryPoints.websecure.http.tls.domains[0].main=${DOMAIN}"
- "--entryPoints.websecure.http.tls.domains[0].sans=*.${DOMAIN}"
ports:
- "80:80"
- "443:443"
volumes:
- ./traefik.yml:/etc/traefik/traefik.yml:ro
- ./services:/etc/traefik/services:ro
- certs:/certs
volumes:
certs:
name: traefik_certs
Domain settings are configured here because static configuration does not support environment variable substitution, whereas this method does. Static configuration is being merged together from the traefik.yml
file and these parameters
If you need an additional domain, add DOMAIN_SECONDARY
to the .env
file and add two more lines using domains[1]
Warning
Traefik v2 allowed you to write entryPoints
as entrypoints
, but v3 no longer does
Static configuration, also known as Install configuration
global:
checkNewVersion: true
sendAnonymousUsage: false
api:
dashboard: true
insecure: false
log:
level: INFO
entryPoints:
web:
address: ":80"
asDefault: false
http:
redirections:
entryPoint:
to: websecure
scheme: https
websecure:
address: ":443"
asDefault: true
http:
tls:
certResolver: cloudflare
providers:
file:
directory: "/etc/traefik/services"
watch: true
certificatesResolvers:
cloudflare:
acme:
storage: "/certs/acme.json"
dnsChallenge:
provider: cloudflare
propagation:
# disablePropagationCheck: true
delayBeforeChecks: 10s
resolvers:
- "1.1.1.1:53"
- "8.8.8.8:53"
- "1.0.0.1:53"
- "8.8.4.4:53"
serversTransport:
insecureSkipVerify: true
insecureSkipVerify
allows using insecure URLs withhttps://
protocol and missing SSL certs (e.q. Proxmox)disablePropagationCheck
skips the DNS propagation checks before notifying ACME that the DNS challenge is readywebsecure.asDefault
allows you to avoid specifying an entry point for each route
Service configuration template
{{ $app := "app" }}
{{ $url := "http://10.0.10.XX:80" }}
http:
routers:
{{ $app }}-router:
rule: Host(`{{ $app }}.{{ env "DOMAIN" }}`)
service: "{{ $app }}-service"
middlewares:
- "secure-headers"
# - "{{ $app }}-middleware"
services:
{{ $app }}-service:
loadBalancer:
servers:
- url: "{{ $url }}"
passHostHeader: true
# middlewares:
# {{ $app }}-middleware:
# headers:
# customRequestHeaders:
# X-Custom-Header: "Example"
How to use:
- Run
cp template.yml services/service.yml
to deploy new service - Edit
$app
and$url
variables -$app
will be your subdomain - Custom middleware sections can be removed
How this works: Traefik uses Go text/template engine to parse expressions in dynamic configuration
Example configuration for Proxmox
{{ $app := "server" }}
{{ $url := "https://10.0.10.42:8006" }}
http:
routers:
{{ $app }}-router:
rule: Host(`{{ $app }}.{{ env "DOMAIN" }}`)
service: "{{ $app }}-service"
middlewares:
- "secure-headers"
services:
{{ $app }}-service:
loadBalancer:
servers:
- url: "{{ $url }}"
passHostHeader: true
Common secure headers configuration
http:
middlewares:
secure-headers:
headers:
stsSeconds: 31536000
stsIncludeSubdomains: true
forceSTSHeader: true
contentTypeNosniff: true
browserXssFilter: true
customFrameOptionsValue: SAMEORIGIN
