Arsitektur Caddy #

Memahami arsitektur internal Caddy bukan sekadar kebutuhan teori. Ketika kita menghadapi masalah konfigurasi yang rumit di lingkungan produksi, merancang arsitektur microservices tingkat lanjut, atau melakukan debugging performa jabat tangan TLS yang gagal, pemahaman tentang bagaimana Caddy bekerja di balik layar adalah pembeda utama antara menebak-nebak solusi atau memecahkannya secara presisi. Caddy dirancang dengan filosofi arsitektur yang sangat modern: semua komponen adalah modul yang dapat disusun (composable), dan konfigurasi server diperlakukan sebagai data terstruktur yang hidup dan dapat dimanipulasi setiap saat tanpa mengganggu koneksi aktif.


Gambaran Arsitektur Keseluruhan #

Core Caddy dirancang sebagai runtime engine minimalis. Fungsionalitas utama server web yang kita kenal sehari-hari sebenarnya diimplementasikan melalui modul-modul independen yang terhubung satu sama lain secara dinamis.

Hubungan antar komponen utama di dalam proses Caddy digambarkan pada diagram berikut:

flowchart TD
      subgraph Process ["Caddy Process Space"]
          direction TB
          Admin["Admin REST API (Port 2019)"]
          HTTPApp["HTTP App Module (Port 80/443)"]
          Registry["Caddy Module Registry"]
          TLSApp["TLS App (Certificate Manager)"]
          
          Admin --> Registry
          HTTPApp --> Registry
          TLSApp --> Registry
      end
      
      subgraph Modules ["Modul Handler & Provider"]
          direction LR
          Handlers["http.handlers.*"]
          Issuance["tls.issuance.*"]
          Storage["tls.storage.*"]
      end
      
      Registry --> Handlers
      Registry --> Issuance
      Registry --> Storage
      
      style Process stroke:#0288d1,stroke-width:2px
      style Modules stroke:#43a047,stroke-width:2px

Setiap bagian dari Caddy berkomunikasi melalui antarmuka (interface) Go yang terstandarisasi. Komponen krusial seperti HTTP server (http), modul TLS (tls), dan Admin API (admin) semuanya terdaftar sebagai modul di dalam registry global Caddy saat server dijalankan.


Sistem Modul, Namespace, dan Siklus Hidup (Lifecycle Hook) #

Struktur modular Caddy v2 dibangun di atas sistem modul Go yang terdaftar secara dinamis saat program diinisialisasi (init()). Setiap modul memiliki ID unik dalam format namespace hierarkis menggunakan tanda titik sebagai pemisah.

Struktur namespace modul Caddy dikelompokkan berdasarkan kategori perannya masing-masing:

