DNS Challenge

DNS Challenge #

DNS-01 challenge adalah metode verifikasi kepemilikan domain yang bekerja melalui DNS record, bukan melalui HTTP. Dibandingkan HTTP-01, DNS challenge memiliki keunggulan yang signifikan: bisa digunakan untuk wildcard certificate, tidak membutuhkan port yang bisa diakses dari internet, dan bisa bekerja di server yang berada di belakang firewall atau di jaringan private.

Artikel ini membahas cara kerja DNS challenge secara mendalam, konfigurasi untuk semua provider DNS utama, dan strategi untuk mengelola credentials dengan aman.

Cara Kerja DNS-01 Challenge secara Detail #

Timeline lengkap DNS-01 challenge:

T+0  Caddy memulai proses perolehan sertifikat untuk *.example.com
     │
T+1  Caddy menghubungi ACME CA (Let's Encrypt) via HTTPS:
     "Saya ingin sertifikat untuk *.example.com.
      Saya menggunakan akun ACME dengan key fingerprint: abc123"
     │
T+2  CA membalas:
     "Oke. Untuk membuktikan kontrol domain, buat TXT record ini:
      Name:  _acme-challenge.example.com
      Value: H3t8xK_qP2...4WvRmLn9Z (random token)"
     │
T+3  Caddy menghubungi Cloudflare API (atau DNS provider lain):
     POST /zones/{zone_id}/dns_records
     { "type": "TXT",
       "name": "_acme-challenge",
       "content": "H3t8xK_qP2...4WvRmLn9Z",
       "ttl": 120 }
     │
T+4  DNS provider membuat TXT record
     (Record ada di nameserver Cloudflare)
     │
T+5  Caddy menunggu propagation
     (Cek berkala apakah record sudah resolve)
     │
T+n  DNS record sudah propagate ke nameserver yang dikonfigurasi CA
     │
T+n+1 CA melakukan DNS lookup untuk _acme-challenge.example.com
      Mendapatkan TXT record dengan nilai yang cocok
     │
T+n+2 CA menerbitkan sertifikat untuk *.example.com
     │
T+n+3 Caddy menyimpan sertifikat ke storage
     │
T+n+4 Caddy membersihkan TXT record dari DNS
      (Record sementara tidak lagi dibutuhkan)

Seluruh proses ini otomatis — tidak ada intervensi manual yang diperlukan.


Propagation Delay — Tantangan Utama DNS-01 #

Berbeda dengan HTTP-01 yang hampir instan, DNS-01 membutuhkan waktu untuk TXT record propagasi ke seluruh internet sebelum CA bisa melakukan verifikasi. Ini adalah salah satu alasan DNS-01 lebih lambat dari HTTP-01 untuk perolehan sertifikat baru.

Faktor yang mempengaruhi propagation time:

TTL Record:
  TTL rendah (60-300 detik) → Propagasi lebih cepat ke resolver caching
  TTL tinggi (86400 detik = 1 hari) → Resolver caching lebih lama

DNS Provider:
  Cloudflare → Biasanya < 30 detik (anycast, propagasi sangat cepat)
  Route53    → Biasanya 30-60 detik
  GCP Cloud DNS → Biasanya 30-120 detik
  Provider lain → Bisa 1-5 menit

Geographic distribution:
  CA Let's Encrypt menggunakan multiple resolver dari berbagai lokasi
  Semua resolvernya harus mendapat record sebelum challenge diterima

Mengkonfigurasi Propagation Timeout #

{
    email [email protected]
}

*.example.com {
    tls {
        dns cloudflare {env.CLOUDFLARE_API_TOKEN}
        
        # Timeout untuk menunggu propagasi DNS (default: 2 menit)
        # Naikkan jika DNS provider kamu lambat
        propagation_timeout 5m
        
        # Delay sebelum mulai cek propagasi
        # Berguna untuk provider yang butuh warmup time
        propagation_delay 30s
        
        # Custom resolvers untuk mengecek propagasi
        # Default: menggunakan resolver sistem
        resolvers 8.8.8.8 1.1.1.1
    }
    
    reverse_proxy localhost:3000
}

Konfigurasi Per Provider #

Cloudflare #

# Cara membuat API token:
# 1. Login Cloudflare Dashboard
# 2. My Profile → API Tokens → Create Token
# 3. Template: "Edit zone DNS"
# 4. Zone Resources: Include → Specific zone → example.com
# 5. Klik Continue dan Create Token
*.example.com, example.com {
    tls {
        dns cloudflare {env.CF_API_TOKEN}
    }
    reverse_proxy localhost:3000
}
# Opsi environment variable
export CF_API_TOKEN="your_cloudflare_token"

# Atau gunakan nama lain dan sesuaikan di Caddyfile
export CLOUDFLARE_API_TOKEN="your_token"
# Di Caddyfile: dns cloudflare {env.CLOUDFLARE_API_TOKEN}

Plugin Cloudflare juga mendukung API key lama (bukan token):

*.example.com {
    tls {
        dns cloudflare {
            api_token {env.CF_API_TOKEN}
            # Atau API key global (kurang aman):
            # email    {env.CF_EMAIL}
            # api_key  {env.CF_API_KEY}
        }
    }
}

AWS Route 53 #

# Setup: Buat IAM user/role dengan permissions minimal
# Policy JSON (simpan sebagai caddy-dns-policy.json):
cat << 'EOF' > caddy-dns-policy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "route53:GetChange",
                "route53:ChangeResourceRecordSets",
                "route53:ListResourceRecordSets"
            ],
            "Resource": [
                "arn:aws:route53:::hostedzone/Z1234567890ABC",
                "arn:aws:route53:::change/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": ["route53:ListHostedZones"],
            "Resource": "*"
        }
    ]
}
EOF

