Let's Encrypt DNS Challenge with Traefik and AWS Route 53

So, you're self-hosting awesome apps like Jellyfin, Home Assistant, or your personal blog with Docker. You want that sweet, sweet HTTPS padlock for secure connections, and Let's Encrypt is the obvious choice for free SSL certs. Awesome! You set up your reverse proxy (maybe Traefik, because it's slick!), point it to your app, and tell it to get a certificate... only to hit a wall. Why? Meet the home networker's nemesis: ISPs blocking incoming port 80. The Standard Way (and the Wall) Let's Encrypt's default validation method, HTTP-01, is simple: their servers try to access a special file on your server over standard HTTP (port 80) to prove you control the domain. Let's Encrypt -> Your Public IP:80 -> Does challenge file exist? -> OK! Cert Issued! But if your ISP blocks incoming connections on port 80 (super common on residential plans!), Let's Encrypt's request never reaches your server. Let's Encrypt -> Your Public IP:80 -> [ISP BLOCK] -> Challenge Failed! No Cert! :( Frustrating, right? Your quest for HTTPS seems doomed... or is it? The DNS-01 Challenge: A Different Kind of Proof Enter the DNS-01 challenge – Let's Encrypt's clever workaround. Instead of checking a file via HTTP, it asks you to prove domain ownership by creating a specific DNS TXT record with a unique value. Let's Encrypt -> Checks Your Domain's DNS Records -> Does specific TXT record exist? -> OK! Cert Issued! Since this validation happens entirely via DNS lookups (usually port 53, which is almost never blocked inbound for lookups), it completely bypasses the port 80 problem! The Magic Combo: Traefik + AWS Route 53 Okay, manually creating DNS records every ~90 days sounds tedious. This is where the dynamic duo comes in: Traefik: Our smart reverse proxy knows how to talk the ACME protocol and request certificates using the DNS-01 method. AWS Route 53: A robust, API-driven DNS service. You configure Traefik with AWS API credentials (using an IAM user with just enough permission to modify Route 53 records – security first!). When it's time to issue or renew a cert, Traefik tells Route 53 (via the API) to create the temporary TXT record Let's Encrypt needs. Once Let's Encrypt verifies it, Traefik tells Route 53 to clean it up. Chef's kiss! The Key Configuration (Traefik + Docker Compose): In your Traefik service definition within docker-compose.yml, the magic lies in the command arguments for your certificate resolver: # Snippet from docker-compose.yml command section for Traefik command: # ... other args ... # Let's Encrypt Resolver Config - "--certificatesresolvers.myresolver.acme.email=your-email@example.com" - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json" # --- THE IMPORTANT PART --- - "--certificatesresolvers.myresolver.acme.dnschallenge=true" # Enable DNS Challenge - "--certificatesresolvers.myresolver.acme.dnschallenge.provider=route53" # Tell it to use AWS Route 53 # ... other args ... Complete example config, # docker-compose.yml services: # Traefik Service (Reverse Proxy & SSL) traefik: image: "traefik:v2.11" # Pin to a specific stable version container_name: "traefik-proxy" # Descriptive container name command: # Enable Docker provider & disable exposing by default - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" # Define HTTP (80) and HTTPS (443) entry points - "--entrypoints.web.address=:80" - "--entrypoints.websecure.address=:443" # Enable Traefik API/Dashboard (for monitoring) - "--api.dashboard=true" # Configure Let's Encrypt Resolver (named 'myresolver') - "--certificatesresolvers.myresolver.acme.email=your-email@example.com" # !! REPLACE THIS with your email !! - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json" # Use DNS-01 challenge with Route53 provider - "--certificatesresolvers.myresolver.acme.dnschallenge=true" - "--certificatesresolvers.myresolver.acme.dnschallenge.provider=route53" # Global Redirect: HTTP -> HTTPS - "--entrypoints.web.http.redirections.entrypoint.to=websecure" - "--entrypoints.web.http.redirections.entrypoint.scheme=https" ports: # Expose port 80 for HTTP->HTTPS redirect (optional if ISP blocks) - "80:80" # Expose port 443 for HTTPS access (ESSENTIAL) - "443:443" volumes: # Mount Docker socket to detect container events - "/var/run/docker.sock:/var/run/docker.sock:ro" # Persist Let's Encrypt certificates - "./letsencrypt:/letsencrypt" networks: - proxy # Connect to our custom network # Environment variables for AWS credentials (will use .env file) environment: - AWS_ACCESS_KEY_ID - AWS_SECRET_ACCESS_KEY # - AWS_REGION=us-east-1 # Optional: Specify if needed # - AWS_HOSTED_ZONE_ID=YOUR_HOSTED_ZONE_ID # Optional labels: # Enable Tr

May 6, 2025 - 01:29
 0
Let's Encrypt DNS Challenge with Traefik and AWS Route 53

So, you're self-hosting awesome apps like Jellyfin, Home Assistant, or your personal blog with Docker. You want that sweet, sweet HTTPS padlock for secure connections, and Let's Encrypt is the obvious choice for free SSL certs. Awesome!

You set up your reverse proxy (maybe Traefik, because it's slick!), point it to your app, and tell it to get a certificate... only to hit a wall. Why? Meet the home networker's nemesis: ISPs blocking incoming port 80.

The Standard Way (and the Wall)

Let's Encrypt's default validation method, HTTP-01, is simple: their servers try to access a special file on your server over standard HTTP (port 80) to prove you control the domain.

Let's Encrypt -> Your Public IP:80 -> Does challenge file exist? -> OK! Cert Issued!

But if your ISP blocks incoming connections on port 80 (super common on residential plans!), Let's Encrypt's request never reaches your server.

Let's Encrypt -> Your Public IP:80 -> [ISP BLOCK] -> Challenge Failed! No Cert! :(

Frustrating, right? Your quest for HTTPS seems doomed... or is it?

The DNS-01 Challenge: A Different Kind of Proof

Enter the DNS-01 challenge – Let's Encrypt's clever workaround. Instead of checking a file via HTTP, it asks you to prove domain ownership by creating a specific DNS TXT record with a unique value.

Let's Encrypt -> Checks Your Domain's DNS Records -> Does specific TXT record exist? -> OK! Cert Issued!

Since this validation happens entirely via DNS lookups (usually port 53, which is almost never blocked inbound for lookups), it completely bypasses the port 80 problem!

The Magic Combo: Traefik + AWS Route 53

Okay, manually creating DNS records every ~90 days sounds tedious. This is where the dynamic duo comes in:

  1. Traefik: Our smart reverse proxy knows how to talk the ACME protocol and request certificates using the DNS-01 method.
  2. AWS Route 53: A robust, API-driven DNS service.

You configure Traefik with AWS API credentials (using an IAM user with just enough permission to modify Route 53 records – security first!). When it's time to issue or renew a cert, Traefik tells Route 53 (via the API) to create the temporary TXT record Let's Encrypt needs. Once Let's Encrypt verifies it, Traefik tells Route 53 to clean it up. Chef's kiss!

The Key Configuration (Traefik + Docker Compose):

In your Traefik service definition within docker-compose.yml, the magic lies in the command arguments for your certificate resolver:

# Snippet from docker-compose.yml command section for Traefik
command:
  # ... other args ...
  # Let's Encrypt Resolver Config
  - "--certificatesresolvers.myresolver.acme.email=your-email@example.com"
  - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
  # --- THE IMPORTANT PART ---
  - "--certificatesresolvers.myresolver.acme.dnschallenge=true"          # Enable DNS Challenge
  - "--certificatesresolvers.myresolver.acme.dnschallenge.provider=route53" # Tell it to use AWS Route 53
  # ... other args ...

Complete example config,

# docker-compose.yml
services:
  # Traefik Service (Reverse Proxy & SSL)
  traefik:
    image: "traefik:v2.11" # Pin to a specific stable version
    container_name: "traefik-proxy" # Descriptive container name
    command:
      # Enable Docker provider & disable exposing by default
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      # Define HTTP (80) and HTTPS (443) entry points
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      # Enable Traefik API/Dashboard (for monitoring)
      - "--api.dashboard=true"
      # Configure Let's Encrypt Resolver (named 'myresolver')
      - "--certificatesresolvers.myresolver.acme.email=your-email@example.com" # !! REPLACE THIS with your email !!
      - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json"
      # Use DNS-01 challenge with Route53 provider
      - "--certificatesresolvers.myresolver.acme.dnschallenge=true"
      - "--certificatesresolvers.myresolver.acme.dnschallenge.provider=route53"
      # Global Redirect: HTTP -> HTTPS
      - "--entrypoints.web.http.redirections.entrypoint.to=websecure"
      - "--entrypoints.web.http.redirections.entrypoint.scheme=https"
    ports:
      # Expose port 80 for HTTP->HTTPS redirect (optional if ISP blocks)
      - "80:80"
      # Expose port 443 for HTTPS access (ESSENTIAL)
      - "443:443"
    volumes:
      # Mount Docker socket to detect container events
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      # Persist Let's Encrypt certificates
      - "./letsencrypt:/letsencrypt"
    networks:
      - proxy # Connect to our custom network
    # Environment variables for AWS credentials (will use .env file)
    environment:
      - AWS_ACCESS_KEY_ID
      - AWS_SECRET_ACCESS_KEY
      # - AWS_REGION=us-east-1 # Optional: Specify if needed
      # - AWS_HOSTED_ZONE_ID=YOUR_HOSTED_ZONE_ID # Optional
    labels:
      # Enable Traefik for its own dashboard
      - "traefik.enable=true"
      # Dashboard Router Rule (HTTPS)
      - "traefik.http.routers.traefik-dashboard.rule=Host(`traefik.yourdomain.com`)" # !! REPLACE with your dashboard hostname !!
      - "traefik.http.routers.traefik-dashboard.entrypoints=websecure"
      - "traefik.http.routers.traefik-dashboard.service=api@internal"
      # Use Let's Encrypt for the dashboard domain
      - "traefik.http.routers.traefik-dashboard.tls.certresolver=myresolver"
      # Secure the dashboard with Basic Auth
      - "traefik.http.routers.traefik-dashboard.middlewares=auth"
      - "traefik.http.middlewares.auth.basicauth.users=user:$ class="math-inline">apr1abcdefg$yourhashedpassword" # !! REPLACE THIS HASH !!
    restart: unless-stopped

You also need to securely provide your AWS credentials (Access Key ID & Secret) as environment variables, preferably using a .env file.

Conclusion: Problem Solved!

By switching to the DNS-01 challenge and leveraging the automation power of Traefik integrated with the AWS Route 53 API, the infamous port 80 block becomes irrelevant for getting your Let's Encrypt certificates. You get fully automated, secure HTTPS for your self-hosted services without hassle. Now that's cool.

Happy (secure) self-hosting!