caddy/
  ├── apps/
  │   ├── http                          (Modul utama untuk server HTTP/HTTPS)
  │   └── tls                           (Modul utama untuk manajemen TLS)
  ├── http.handlers/
  │   ├── static_response               (Menyajikan respons HTTP statis langsung)
  │   ├── reverse_proxy                 (Meneruskan request ke backend upstream)
  │   ├── file_server                   (Menyajikan file fisik dari disk)
  │   ├── encode                        (Melakukan kompresi Brotli/Gzip)
  │   ├── rewrite                       (Mengubah URI request internal)
  │   └── basicauth                     (Proteksi otentikasi dasar HTTP)
  ├── http.matchers/
  │   ├── host                          (Mencocokkan domain request)
  │   ├── path                          (Mencocokkan jalur URL request)
  │   └── method                        (Mencocokkan metode HTTP seperti GET/POST)
  ├── tls.issuance/
  │   ├── acme                          (Negosiasi Let's Encrypt / ZeroSSL)
  │   └── internal                      (Penerbitan CA internal untuk localhost)
  └── tls.storage/
      └── file                          (Menyimpan sertifikat SSL di disk lokal)

Siklus Hidup Penyusunan Modul (Lifecycle Hooks) #

Ketika Caddy memuat konfigurasi baru, modul-modul yang dideklarasikan tidak langsung dijalankan begitu saja. Caddy memaksa setiap modul melewati siklus hidup (lifecycle hooks) yang ketat menggunakan refleksi tipe data Go untuk menjamin bahwa seluruh dependensi modul telah terisi dan siap digunakan secara aman:

  1. Instantiation (Unmarshal): Caddy membaca struktur JSON konfigurasi dan membuat objek memori modul yang sesuai menggunakan parser JSON.
  2. Provisioning (Provision Hook): Jika modul mengimplementasikan interface caddy.Provisioner, Caddy akan memicu fungsi Provision(). Di sini, modul dapat menginisialisasi dependensi internal (seperti membuka koneksi database lokal atau melakukan kompilasi regular expression) dan mengambil referensi ke modul Caddy lainnya.
  3. Validation (Validator Hook): Jika modul mengimplementasikan caddy.Validator, fungsi Validate() akan dipanggil. Di sini modul harus memeriksa apakah konfigurasi yang diberikan sudah benar dan aman (misalnya memastikan port yang dituju valid atau jalur file ada). Jika validasi gagal, seluruh proses perubahan konfigurasi dibatalkan secara otomatis.
  4. Activation (Start Hook): Setelah semua modul lolos validasi, modul akan mulai aktif melayani request atau menjalankan background task.
  5. Destruction (Cleanup Hook): Modul lama yang digantikan akan dipanggil fungsi Cleanup()-nya untuk melepaskan resource secara bersih (seperti menutup port socket atau koneksi file).

Pipeline Pemrosesan Request HTTP #

Setiap request HTTP yang diterima oleh Caddy melewati pipeline pemrosesan yang terdefinisi dengan sangat ketat. Memahami alur request ini akan mempermudah kita mendiagnosis di lapisan mana request kita mengalami masalah.

Aliran pemrosesan request dari klien hingga menghasilkan respons digambarkan dalam diagram berikut:

flowchart TD
      Client["Client (Browser)"] -->|"TCP Connection"| Listener["TLS Listener (SNI Decryption)"]
      Listener -->|"Decrypted Request"| Server["HTTP Server Module"]
      Server -->|"Match Routes"| Routing["Route Matching Engine"]
      Routing -->|"Evaluate Matchers"| Matchers["HTTP Matchers (AND Logic)"]
      
      subgraph Chain ["Handler Chain (Pipeline)"]
          direction TB
          H1["Handler 1: encode (gzip/zstd)"]
          H2["Handler 2: headers"]
          H3["Handler 3: basicauth"]
          H4["Handler 4: reverse_proxy / file_server"]
          
          H1 --> H2
          H2 --> H3
          H3 --> H4
      end
      
      Matchers -->|"All Match"| H1
      H4 -. "Return Response" .-> Client

      style Chain stroke:#43a047,stroke-width:2px

Request didekripsi oleh TLS Listener menggunakan sertifikat yang cocok berdasarkan Server Name Indication (SNI), kemudian diparse oleh HTTP server. Routing engine kemudian mengevaluasi seluruh rute yang dikonfigurasi menggunakan HTTP Matchers secara berurutan.

Mekanisme Middleware Chain di Caddy #

Di dalam Caddy, rantai pemrosesan (Handler Chain) disusun menggunakan pola middleware Go yang sangat efisien. Setiap handler diwakili oleh interface caddyhttp.MiddlewareHandler yang membungkus pemrosesan request:

// Representasi logis interface handler di Caddy
type MiddlewareHandler interface {
    ServeHTTP(w http.ResponseWriter, r *http.Request, next Handler) error
}

Request dikirimkan ke handler pertama. Handler pertama dapat memproses request, lalu memanggil next.ServeHTTP() untuk meneruskan request ke handler berikutnya di dalam rantai. Ketika handler terakhir (misalnya reverse_proxy atau file_server) memproduksi respons, respons tersebut akan mengalir balik secara terbalik melalui rantai middleware. Hal ini memungkinkan handler seperti encode (kompresi) untuk memodifikasi respons terenkripsi sebelum benar-benar dikirimkan keluar ke soket jaringan TCP.


Urutan Eksekusi Directive Internal #

Satu detail krusial yang sering memicu kebingungan bagi pengguna yang baru bermigrasi dari Nginx atau Apache: Caddy mengeksekusi handler directive berdasarkan urutan prioritas logis internalnya, bukan berdasarkan urutan penulisan baris kode di Caddyfile.

Secara default, Caddyfile compiler akan menyusun ulang directive yang kita tulis ke dalam urutan eksekusi internal berikut (dari prioritas tertinggi ke terendah):

  1. tracing (Pelacakan request terdistribusi)
  2. map (Pemetaan variabel dinamis)
  3. root (Penentuan direktori root dokumen)
  4. vars (Deklarasi variabel internal)
  5. rewrite (URL rewriting internal)
  6. uri (Manipulasi URI tambahan)
  7. try_files (Pengecekan keberadaan file fisik di disk)
  8. basicauth (Proteksi otentikasi dasar HTTP)
  9. forward_auth (Delegasi otentikasi ke service eksternal)
  10. request_header (Modifikasi header request sebelum dikirim ke backend)
  11. rate_limit (Pembatasan jumlah request)
  12. encode (Kompresi respons Gzip/Brotli)
  13. header (Modifikasi header respons sebelum dikirim ke klien)
  14. respond (Menyajikan respons teks langsung ke klien)
  15. reverse_proxy (Meneruskan request ke backend internal)
  16. file_server (Menyajikan file statis dari disk)
  17. templates (Rendering halaman HTML dinamis)

Perhatikan contoh perbandingan penulisan berikut:

# ANTI-PATTERN: Developer mencoba menulis file_server di atas basicauth agar
# file statis disajikan terlebih dahulu tanpa memedulikan otentikasi.
example.com {
    file_server
    basicauth /protected/* {
        Bob JDJhJDE0JFJT...
    }
}

# BENAR: Urutan penulisan di atas tidak akan memengaruhi eksekusi.
# Caddy akan secara otomatis mengeksekusi basicauth (urutan 8) terlebih dahulu
# sebelum file_server (urutan 16) dipanggil.

Jika kita benar-benar perlu mengubah urutan eksekusi default ini untuk kebutuhan kustom yang sangat spesifik, kita dapat menggunakan blok directive route untuk memaksa Caddy mengeksekusi perintah secara linier sesuai urutan penulisan kita.


Certificate Manager: Otomatisasi Siklus Hidup SSL #

Certificate Manager Caddy berjalan secara asinkron di background proses server web untuk mengelola perolehan, penyimpanan, dan perpanjangan sertifikat TLS secara otomatis.

Alur pengambilan keputusan dan negosiasi sertifikat SSL digambarkan pada diagram berikut:

flowchart TD
      Start["Domain Terdeteksi di Config"] --> Check["Cek Storage Lokal"]
      Check -- "Ada & Valid" --> Use["Gunakan Sertifikat"]
      Check -- "Tidak Ada / Expired" --> Policy["Pilih Issuer Policy"]
      
      subgraph Issuers ["Issuer Policy Selection"]
          direction TB
          I1["Let's Encrypt (Default)"]
          I2["ZeroSSL (Fallback)"]
          I3["Internal CA (Local/IP)"]
      end
      
      Policy --> Issuers
      Issuers --> Challenge["Tentukan ACME Challenge"]
      
      subgraph Challenges ["ACME Challenge Types"]
          direction TB
          C1["HTTP-01 (Port 80)"]
          C2["TLS-ALPN-01 (Port 443)"]
          C3["DNS-01 (TXT via API)"]
      end
      
      Challenge --> Challenges
      Challenges --> Request["Minta Sertifikat Baru"]
      Request --> Save["Simpan ke Disk & Memori"]
      Save --> Active["HTTPS Aktif & Siap"]
      
      style Issuers stroke:#ffb300,stroke-width:2px
      style Challenges stroke:#ffb300,stroke-width:2px

Caddy akan memeriksa folder penyimpanan lokal (default berada di $HOME/.local/share/caddy). Jika sertifikat tidak ditemukan atau masa berlakunya kurang dari 30 hari, Caddy akan menghubungi Let’s Encrypt. Jika Let’s Encrypt mengalami limit kuota request (rate limit) atau gangguan jaringan, Caddy akan secara otomatis beralih (fallback) ke ZeroSSL untuk meminta sertifikat alternatif.

Mekanisme Penguncian Terdistribusi (Distributed Locking) #

Jika kita menjalankan beberapa instance Caddy dalam sebuah klaster (misalnya di belakang load balancer eksternal) yang berbagi storage penyimpanan sertifikat yang sama (seperti AWS S3 atau database Redis shared), kita harus mencegah skenario di mana setiap node secara simultan meminta sertifikat SSL untuk domain yang sama ke Let’s Encrypt. Ini akan memicu rate limit CA dengan sangat cepat.

Caddy menyelesaikan masalah ini menggunakan modul penguncian penyimpanan (Storage Locking). Sebelum mengirimkan request ACME baru, node Caddy akan mencoba membuat kunci kunci (lock file atau distributed lock key) pada media storage bersama. Node lain yang melihat keberadaan lock tersebut akan menunda request mereka dan menunggu secara asinkron hingga proses penerbitan sertifikat SSL oleh node pertama selesai, kemudian memuat sertifikat yang berhasil disimpan tersebut langsung ke memori lokal mereka.


Fitur On-Demand TLS untuk Skala Enterprise #

Salah satu keunggulan terbesar Certificate Manager Caddy adalah dukungan fitur On-Demand TLS. Pada server web tradisional, semua nama domain harus dideklarasikan terlebih dahulu di dalam file konfigurasi sebelum server dijalankan.

Dengan On-Demand TLS, Caddy dapat menerbitkan sertifikat SSL secara dinamis saat jabat tangan TLS (TLS Handshake) pertama kali terjadi dari pengunjung baru, tanpa perlu melakukan restart atau reload konfigurasi.

{
    # Konfigurasi opsi On-Demand TLS global
    on_demand_tls {
        # Endpoint API internal yang bertugas memvalidasi domain
        ask https://api.infrastrukturkita.com/v1/validate-domain
        
        # Batasi pembuatan sertifikat untuk mencegah abuse
        interval 1m
        burst 10
    }
}

# Tangkap semua trafik port 443 untuk domain kustom dinamis
:443 {
    tls {
        on_demand
    }
    
    reverse_proxy localhost:8000
}

Pencegahan Serangan Denial of Service (DoS) #

Fitur On-Demand TLS jika tidak diamankan dapat dieksploitasi oleh penyerang untuk melumpuhkan server kita. Penyerang cukup mengirimkan ribuan request HTTPS menggunakan nama domain acak yang diarahkan ke IP server kita. Jika Caddy memproses seluruh request tersebut, disk penyimpanan kita akan penuh oleh sertifikat sampah dan server kita akan diblokir oleh Let’s Encrypt karena melanggar batas kuota request.

Caddy memitigasi serangan DoS ini melalui dua parameter pengamanan:

  1. The ask Endpoint: Caddy mengirimkan HTTP request GET ke endpoint internal kita sebelum memproses penerbitan sertifikat. Jika domain tidak terdaftar di database kita, API kita harus mengembalikan status selain 200 OK, dan jabat tangan TLS akan langsung diputuskan secara kasar oleh Caddy tanpa memicu request ke Let’s Encrypt.
  2. Rate Limiting (Interval & Burst): Membatasi kecepatan pembuatan sertifikat SSL baru dalam rentang waktu tertentu agar CPU server tidak mengalami kelebihan beban akibat kalkulasi kunci enkripsi jabat tangan TLS.

Admin API dan Konfigurasi JSON Native #

Meskipun sebagian besar pengembang lebih akrab dengan sintaks Caddyfile, Caddy sebenarnya tidak mengenali format file konfigurasi ini secara langsung. Caddyfile hanyalah representasi ramah manusia yang akan diterjemahkan (adapt) menjadi dokumen JSON terstruktur saat server dijalankan.

Format JSON terstruktur inilah yang kemudian dikirimkan ke REST Admin API Caddy di port 2019 untuk memodifikasi konfigurasi memori secara dinamis:

# Contoh: Mengambil salinan konfigurasi aktif dalam format JSON native
curl http://localhost:2019/config/ | jq .

Mekanisme Transaksi Konfigurasi Dinamis (Atomic Rollback) #

Saat kita mengirimkan konfigurasi JSON baru ke Admin API Caddy (misalnya melalui POST ke /load atau modifikasi parsial lewat JSON Pointer), Caddy memproses perubahan tersebut dalam satu transaksi atomik yang sangat aman:

  1. Validation Stage: Caddy melakukan parsing terhadap payload JSON yang masuk dan menginisialisasi seluruh modul baru yang terpengaruh. Caddy memicu fungsi Provision() dan Validate() pada setiap modul baru tersebut untuk memastikan tidak ada kesalahan konfigurasi logika atau dependensi yang rusak.
  2. Transaction Rollback (Abort): Jika ada satu saja modul baru yang mengembalikan error selama proses validasi atau penyusunan, transaksi dibatalkan seketika. Seluruh modul baru yang sempat dibuat akan dibersihkan, dan Caddy akan terus melayani request trafik menggunakan modul konfigurasi lama yang sedang berjalan. Tidak ada downtime dan tidak ada kondisi server setengah terkonfigurasi.
  3. Atomic Swap: Jika seluruh modul baru lolos validasi, Caddy akan melakukan swap pointer memori internal secara instan (hot-swap). Trafik request baru akan diarahkan ke modul baru, sedangkan koneksi aktif pada modul lama akan dibiarkan selesai memproses request mereka secara alami sebelum modul lama dimatikan secara bersih.

Ringkasan #

  • Arsitektur Berbasis Modul — Core Caddy bertindak sebagai runtime minimalis, sedangkan seluruh fitur web server terpasang di atasnya sebagai modul dengan namespace terstruktur.
  • Pipeline Request Teratur — Alur request HTTP melewati TLS Listener, HTTP Server, Route Matchers, dan Handler Chain secara terprediksi.
  • Prioritas Directive Internal — Urutan eksekusi directive ditentukan oleh logika prioritas internal Caddy, bukan berdasarkan urutan penulisan kita di file Caddyfile.
  • Certificate Manager Background — Mengelola siklus hidup SSL (Let’s Encrypt / ZeroSSL) di background, dengan dukungan perpanjangan otomatis 30 hari sebelum kedaluwarsa.
  • On-Demand TLS Dinamis — Mampu menerbitkan sertifikat SSL baru secara real-time saat jabat tangan TLS pertama kali diterima dari domain yang belum didaftarkan.
  • API-First & JSON Native — Konfigurasi internal berupa JSON terstruktur yang dapat diakses dan diubah secara instan lewat REST API port 2019 tanpa downtime.

← Sebelumnya: Caddy vs Apache   Berikutnya: Instalasi Ubuntu / Debian →

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