Passive Health Check

Passive Health Check #

Passive health check adalah mekanisme di mana Caddy mengamati hasil dari request nyata yang dikirim ke backend — tanpa mengirim request health check tambahan. Ketika Caddy melihat backend gagal menjawab (koneksi ditolak, timeout, atau error) dalam batas tertentu, ia secara otomatis menandai backend tersebut sebagai unhealthy dan berhenti mengirim traffic ke sana.

Berbeda dari active health check yang “aktif bertanya”, passive health check “pasif mengamati”. Keduanya melengkapi satu sama lain dan bisa dikonfigurasi bersamaan untuk proteksi berlapis.

Cara Kerja Passive Health Check #

Caddy mengamati setiap response dari backend:

Request 1 → backend-2 → 500 Internal Server Error ← Catat kegagalan (1/3)
Request 2 → backend-2 → Connection refused         ← Catat kegagalan (2/3)
Request 3 → backend-2 → Timeout setelah 30s        ← Catat kegagalan (3/3)

Threshold 3 tercapai! → backend-2 ditandai UNHEALTHY
Traffic tidak lagi dikirim ke backend-2

[Jika passive saja, tidak ada check aktif]
[Caddy tetap sesekali mencoba backend-2 untuk recovery]

Request nyata dikirim ke backend-2 → 200 OK ← Catat sukses (1/2)
Request nyata dikirim ke backend-2 → 200 OK ← Catat sukses (2/2)

Threshold recovery 2 tercapai! → backend-2 kembali HEALTHY

Konfigurasi Passive Health Check #

example.com {
    reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000 {
        # Tandai unhealthy setelah N kegagalan berturut-turut
        health_fails 3
        
        # Tandai kembali healthy setelah N sukses berturut-turut
        health_passes 2
    }
}

Kegagalan yang Dihitung #

Yang DIHITUNG sebagai kegagalan:
  ✓ Connection refused (backend tidak bisa dijangkau)
  ✓ Connection timeout (backend tidak respond dalam batas waktu)
  ✓ Network error (DNS tidak resolve, interface down, dll.)
  ✓ Status code yang dianggap error (5xx secara default)

Yang TIDAK dihitung sebagai kegagalan:
  ✗ Response 4xx dari backend (itu adalah response valid dari backend)
  ✗ Request yang di-cancel oleh client sebelum backend respond
  ✗ Health check requests (passive health check tidak menghitung health check)

max_fails dan fail_duration — Window-Based Failure Counting #

Selain menghitung kegagalan berurutan (health_fails), Caddy mendukung window-based failure counting yang lebih nuanced:

example.com {
    reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000 {
        # Tandai unhealthy jika ada lebih dari N kegagalan
        # dalam window waktu tertentu
        # (ini adalah konfigurasi lebih lanjut via JSON config)
        
        # Untuk Caddyfile, gunakan:
        health_fails    5    # Maksimum 5 kegagalan berturut-turut sebelum unhealthy
        health_passes   3    # Butuh 3 sukses berturut-turut untuk recovery
    }
}

Passive vs Active: Kapan Menggunakan Mana #

Active Health Check:
  + Deteksi kegagalan SEBELUM user terpengaruh
  + Backend down dideteksi dalam 1 interval (misal 10s)
  + Tidak ada user yang mengalami error akibat backend down
  - Menambah traffic ke backend (health check requests)
  - Perlu endpoint /health di backend
  - Overhead kecil untuk koneksi + request health check
  
Passive Health Check:
  + Tidak ada overhead request tambahan
  + Tidak perlu endpoint /health khusus di backend
  + Deteksi otomatis berbasis traffic nyata
  - User AKAN mengalami beberapa error sebelum backend ditandai unhealthy
  - Jika traffic rendah, deteksi butuh waktu lebih lama
  - Backend yang sangat jarang menerima traffic mungkin tidak pernah dideteksi

Kombinasi keduanya (TERBAIK untuk production):
  Active  → Deteksi proaktif, user tidak terpengaruh
  Passive → Safety net tambahan untuk kondisi yang mungkin lolos active check

Kombinasi Active + Passive Health Check #

Ini adalah konfigurasi yang direkomendasikan untuk production:

{
    email [email protected]
}

api.example.com {
    reverse_proxy backend-1:8080 backend-2:8080 backend-3:8080 {
        lb_policy least_conn
        
        # ── Active Health Check ──────────────────────────────────
        health_uri      /health         # Endpoint yang dicek aktif
        health_interval 10s             # Cek setiap 10 detik
        health_timeout  5s              # Timeout per check
        health_status   200             # Kode yang dianggap sehat
        
        # ── Passive Health Check ─────────────────────────────────
        health_fails    3               # Unhealthy setelah 3 error berurutan
        health_passes   2               # Recovery setelah 2 sukses berurutan
        
        # Header forwarding
        header_up X-Real-IP {remote_host}
        header_up X-Forwarded-Proto {scheme}
        
        # Transport
        transport http {
            dial_timeout            5s
            response_header_timeout 30s
        }
        
        # Retry ke upstream lain jika gagal
        lb_try_duration  5s
        lb_try_interval  200ms
    }
}

Retry Logic — Melengkapi Health Check #

Retry memungkinkan Caddy mencoba upstream lain ketika request pertama gagal, SEBELUM passive health check threshold tercapai:

example.com {
    reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000 {
        health_fails  3
        health_passes 2
        
        # Jika request ke backend pertama gagal (koneksi error),
        # coba backend lain dalam waktu maksimal 5 detik
        lb_try_duration  5s    # Total waktu untuk semua retry
        lb_try_interval  250ms # Jeda antara percobaan
        
        # Retry hanya terjadi untuk kegagalan koneksi (sebelum response diterima)
        # Tidak retry jika backend sudah return response (misal: 500 error)
    }
}
Scenario: backend-2 down, request dikirim ke backend-2

Tanpa retry:
  Request → backend-2 → Connection refused → 502 Bad Gateway ke user
  (user langsung dapat error, passive health check catat +1 failure)

Dengan retry (lb_try_duration 5s):
  T=0ms   Request → backend-2 → Connection refused (failure!)
  T=0ms   Caddy retry → backend-3 → 200 OK → user dapat response normal
  (user tidak mengalami error sama sekali)
  (passive health check tetap catat failure untuk backend-2)
  (setelah N failures, backend-2 ditandai unhealthy)

Circuit Breaker Pattern dengan Passive Health Check #

Passive health check dalam Caddy mengimplementasikan pola circuit breaker secara implisit:

State: CLOSED (normal)
  Traffic mengalir ke semua backend
  Passive health check mengamati error
  
State: OPEN (backend unhealthy)
  Backend X tandai unhealthy setelah threshold failures
  Traffic di-redirect ke backend lain
  Backend X tidak menerima traffic baru
  
State: HALF-OPEN (percobaan recovery)
  Caddy sesekali kirim request ke backend X
  (bergantung pada traffic yang masuk dan rotation)
  Jika N sukses berurutan → kembali CLOSED
  Jika masih gagal → tetap OPEN
Implementasi di Caddy:
  CLOSED  → health_fails threshold belum tercapai
  OPEN    → health_fails threshold tercapai, backend unhealthy
  HALF-OPEN → otomatis: backend di-coba lagi saat ada traffic
              (tidak perlu konfigurasi terpisah)

Konfigurasi untuk Berbagai Skenario #

Toleransi Tinggi (Loose) #

Untuk service yang sesekali error tapi tidak kritis:

example.com {
    reverse_proxy backend-1:3000 backend-2:3000 {
        # Butuh lebih banyak kegagalan sebelum unhealthy
        health_fails  10
        health_passes 5
        
        # Tidak ada active health check
        # Hemat resource untuk service non-kritis
    }
}

Toleransi Rendah (Strict) #

Untuk service kritis yang tidak boleh ada error sama sekali:

critical.example.com {
    reverse_proxy backend-1:3000 backend-2:3000 backend-3:3000 {
        # Active health check yang agresif
        health_uri      /health
        health_interval 5s     # Check lebih sering
        health_timeout  3s     # Timeout lebih ketat
        
        # Passive: fail cepat
        health_fails  1        # Langsung unhealthy setelah 1 kegagalan
        health_passes 3        # Butuh 3 sukses untuk recovery (lebih konservatif)
        
        # Retry agresif
        lb_try_duration  3s
        lb_try_interval  100ms
    }
}

Logging dan Observability #

Passive health check events bisa diamati dari log Caddy:

# Filter log untuk health check events
sudo journalctl -u caddy | grep -i "health\|unhealthy\|upstream"

# Output contoh:
# INFO  http.handlers.reverse_proxy  upstream is now unavailable
#   {"upstream": "backend-2:3000", "total_fails": 3}
# INFO  http.handlers.reverse_proxy  upstream is now available
#   {"upstream": "backend-2:3000"}

# Dengan JSON log, bisa di-filter dengan jq
tail -f /var/log/caddy/access.log | jq 'select(.upstream_addr != null) | {
    time: .ts,
    upstream: .upstream_addr,
    status: .status,
    latency: .duration
}'

# Hitung error rate per upstream (dari access log)
cat /var/log/caddy/access.log | jq -r \
    '[.upstream_addr, (.status | tostring)] | @tsv' | \
    awk -F'\t' '{
        total[$1]++
        if ($2 >= "500") errors[$1]++
    } END {
        for (addr in total) {
            err_rate = (errors[addr]+0) / total[addr] * 100
            printf "%s: %d total, %.1f%% errors\n", addr, total[addr], err_rate
        }
    }'

Ringkasan #

  • Passive health check mengamati error dari request nyata — tanpa mengirim request tambahan, tanpa membutuhkan endpoint /health di backend.
  • Konfigurasi inti: health_fails N (unhealthy setelah N kegagalan berurutan) dan health_passes N (recovery setelah N sukses berurutan).
  • Passive saja tidak cukup: user akan mengalami beberapa error sebelum backend ditandai unhealthy. Selalu kombinasikan dengan active health check untuk production.
  • Retry (lb_try_duration) bekerja sinergis dengan passive health check — request pertama dicoba ke backend lain sebelum user merasakan error, sambil tetap mencatat kegagalan untuk passive health check.
  • Pola circuit breaker diimplementasikan secara implisit: CLOSED (normal) → OPEN (unhealthy) → HALF-OPEN (recovery otomatis) tanpa konfigurasi tambahan.
  • Untuk service kritis, gunakan health_fails 1 agar satu kegagalan langsung menyebabkan backend dikeluarkan dari rotasi — kombinasikan dengan lebih banyak backend untuk redundansi.

← Sebelumnya: Active Health Check   Berikutnya: Admin Endpoint →

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