# Buat policy
aws iam create-policy \
    --policy-name CaddyDNSPolicy \
    --policy-document file://caddy-dns-policy.json
{
    email [email protected]
}

*.example.com, example.com {
    tls {
        dns route53 {
            # Credentials via environment variables (preferred)
            # AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION
            # akan dibaca otomatis
            
            # Atau specify hosted zone ID secara eksplisit
            max_retries 10
        }
    }
    
    reverse_proxy localhost:3000
}
# Environment variables untuk Route53
export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
export AWS_REGION="us-east-1"

# Di EC2 dengan IAM Role: tidak perlu set key sama sekali
# Plugin otomatis menggunakan instance metadata service

Google Cloud DNS #

# Buat Service Account dengan permissions Cloud DNS
gcloud iam service-accounts create caddy-dns-sa \
    --display-name "Caddy DNS Service Account"

# Berikan permission Cloud DNS Administrator ke SA
gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
    --member "serviceAccount:caddy-dns-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com" \
    --role "roles/dns.admin"

# Buat dan download credentials key
gcloud iam service-accounts keys create caddy-gcp-credentials.json \
    --iam-account caddy-dns-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com
*.example.com, example.com {
    tls {
        dns googleclouddns {
            gcp_project "your-gcp-project-id"
            # Credentials dibaca dari GOOGLE_APPLICATION_CREDENTIALS env var
        }
    }
    
    reverse_proxy localhost:3000
}
# Set path ke credentials file
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/caddy-gcp-credentials.json"

# Di GCE/GKE, bisa menggunakan Workload Identity atau VM Service Account
# tanpa credentials file

Azure DNS #

# Buat Service Principal dengan DNS Zone Contributor role
az ad sp create-for-rbac \
    --name "CaddyDNS" \
    --role "DNS Zone Contributor" \
    --scopes "/subscriptions/YOUR_SUB/resourceGroups/YOUR_RG/providers/Microsoft.Network/dnszones/example.com"

# Output menyertakan:
# appId (client_id), password (client_secret), tenant
*.example.com, example.com {
    tls {
        dns azure {
            tenant_id       {env.AZURE_TENANT_ID}
            client_id       {env.AZURE_CLIENT_ID}
            client_secret   {env.AZURE_CLIENT_SECRET}
            subscription_id {env.AZURE_SUBSCRIPTION_ID}
            resource_group  "your-resource-group"
        }
    }
    
    reverse_proxy localhost:3000
}

DigitalOcean #

# Buat API token di:
# DigitalOcean Dashboard → API → Tokens/Keys → Generate New Token
# Nama token: CaddyDNS, scope: Read + Write
*.example.com, example.com {
    tls {
        dns digitalocean {env.DO_AUTH_TOKEN}
    }
    
    reverse_proxy localhost:3000
}

Keamanan API Credentials #

API credentials untuk DNS challenge memberikan akses penuh ke zone DNS kamu — ini bukan hal yang sepele. Beberapa best practice:

1. Gunakan Scope Minimal #

# Cloudflare: Buat token dengan scope DNS:Edit untuk zone tertentu saja
# (bukan API Key global yang punya akses ke semua zone dan semua fitur)

# Route53: Buat IAM policy yang hanya izinkan:
# - ChangeResourceRecordSets untuk zone tertentu
# - GetChange untuk zone tertentu
# - ListHostedZones (readonly)

# GCP: Gunakan Service Account dengan roles/dns.admin
# (lebih baik lagi: roles/dns.recordSets jika tersedia di project kamu)

