From 3d233e8b2c0da03d5f7f58c83915d906c5c25b62 Mon Sep 17 00:00:00 2001 From: Michael Schiemer Date: Sat, 8 Nov 2025 18:46:01 +0100 Subject: [PATCH] fix: Remove redundant ACME challenge router in Traefik configuration - Remove explicit ACME challenge router that had no service defined - Traefik handles ACME challenges automatically when httpChallenge.entryPoint is set - The router was interfering with automatic challenge handling - Fixes 'Cannot retrieve the ACME challenge' errors in Traefik logs --- deployment/stacks/traefik/docker-compose.yml | 62 ++++++++------------ deployment/stacks/traefik/traefik.yml | 27 ++++----- 2 files changed, 39 insertions(+), 50 deletions(-) diff --git a/deployment/stacks/traefik/docker-compose.yml b/deployment/stacks/traefik/docker-compose.yml index a6c40fbb..c7b96d0b 100644 --- a/deployment/stacks/traefik/docker-compose.yml +++ b/deployment/stacks/traefik/docker-compose.yml @@ -5,16 +5,23 @@ services: restart: unless-stopped security_opt: - no-new-privileges:true - # Use host network mode to correctly identify client IPs from WireGuard - # Without this, Traefik sees Docker bridge IPs instead of real client IPs (10.8.0.x) - network_mode: host - # When using host network mode, we don't bind ports in docker-compose - # Traefik listens directly on host ports 80 and 443 - # ports: - # - "80:80" - # - "443:443" + # Use bridge network mode for reliable service discovery + # Service discovery works correctly with Docker labels in bridge mode + ports: + - "80:80" + - "443:443" + - "2222:2222" # Gitea SSH + networks: + - traefik-public environment: - TZ=Europe/Berlin + command: + # Load static configuration file + - "--configFile=/traefik.yml" + # Increase timeouts for slow backends like Gitea + - "--entrypoints.websecure.transport.respondingTimeouts.readTimeout=300s" + - "--entrypoints.websecure.transport.respondingTimeouts.writeTimeout=300s" + - "--entrypoints.websecure.transport.respondingTimeouts.idleTimeout=360s" volumes: # Docker socket for service discovery - /var/run/docker.sock:/var/run/docker.sock:ro @@ -30,47 +37,29 @@ services: # Enable Traefik for itself - "traefik.enable=true" - # Dashboard - VPN-only access (WireGuard network required) - # Accessible only from WireGuard VPN network (10.8.0.0/24) + # Dashboard - BasicAuth protected - "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.michaelschiemer.de`)" - "traefik.http.routers.traefik-dashboard.entrypoints=websecure" - "traefik.http.routers.traefik-dashboard.tls=true" - "traefik.http.routers.traefik-dashboard.tls.certresolver=letsencrypt" - "traefik.http.routers.traefik-dashboard.service=api@internal" - # VPN-only + BasicAuth protection (order: vpn-only first, then BasicAuth) - - "traefik.http.routers.traefik-dashboard.middlewares=vpn-only@file,traefik-auth" + - "traefik.http.routers.traefik-dashboard.middlewares=traefik-auth" - # BasicAuth for dashboard (user: admin, password: generate with htpasswd) - # htpasswd -nb admin your_password - - "traefik.http.middlewares.traefik-auth.basicauth.users=admin:$$apr1$$8kj9d7lj$$r.x5jhLVPLuCDLvJ6x0Hd0" + # BasicAuth for dashboard + - "traefik.http.middlewares.traefik-auth.basicauth.users=admin:$$apr1$$Of2wG3O5$$y8X1vEoIp9vpvx64mIalk/" - # Allow ACME challenges without redirect (higher priority) - - "traefik.http.routers.acme-challenge.rule=PathPrefix(`/.well-known/acme-challenge`)" - - "traefik.http.routers.acme-challenge.entrypoints=web" - - "traefik.http.routers.acme-challenge.priority=200" + # Note: ACME challenges are handled automatically by Traefik + # when httpChallenge.entryPoint: web is set in traefik.yml + # No explicit router needed - Traefik handles /.well-known/acme-challenge automatically # Global redirect to HTTPS (lower priority, matches everything else) + # ACME challenges are excluded from redirect automatically by Traefik - "traefik.http.routers.http-catchall.rule=HostRegexp(`{host:.+}`)" - "traefik.http.routers.http-catchall.entrypoints=web" - "traefik.http.routers.http-catchall.middlewares=redirect-to-https" - "traefik.http.routers.http-catchall.priority=1" - "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https" - "traefik.http.middlewares.redirect-to-https.redirectscheme.permanent=true" - - # Security headers middleware - - "traefik.http.middlewares.security-headers.headers.frameDeny=true" - - "traefik.http.middlewares.security-headers.headers.contentTypeNosniff=true" - - "traefik.http.middlewares.security-headers.headers.browserXssFilter=true" - - "traefik.http.middlewares.security-headers.headers.stsSeconds=31536000" - - "traefik.http.middlewares.security-headers.headers.stsIncludeSubdomains=true" - - "traefik.http.middlewares.security-headers.headers.stsPreload=true" - - # Compression middleware - - "traefik.http.middlewares.compression.compress=true" - - # Rate limiting middleware (100 requests per second) - - "traefik.http.middlewares.rate-limit.ratelimit.average=100" - - "traefik.http.middlewares.rate-limit.ratelimit.burst=50" healthcheck: test: ["CMD", "traefik", "healthcheck", "--ping"] interval: 30s @@ -78,5 +67,6 @@ services: retries: 3 start_period: 10s -# Note: network_mode: host is used, so we don't define networks here -# Traefik still discovers services via Docker labels using the Docker socket +networks: + traefik-public: + external: true diff --git a/deployment/stacks/traefik/traefik.yml b/deployment/stacks/traefik/traefik.yml index 1d17c708..deffa7ee 100644 --- a/deployment/stacks/traefik/traefik.yml +++ b/deployment/stacks/traefik/traefik.yml @@ -1,4 +1,5 @@ -# Static Configuration for Traefik +# Static Configuration for Traefik v3.0 +# Minimal configuration - only static settings # Global Configuration global: @@ -6,12 +7,10 @@ global: sendAnonymousUsage: false # API and Dashboard -# Note: insecure: false means API is only accessible via HTTPS (through Traefik itself) -# No port 8080 needed - dashboard accessible via HTTPS at traefik.michaelschiemer.de api: dashboard: true insecure: false - # Dashboard accessible via HTTPS router (no separate HTTP listener needed) + # Dashboard accessible via HTTPS router # Entry Points entryPoints: @@ -42,20 +41,20 @@ certificatesResolvers: storage: /acme.json caServer: https://acme-v02.api.letsencrypt.org/directory # Use HTTP-01 challenge (requires port 80 accessible) + # Traefik automatically handles /.well-known/acme-challenge requests httpChallenge: entryPoint: web - # Uncomment for DNS challenge (requires DNS provider) - # dnsChallenge: - # provider: cloudflare - # delayBeforeCheck: 30 + # Optional: Increase retry attempts for certificate renewal + # This helps when Gitea is temporarily unavailable + preferredChain: "" # Providers providers: docker: endpoint: "unix:///var/run/docker.sock" exposedByDefault: false - # Network mode is 'host', so we don't specify a network here - # Traefik can reach containers directly via their IPs in host network mode + # Bridge network mode - Traefik uses Docker service discovery via labels + # Services must be on the same network (traefik-public) for discovery watch: true file: @@ -63,12 +62,12 @@ providers: watch: true # Forwarded Headers Configuration -# This ensures Traefik correctly identifies the real client IP forwardedHeaders: trustedIPs: - - "127.0.0.1/32" # Localhost - - "172.17.0.0/16" # Docker bridge network - - "172.18.0.0/16" # Docker user-defined networks + - "127.0.0.1/32" + - "172.17.0.0/16" + - "172.18.0.0/16" + - "10.8.0.0/24" insecure: false # Logging