Debug Konfigurasi #
Melakukan pemecahan masalah (debugging) pada konfigurasi Caddy yang tidak berjalan sesuai harapan bisa menjadi proses yang membingungkan jika kita tidak tahu di mana harus mencari informasi. Berbeda dengan server web tradisional yang sering kali menyembunyikan detail pemrosesan internalnya, Caddy memiliki mesin pencatatan log (logging engine) yang sangat kaya dan Admin API berbasis REST yang memungkinkan kita melihat isi memori konfigurasi aktif secara real-time. Artikel ini menyajikan panduan sistematis untuk men-debug berbagai masalah konfigurasi—dari rute yang tidak cocok, placeholder yang kosong, hingga kegagalan jabat tangan TLS.
Siklus Hidup Permintaan HTTP & Titik Intersepsi Debug #
Saat melakukan debugging, kita harus memahami bagaimana Caddy memproses permintaan dari awal koneksi TCP dibentuk hingga respons dikirim kembali ke klien. Diagram di bawah menunjukkan alur pipa pemrosesan internal (HTTP pipeline) Caddy beserta titik di mana kita dapat menyisipkan instrumen debug:
flowchart TD
TCPConnection["Koneksi TCP Terbuka"] --> TLSHandshake{"Jabat Tangan TLS?"}
TLSHandshake -- Gagal --> TLSFailed["Handshake Gagal (tls: no certificate)"]
TLSHandshake -- Sukses --> TLSSuccess["Handshake Sukses (Sertifikat Valid)"]
TLSSuccess --> HTTPMatches{"Pencocokan Named Matchers?"}
HTTPMatches -- Tidak Cocok --> NoMatch["Gunakan Default Handler / Fallback 404"]
HTTPMatches -- Cocok --> Match["Eksekusi Rantai Middleware"]
subgraph MiddlewarePipeline["Pipa Middleware Caddy"]
Log["Log Request ID & Headers (X-Debug-Route)"] --> Rewrite["Rewrite URL / Redirection (redir / rewrite)"]
Rewrite --> Auth["Otentikasi (basicauth / JWT)"]
Auth --> Route["Reverse Proxy ke Upstream / File Server"]
end
Match --> MiddlewarePipeline
Route --> Response["Downstream Response (Transformasi Header)"]
style TCPConnection stroke:#0288d1,stroke-width:2px
style MiddlewarePipeline stroke:#7b1fa2,stroke-width:2pxPrinsip Debugging Sistematis #
Agar proses pencarian masalah berjalan efisien, kita harus membiasakan diri menerapkan empat pilar pemecahan masalah berikut:
- Reproduce (Direproduksi): Pastikan kita dapat memicu kesalahan tersebut secara konsisten. Catat kondisi persis (jalur URL, HTTP header, metode request, dan jenis perangkat klien) saat error terjadi.
- Isolate (Diisolasi): Pisahkan setiap komponen untuk menemukan letak kesalahan. Apakah masalahnya ada pada resolusi DNS, jaringan Caddy ke backend, konfigurasi Caddyfile, atau bug di dalam kode backend kita sendiri? Sederhanakan konfigurasi hingga titik minimal yang masih dapat mereproduksi error.
- Analyze (Dianalisis): Manfaatkan log sistem, adaptasi konfigurasi JSON, dan Admin API untuk memantau keadaan internal server.
- Fix & Verify (Diperbaiki & Diverifikasi): Terapkan solusi, jalankan validasi sintaksis sebelum melakukan deploy, dan uji kembali menggunakan skrip otomatis untuk memastikan tidak ada regresi performa.
Debug Mode #
Langkah pertama dan paling penting saat menemukan keanehan pada Caddy adalah mengaktifkan Debug Mode. Secara default, Caddy hanya mencatat log pada level INFO ke atas untuk menjaga ukuran berkas log dan menghemat I/O disk. Dengan menaikkan level ke DEBUG, Caddy akan mencatat detail setiap keputusan pencocokan rute (route matching), header jabat tangan TLS, dan upaya dial upstream.
1. Mengaktifkan Debug Mode di Caddyfile #
Tambahkan opsi global debug di baris paling atas Caddyfile kita:
{
# Aktifkan pencatatan log level debug secara global
# PERINGATAN: Log akan sangat verbose. Matikan kembali setelah proses debug selesai!
debug
}
example.com {
reverse_proxy localhost:3000
}
Setelah Caddyfile disimpan, muat ulang konfigurasi:
sudo systemctl reload caddy
2. Mengaktifkan Debug Mode Dinamis via Admin API (Tanpa Restart) #
Jika kita tidak ingin mengubah file Caddyfile atau tidak bisa melakukan restart pada lingkungan produksi yang sedang aktif, kita bisa menaikkan level log ke debug secara instan melalui Admin API:
# Mengubah level log default menjadi debug secara dinamis
curl -X PUT http://localhost:2019/config/logging/logs/default/level \
-H "Content-Type: application/json" \
-d '"debug"'
# Pantau log debug secara real-time dari terminal
sudo journalctl -u caddy -f | grep -i "debug"
# Kembalikan ke level info setelah selesai melakukan pelacakan
curl -X PUT http://localhost:2019/config/logging/logs/default/level \
-H "Content-Type: application/json" \
-d '"info"'
Membaca Konfigurasi Aktif via Admin API #
Caddyfile yang kita tulis sebenarnya hanyalah lapisan kemudahan (syntactic sugar). Core engine Caddy sendiri hanya memahami konfigurasi dalam format JSON. Saat kita memuat ulang Caddy, Caddyfile diterjemahkan (adapted) menjadi dokumen JSON raksasa yang disimpan langsung di memori RAM Caddy.
Kita bisa membaca keadaan memori konfigurasi aktif ini kapan saja melalui Admin API untuk memastikan apakah Caddyfile kita diinterpretasikan dengan benar:
# 1. Unduh seluruh konfigurasi JSON aktif dan format menggunakan jq
curl -s http://localhost:2019/config/ | jq .
# 2. Periksa daftar server HTTP yang terdaftar
curl -s http://localhost:2019/config/apps/http/servers/ | jq 'keys'
# 3. Lacak rute aktif pada server utama (srv0)
# Membantu melihat urutan evaluasi middleware yang sebenarnya di memori Caddy
curl -s http://localhost:2019/config/apps/http/servers/srv0/routes/ | \
jq '[.[] | {id: .["@id"], match: .match[0].host, handlers: [.handle[].handler]}]'
# 4. Bandingkan konfigurasi aktif dengan file lokal untuk mendeteksi deviasi
caddy adapt --config /etc/caddy/Caddyfile | jq . > /tmp/expected.json
curl -s http://localhost:2019/config/ | jq . > /tmp/actual.json
diff --color /tmp/expected.json /tmp/actual.json
Debugging Routing Matchers #
Masalah routing adalah salah satu yang paling sering terjadi: kita menulis named matcher, tetapi permintaan dari klien tidak masuk ke blok handle yang kita inginkan dan malah jatuh ke fallback default.
Taktik debugging terbaik adalah dengan menyuntikkan header respons kustom (header X-Debug-Route) secara dinamis di setiap blok handler. Hal ini memungkinkan kita melihat secara visual rute mana yang dieksekusi Caddy hanya dengan memeriksa header respons dari klien:
example.com {
# Tentukan matchers
@api_route path /api/*
@static_route path /static/* /assets/*
@admin_route host admin.example.com
# 1. Uji Rute API
handle @api_route {
# Suntikkan header debug sebelum mem-proxy
header X-Debug-Route "api-handler"
reverse_proxy api:8080
}
# 2. Uji Rute File Statis
handle @static_route {
header X-Debug-Route "static-handler"
file_server {
root /var/www/static
}
}
# 3. Uji Rute Subdomain Admin
handle @admin_route {
header X-Debug-Route "admin-handler"
reverse_proxy admin:9000
}
# 4. Fallback Default
handle {
header X-Debug-Route "fallback-handler"
respond "No route matched by Caddy Matcher" 404
}
}
Setelah konfigurasi diaktifkan, gunakan curl -I untuk memverifikasi jalur routing:
# Uji jalur API
curl -I https://example.com/api/users
# Output harus menunjukkan: X-Debug-Route: api-handler
# Uji jalur statis
curl -I https://example.com/static/logo.png
# Output harus menunjukkan: X-Debug-Route: static-handler
# Uji fallback
curl -I https://example.com/unknown-path
# Output harus menunjukkan: X-Debug-Route: fallback-handler
Debugging Placeholder Values #
Placeholder Caddy (ditulis dengan kurung kurawal {}) adalah cara Caddy menyajikan variabel dinamis (seperti IP remote, header request, atau klaim JWT). Jika placeholder tersebut kosong atau salah ketik, variabel tidak akan terekspos.
Kita bisa men-debug isi nilai placeholder dengan menyuntikkannya sebagai header respons sementara:
example.com {
# Ekspos nilai placeholder ke HTTP response header demi kemudahan debug
header {
X-Debug-Client-IP "{remote_host}"
X-Debug-Host "{host}"
X-Debug-URI "{uri}"
X-Debug-Method "{method}"
X-Debug-Scheme "{scheme}"
X-Debug-Request-UUID "{http.request.uuid}"
}
reverse_proxy localhost:3000
}
# Uji request dan tampilkan semua header buatan kita
curl -I https://example.com/api/test?user=john | grep X-Debug
# Output contoh:
# X-Debug-Client-IP: 203.0.113.10
# X-Debug-Host: example.com
# X-Debug-URI: /api/test?user=john
# X-Debug-Method: GET
# X-Debug-Scheme: https
# X-Debug-Request-UUID: f3b0c4d8-c9ea-4b2a-8d1e-92718c3a9f0e
Debugging TLS dan SSL Handshake #
Jika jabat tangan SSL/TLS gagal, browser klien tidak akan bisa membuat sambungan aman ke server. Kita harus mengisolasi apakah masalahnya ada di tingkat sertifikat (expired/untrusted), dukungan protokol cipher, atau SNI mismatch.
# 1. Tampilkan informasi sertifikat SSL yang disajikan Caddy secara lengkap
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
openssl x509 -noout -text | grep -E "Subject:|DNS:|Not After"
# 2. Periksa daftar cipher suites dan protokol TLS yang didukung
# Sangat penting jika Anda memiliki klien lawas yang membutuhkan TLS 1.2
openssl s_client -connect example.com:443 -brief
# 3. Uji apakah Caddy merespons dengan benar pada protokol TLS 1.3
curl -v --tlsv1.3 https://example.com 2>&1 | grep "SSL connection"
# 4. Periksa masa aktif Root CA lokal Caddy (jika menggunakan local self-signed)
curl -s http://localhost:2019/pki/ca/local | jq '{name: .name, root_expires: .root.not_after}'
Debugging Reverse Proxy Latency #
Ketika aplikasi terasa lambat, klien sering menyalahkan web server. Kita harus mengukur secara presisi apakah keterlambatan terjadi di tingkat transmisi gateway Caddy, atau karena backend kita yang lambat menghasilkan data (TTFB delay).
# 1. Pantau status kesehatan backend (upstreams) secara real-time via Admin API
curl -s http://localhost:2019/reverse_proxy/upstreams/ | \
jq -r '.[] | "\(.address): \(if .healthy then "UP" else "DOWN" end) (active connections: \(.num_requests), fails: \(.fails))"'
# 2. Bandingkan waktu eksekusi request langsung ke backend vs lewat Caddy
# Uji langsung ke backend (misal port 3000)
time curl -o /dev/null -s http://localhost:3000/api/heavy-job
# Uji lewat Caddy (port 443 dengan SSL)
time curl -o /dev/null -s https://example.com/api/heavy-job
# Perbandingan total waktu (time_total) akan menunjukkan overhead Caddy.
# Di lingkungan normal, overhead Caddy hanyalah fraksi milidetik (< 1ms).
Debugging Header Manipulation Issues #
Terkadang kita merasa telah memodifikasi request header menggunakan direktif header_up atau header_down, namun backend melaporkan tidak menerima header tersebut.
Untuk memverifikasinya secara apple-to-apple, kita bisa merutekan traffic sementara ke server echo diagnostik (seperti httpbin.org atau wadah docker containous/whoami) yang mengembalikan seluruh data request header yang diterimanya sebagai bodi respons:
# Konfigurasi uji coba sementara
debug-header.example.com {
reverse_proxy https://httpbin.org {
# Mengubah host header agar httpbin mengenali request
header_up Host {upstream_host}
# Header buatan kita yang ingin di-debug
header_up X-My-Custom-Header "Production-Token-Value"
header_up X-Request-ID {http.request.uuid}
}
}
# Kirim request dan lihat apakah custom header kita muncul di JSON kembalian httpbin
curl -s https://debug-header.example.com/headers | jq .
# Di dalam JSON output, periksa blok "headers" untuk memastikan X-My-Custom-Header ada.
Debugging dengan Adaptasi Caddyfile (caddy adapt)
#
Caddyfile adalah format konfigurasi yang ramah manusia. Sebelum mengaplikasikannya ke server produksi, kita bisa mengecek bagaimana Caddy mengompilasi berkas tersebut menjadi format JSON asli. Ini sangat berguna untuk mendeteksi rute terselubung atau perilaku tidak terduga akibat urutan penulisan direktif.
# Adaptasi berkas konfigurasi lokal dan tampilkan JSON terformat
caddy adapt --config /etc/caddy/Caddyfile | jq .
# Lacak rute tertentu (misalnya rute untuk domain example.com)
caddy adapt --config /etc/caddy/Caddyfile | \
jq '.apps.http.servers.srv0.routes[] | select(.match[0].host[]? == "example.com")'
Membuat Minimal Reproducible Config #
Jika Anda mendapati bug atau perilaku aneh yang sulit diisolasi di Caddyfile produksi yang rumit, buatlah berkas konfigurasi mandiri yang minimal (minimal reproducible configuration). Ini membuang semua kebisingan konfigurasi luar dan fokus hanya pada satu fitur yang bermasalah.
# 1. Buat berkas Caddyfile pengujian minimal di folder temporary
cat << 'EOF' > /tmp/test-caddy.Caddyfile
{
# Jalankan admin API pada port non-standar agar tidak bentrok
admin localhost:2020
# Aktifkan debug log
debug
}
# Gunakan port localhost biasa tanpa SSL otomatis untuk memudahkan pengujian lokal
localhost:8080 {
# Gunakan mock response dinamis
respond "Debug Server OK — Client IP: {remote_host}" 200
}
EOF
# 2. Jalankan instansi Caddy terisolasi menggunakan berkas di atas
caddy run --config /tmp/test-caddy.Caddyfile
# 3. Di terminal terpisah, uji responsnya
curl http://localhost:8080/
Setelah rute minimal berjalan dengan benar, tambahkan baris middleware satu per satu dari Caddyfile produksi Anda hingga perilaku aneh tersebut muncul kembali. Cara ini akan langsung menunjukkan baris mana yang menjadi sumber masalah.
Penggunaan Direktif respond untuk Mocking
#
Saat melakukan pemecahan masalah perutean (routing), kita sering kali terganggu oleh backend yang mati atau lambat. Kita bisa menggunakan direktif respond sebagai mock handler sementara untuk memastikan bahwa named matchers Caddyfile kita telah menyaring request dengan benar:
example.com {
# ANTI-PATTERN: Menulis reverse_proxy langsung saat men-debug routing
# reverse_proxy localhost:3000
# BENAR: Gunakan respond sementara untuk memverifikasi matcher
@api_path path /api/v1/*
handle @api_path {
respond "ROUTE_MATCH: API Versi 1" 200
}
@web_path path /web/*
handle @web_path {
respond "ROUTE_MATCH: Web App Frontend" 200
}
handle {
respond "ROUTE_MATCH: Default Fallback" 404
}
}
Uji respons dengan curl:
curl https://example.com/api/v1/users # Output: ROUTE_MATCH: API Versi 1
curl https://example.com/web/dashboard # Output: ROUTE_MATCH: Web App Frontend
Setelah routing terbukti bekerja secara akurat, ganti blok respond kembali dengan direktif reverse_proxy atau file_server Anda.
Skrip Diagnostik Debug Sistematis #
Ketika menangani server produksi yang bermasalah, kita memerlukan informasi sistem operasi dan Caddy secara cepat dalam satu perintah. Skrip di bawah mengompilasi seluruh data diagnostik penting ke dalam satu laporan teks:
#!/bin/bash
# caddy-diagnostic.sh — Skrip pengumpul data diagnostik Caddy
set -u
REPORT_FILE="/tmp/caddy-diagnostic-report.txt"
echo "=== MEMULAI DIAGNOSTIK CADDY ===" | tee "$REPORT_FILE"
echo "Waktu Uji: $(date)" | tee -a "$REPORT_FILE"
echo "-----------------------------------" | tee -a "$REPORT_FILE"
echo "" >> "$REPORT_FILE"
echo "--- 1. VERSI CADDY ---" >> "$REPORT_FILE"
caddy version >> "$REPORT_FILE" 2>&1
echo "" >> "$REPORT_FILE"
echo "--- 2. STATUS LAYANAN SYSTEMD ---" >> "$REPORT_FILE"
systemctl is-active caddy >> "$REPORT_FILE" 2>&1
systemctl status caddy --no-pager -n 10 >> "$REPORT_FILE" 2>&1
echo "" >> "$REPORT_FILE"
echo "--- 3. PORT NET STATS ---" >> "$REPORT_FILE"
sudo ss -tlnp | grep -E "caddy|2019|80|443" >> "$REPORT_FILE" 2>&1
echo "" >> "$REPORT_FILE"
echo "--- 4. AKTIVITAS UPSTREAM REVERSE PROXY ---" >> "$REPORT_FILE"
curl -s http://localhost:2019/reverse_proxy/upstreams/ | jq . >> "$REPORT_FILE" 2>&1 || echo "Admin API tidak terjangkau" >> "$REPORT_FILE"
echo "" >> "$REPORT_FILE"
echo "--- 5. 10 BARIS ERROR TERAKHIR DI JOURNALD ---" >> "$REPORT_FILE"
sudo journalctl -u caddy -n 50 --no-pager | grep -E "error|warn|fail" | tail -10 >> "$REPORT_FILE" 2>&1
echo "" >> "$REPORT_FILE"
echo "-----------------------------------" >> "$REPORT_FILE"
echo "[✓] Diagnostik selesai. Laporan disimpan di: $REPORT_FILE"
Jalankan skrip di atas saat terjadi kendala untuk mendapatkan gambaran menyeluruh tentang keadaan Caddy Anda dalam hitungan detik.
Ringkasan #
- Debug Verbose — Aktifkan level
debugsecara global di Caddyfile atau gunakan perintah PUT Admin API untuk menaikkan verbositas pencatatan log secara instan tanpa menghentikan traffic.- Admin API Inspection — Gunakan REST API
/config/untuk melihat isi memori Caddy saat ini untuk mendeteksi ketidaksesuaian antara Caddyfile lokal dengan konfigurasi aktif.- Header Debugging — Suntikkan header respons kustom seperti
X-Debug-Routeatau variabel placeholder untuk memverifikasi keputusan route matching secara langsung dari sisi klien.- httpbin Echo Testing — Uji manipulasi request header dengan mengarahkan
reverse_proxysementara kehttpbin.orguntuk melihat header aktual yang dikirimkan Caddy.- Konfigurasi Minimal — Isolasi bug konfigurasi yang rumit dengan membuat Caddyfile mandiri minimal di direktori
/tmp/dan menjalankannya secara lokal menggunakancaddy run.- Mocking respond — Gunakan direktif
respondsebagai pengganti backend sementara untuk menguji logika filter named matcher tanpa gangguan dari kegagalan server upstream.