2. Simpan di Tempat yang Aman #

# ANTI-PATTERN: Hardcode di Caddyfile yang masuk ke Git
tls {
    dns cloudflare cf_token_12345abcde  # ← JANGAN!
}

# BENAR: Environment variable
tls {
    dns cloudflare {env.CF_API_TOKEN}
}

# Di systemd, gunakan override untuk set env var
sudo systemctl edit caddy
# Tambahkan:
# [Service]
# EnvironmentFile=/etc/caddy/caddy.env

# Buat file /etc/caddy/caddy.env
# CF_API_TOKEN=token_kamu
# Set permission strict
sudo chmod 600 /etc/caddy/caddy.env
sudo chown caddy:caddy /etc/caddy/caddy.env

3. Rotasi Credentials Berkala #

# Buat token baru di Cloudflare/AWS/GCP
# Update environment variable di server
# Reload Caddy (bukan restart — tidak perlu downtime)
sudo systemctl reload caddy
# Hapus token lama setelah yakin yang baru bekerja

DNS Challenge untuk Server di Jaringan Private #

Salah satu keunggulan terbesar DNS challenge: server tidak perlu bisa diakses dari internet. Ini membuka kemungkinan untuk mendapatkan sertifikat Let’s Encrypt yang valid untuk server internal:

# Server internal — tidak ada koneksi dari internet ke server ini
# Tapi tetap bisa mendapat sertifikat valid via DNS challenge!
{
    email [email protected]
}

# Domain ini resolve ke IP private
internal.company.com {
    tls {
        # DNS challenge bekerja karena verifikasi via DNS API
        # (bukan via HTTP ke server ini)
        dns cloudflare {env.CF_API_TOKEN}
    }
    
    reverse_proxy localhost:8080
}

# Wildcard untuk semua service internal
*.services.company.com {
    tls {
        dns cloudflare {env.CF_API_TOKEN}
    }
    
    reverse_proxy localhost:9000
}

Troubleshooting DNS Challenge #

TXT Record Tidak Dibuat #

# Gejala: "DNS record not created" di log
# Diagnosa: Cek apakah Caddy bisa reach DNS API

# Test koneksi ke Cloudflare API dari server
curl -X GET "https://api.cloudflare.com/client/v4/zones" \
    -H "Authorization: Bearer $CF_API_TOKEN"

# Jika gagal: cek firewall outbound ke api.cloudflare.com:443
# Jika berhasil tapi error 403: token permissions tidak cukup

Record Dibuat tapi Challenge Gagal #

# Gejala: TXT record ada di DNS tapi CA tetap reject

# Diagnosa 1: Cek apakah record resolve dari luar
dig TXT _acme-challenge.example.com @8.8.8.8
dig TXT _acme-challenge.example.com @1.1.1.1
# Keduanya harus return record yang sama

# Diagnosa 2: Mungkin ada multiple TXT record (dari attempt sebelumnya)
# CA kadang gagal jika ada multiple records dengan nilai berbeda
dig TXT _acme-challenge.example.com

# Solusi: Hapus record lama dari DNS dashboard
# Caddy akan buat yang baru saat attempt berikutnya

# Diagnosa 3: Naikkan propagation timeout
# tls { dns cloudflare {env.CF} propagation_timeout 10m }

Rate Limit DNS API #

# Gejala: "429 Too Many Requests" di log API calls
# Cloudflare: 1200 req/5 menit per token
# Route53: 5 requests per second per account

# Jika banyak domain di-renew bersamaan, bisa hit rate limit
# Solusi: Caddy otomatis retry — tidak perlu aksi manual
# Atau set renewal_window lebih lebar agar tidak semua renew bersamaan

Ringkasan #

  • DNS-01 challenge bekerja dengan membuat TXT record sementara di _acme-challenge.domain.com — tidak membutuhkan port 80/443 terbuka dari internet.
  • Selalu konfigurasi propagation_timeout yang cukup sesuai DNS provider kamu — Cloudflare sangat cepat, provider lain mungkin butuh 5-10 menit.
  • Gunakan resolvers eksplisit (misalnya 8.8.8.8 1.1.1.1) untuk checking propagasi yang konsisten dan cepat.
  • Simpan API credentials di environment variable yang terproteksi (chmod 600) — jangan hardcode di Caddyfile.
  • Gunakan API token/key dengan scope minimal — hanya permissions yang diperlukan untuk membuat dan menghapus DNS records.
  • DNS challenge adalah satu-satunya cara untuk mendapat sertifikat untuk server internal yang tidak bisa diakses dari internet.

← Sebelumnya: Wildcard Certificate   Berikutnya: Static Files →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact