Arsitektur Caddy

Arsitektur Caddy #

Memahami arsitektur internal Caddy bukan sekadar pengetahuan akademis. Ketika kamu debug masalah konfigurasi yang membingungkan, menulis konfigurasi lanjutan yang tidak ada contohnya, atau mencoba memahami mengapa Caddy berperilaku berbeda dari yang kamu ekspektasikan — pemahaman arsitektur adalah yang membuat perbedaan antara menemukan solusi dalam 5 menit atau 5 jam.

Caddy v2 dibangun di atas satu prinsip desain yang konsisten: semua komponen adalah modul yang dapat di-compose, dan konfigurasi adalah data JSON yang hidup dan bisa dimanipulasi kapan saja. Prinsip ini terdengar abstrak, tapi begitu kamu memahaminya secara konkret, semua keputusan desain Caddy akan terasa masuk akal.

Gambaran Arsitektur Keseluruhan #

Caddy terdiri dari beberapa lapisan yang bekerja bersama secara terkoordinasi:

┌────────────────────────────────────────────────────────────┐
│                       Caddy Process                        │
│                                                            │
│  ┌──────────────────┐    ┌────────────────────────────┐   │
│  │   Admin API      │    │       HTTP App             │   │
│  │   :2019          │◄──►│   :80 (HTTP)               │   │
│  │   REST API       │    │   :443 (HTTPS)             │   │
│  └──────────────────┘    └────────────────────────────┘   │
│           │                          │                     │
│           │          ┌───────────────▼──────────────┐     │
│           │          │        Module Registry        │     │
│           └─────────►│   http.handlers.*             │     │
│                      │   tls.issuance.*              │     │
│                      │   dns.providers.*             │     │
│                      │   tls.storage.*               │     │
│                      └───────────────────────────────┘     │
│                                  │                         │
│           ┌──────────────────────▼──────────────────────┐  │
│           │          Certificate Manager (TLS App)      │  │
│           │  ┌──────────────┐ ┌──────────┐ ┌────────┐  │  │
│           │  │  ACME Client │ │ Storage  │ │ Renew  │  │  │
│           │  │  (Let's Enc) │ │ (disk)   │ │ Timer  │  │  │
│           │  └──────────────┘ └──────────┘ └────────┘  │  │
│           └────────────────────────────────────────────┘  │
└────────────────────────────────────────────────────────────┘

Setiap kotak dalam diagram di atas adalah modul — bukan kode hardcoded yang tidak bisa diganti. Bahkan HTTP App, TLS App, dan Certificate Manager adalah modul yang mengimplementasikan interface standar dan mendaftarkan diri ke registry global.


Sistem Modul dan Namespace #

Ini adalah fondasi dari seluruh arsitektur Caddy v2. Semua fungsionalitas diimplementasikan sebagai modul Go yang:

  1. Mendaftarkan diri ke registry global saat program start (init())
  2. Memiliki ID unik dalam format namespace hierarkis
  3. Mengimplementasikan interface tertentu sesuai perannya
  4. Bisa di-serialize ke dan di-deserialized dari JSON

Namespace menggunakan titik sebagai separator dan mencerminkan hierarki fungsional:

Namespace lengkap modul Caddy (tidak exhaustive):

apps/
  http                          → "http" — HTTP server app
  tls                           → "tls" — TLS/certificate manager app

http.handlers.*                 → Handler yang memproses HTTP request
  http.handlers.static_response → Kembalikan response statis langsung
  http.handlers.reverse_proxy   → Forward request ke upstream
  http.handlers.file_server     → Sajikan file dari filesystem
  http.handlers.encode          → Kompres response (gzip, zstd)
  http.handlers.rewrite         → Modifikasi URI request
  http.handlers.redirect        → Redirect ke URL lain
  http.handlers.basicauth       → HTTP Basic Authentication
  http.handlers.headers         → Manipulasi request/response headers
  http.handlers.templates       → Render template HTML
  http.handlers.request_body    → Batasi/manipulasi request body
  http.handlers.map             → Peta nilai ke variabel

http.matchers.*                 → Matcher untuk mencocokkan request
  http.matchers.host            → Cocokkan berdasarkan host/domain
  http.matchers.path            → Cocokkan berdasarkan URL path
  http.matchers.method          → Cocokkan berdasarkan HTTP method
  http.matchers.header          → Cocokkan berdasarkan header
  http.matchers.remote_ip       → Cocokkan berdasarkan IP
  http.matchers.query           → Cocokkan berdasarkan query string
  http.matchers.expression      → Cocokkan dengan CEL expression

tls.issuance.*                  → Issuer/CA untuk mendapat sertifikat
  tls.issuance.acme             → ACME protocol (Let's Encrypt, dsb)
  tls.issuance.internal         → CA internal Caddy (untuk localhost)
  tls.issuance.zerossl          → ZeroSSL CA

tls.storage.*                   → Tempat menyimpan sertifikat
  tls.storage.file              → Simpan di filesystem lokal
  (redis, consul, dll via plugin)

dns.providers.*                 → DNS API untuk DNS-01 challenge
  dns.providers.cloudflare      → Cloudflare DNS API
  dns.providers.route53         → AWS Route 53
  dns.providers.digitalocean    → DigitalOcean DNS
  (20+ provider lainnya via plugin)

Konsistensi namespace ini bukan hanya estetika. Ini berarti:

  • Dokumentasi bisa digenerate secara otomatis karena setiap modul punya schema yang terdefinisi
  • Plugin bisa diintegrasikan dengan mulus karena mengikuti interface yang sama
  • Kamu bisa memprediksi nama modul berdasarkan kategorinya

Pipeline Pemrosesan Request HTTP #

Setiap request HTTP yang masuk ke Caddy melewati pipeline yang sudah terdefinisi dengan jelas. Memahami pipeline ini sangat penting untuk debugging dan untuk menulis konfigurasi yang benar.

Request dari browser/client
          │
          ▼ TCP connection diterima
┌─────────────────────────┐
│     TLS Listener        │
│  - Dekripsi TLS         │
│  - Identifikasi SNI     │  ← Server Name Indication menentukan
│  - Pilih sertifikat     │    sertifikat mana yang digunakan
└──────────┬──────────────┘
           │
           ▼ Request didekripsi
┌─────────────────────────┐
│     HTTP Server         │
│  - Parse HTTP request   │
│  - Method, URI, headers │
│  - Body streaming       │
└──────────┬──────────────┘
           │
           ▼ Request siap diproses
┌─────────────────────────┐
│     Route Matching      │
│  - Iterasi semua routes │
│  - Temukan route yang   │  ← Routes diurutkan secara spesifik-ke-umum
│    cocok pertama kali   │
└──────────┬──────────────┘
           │
           ▼ Route ditemukan
┌─────────────────────────┐
│     Matchers            │
│  - host matcher         │
│  - path matcher         │  ← Semua matcher dalam satu route
│  - method matcher       │    harus cocok (AND logic)
│  - header matcher       │
│  - remote_ip matcher    │
└──────────┬──────────────┘
           │ Semua matcher cocok
           ▼
┌─────────────────────────────────────────────────────────┐
│                   Handler Chain                         │
│                                                         │
│  Handler 1 (encode)                                     │
│      │ panggil handler berikutnya                       │
│      ▼                                                  │
│  Handler 2 (headers)                                    │
│      │ panggil handler berikutnya                       │
│      ▼                                                  │
│  Handler 3 (basicauth)                                  │
│      │ jika auth berhasil, panggil handler berikutnya   │
│      ▼                                                  │
│  Handler 4 (reverse_proxy / file_server / respond)      │
│      │ generate response                                │
│      ▼                                                  │
│  (Response mengalir balik melalui chain)                │
└──────────────────────────────────────────────────────────┘
           │
           ▼
Response dikirim ke client

Urutan Eksekusi Handler yang Penting #

Ini adalah detail yang sangat sering menjadi sumber kebingungan: Caddy mengeksekusi handler dalam urutan yang sudah ditentukan secara internal, bukan berdasarkan urutan penulisan di Caddyfile.

Urutan eksekusi yang sebenarnya (dari pertama ke terakhir):

1.  tracing          → Distributed tracing
2.  map              → Pemetaan nilai ke variabel
3.  root             → Set direktori root filesystem
4.  vars             → Set variabel
5.  rewrite          → Modifikasi URI
6.  uri              → Manipulasi URI tambahan
7.  try_files        → Test keberadaan file
8.  basicauth        → HTTP Basic Authentication
9.  forward_auth     → Autentikasi ke service eksternal
10. request_header   → Modifikasi header request
11. rate_limit       → Rate limiting (via plugin)
12. copy_response_headers → Copy response headers
13. encode           → Kompresi (gzip, zstd)
14. push             → HTTP/2 Server Push
15. header           → Set response headers
16. respond          → Kembalikan response langsung
17. metrics          → Endpoint Prometheus metrics
18. reverse_proxy    → Forward ke upstream
19. file_server      → Sajikan file statis
20. php_fastcgi      → Proxy ke PHP-FPM (shorthand)
21. templates        → Render template
# ANTI-PATTERN: Developer berharap basicauth berjalan sebelum encode
# Tapi urutan di Caddyfile tidak menentukan urutan eksekusi
example.com {
    encode gzip          # Ditulis pertama
    basicauth /admin/* { # Ditulis kedua
        user JDJhJDE0JFJT...
    }
    file_server
}

# BENAR: Urutan di atas sudah benar secara eksekusi, karena
# Caddy otomatis menjalankan basicauth (urutan 9) SEBELUM encode (urutan 13)
# Kamu TIDAK perlu mengatur urutan penulisan untuk menentukan ini

Pemahaman tentang urutan ini penting karena menjelaskan mengapa konfigurasi Caddyfile yang terlihat “salah urutan” sebenarnya bekerja dengan benar.


Certificate Manager — Detail Cara Kerjanya #

Certificate Manager adalah salah satu komponen paling canggih dan paling sering dipuji di Caddy. Ia berjalan sepenuhnya di background dan mengelola siklus hidup sertifikat secara otomatis tanpa intervensi manual.

Saat Domain Baru Terdeteksi #

Domain "example.com" muncul di konfigurasi saat startup atau reload
                    │
                    ▼
        Cek storage lokal (default: $HOME/.local/share/caddy)
                    │
        ┌───────────┴────────────┐
        │                        │
   Sertifikat ada            Tidak ada
   dan valid                      │
        │                         ▼
        │              Pilih issuer berdasarkan policy:
        │              1. Let's Encrypt (default, produksi)
        │              2. ZeroSSL (fallback otomatis)
        │              3. Internal CA (untuk localhost/IP)
        │              4. ACME CA kustom (jika dikonfigurasi)
        │                         │
        │                         ▼
        │              Tentukan ACME challenge type:
        │              - HTTP-01  → Caddy serve token di port 80
        │              - TLS-ALPN-01 → Caddy respond di port 443
        │              - DNS-01   → Tambah TXT record via DNS API
        │                         │
        │                         ▼
        │              Komunikasi dengan CA ACME:
        │              1. Buat account ACME (jika belum ada)
        │              2. Request sertifikat
        │              3. CA memberi challenge
        │              4. Caddy fulfill challenge
        │              5. CA verifikasi dan terbitkan sertifikat
        │              6. Simpan sertifikat ke storage
        │                         │
        └─────────────────────────┘
                    │
                    ▼
        HTTPS aktif untuk domain, koneksi bisa dilayani
                    │
                    ▼
        Background worker berjalan terus:
        - Cek semua sertifikat setiap beberapa jam
        - Mulai renewal ~30 hari sebelum expire
        - Retry jika renewal gagal (dengan exponential backoff)

On-Demand TLS #

Fitur canggih Caddy yang jarang diketahui: On-Demand TLS. Dengan fitur ini, Caddy bisa mendapatkan sertifikat untuk domain yang belum diketahui saat konfigurasi dibuat — sertifikat didapat saat pertama kali ada request untuk domain tersebut.

{
    on_demand_tls {
        # Endpoint yang dikonsultasikan untuk setiap domain baru
        # Harus return 200 jika domain valid, non-200 jika tidak
        ask https://your-api.example.com/check-domain
        
        interval 2m    # Maksimal satu sertifikat baru per 2 menit
        burst 5        # Maksimal 5 sertifikat baru dalam satu burst
    }
}

# Wildcard matcher — terima semua domain
:443 {
    tls {
        on_demand
    }
    reverse_proxy localhost:3000
}

Ini sangat berguna untuk platform multi-tenant di mana kamu tidak tahu domain customer di muka.


Admin API — Konfigurasi sebagai Data Hidup #

Admin API adalah implementasi dari filosofi “konfigurasi adalah data JSON yang hidup”. API ini berjalan di localhost:2019 dan memberikan akses penuh ke seluruh state Caddy yang sedang berjalan.

Struktur URL API #

URL API mencerminkan struktur JSON konfigurasi Caddy secara langsung:

http://localhost:2019/config/
                            │
                            └── apps/
                                  │
                                  ├── http/
                                  │     │
                                  │     └── servers/
                                  │           │
                                  │           └── main/
                                  │                 │
                                  │                 └── routes/
                                  │                       │
                                  │                       ├── [0]  ← Route pertama
                                  │                       └── [1]  ← Route kedua
                                  │
                                  └── tls/
                                        │
                                        └── automation/
                                              │
                                              └── policies/

Navigasi URL ini 1:1 dengan navigasi JSON:

# GET — ambil seluruh konfigurasi
curl http://localhost:2019/config/

# GET — ambil hanya konfigurasi HTTP server
curl http://localhost:2019/config/apps/http/

# GET — ambil route pertama
curl http://localhost:2019/config/apps/http/servers/main/routes/0

# POST — tambah route baru
curl -X POST http://localhost:2019/config/apps/http/servers/main/routes/ \
  -H "Content-Type: application/json" \
  -d '{
    "match": [{"host": ["newsite.com"]}],
    "handle": [{"handler": "file_server", "root": "/var/www/newsite"}]
  }'

# PATCH — update bagian tertentu
curl -X PATCH http://localhost:2019/config/apps/http/servers/main/routes/0 \
  -H "Content-Type: application/json" \
  -d '{"@id": "route-0", "match": [{"host": ["updated.com"]}]}'

# DELETE — hapus route
curl -X DELETE http://localhost:2019/config/apps/http/servers/main/routes/0

# POST /load — load konfigurasi baru dari Caddyfile (replace semua)
curl -X POST http://localhost:2019/load \
  -H "Content-Type: text/caddyfile" \
  --data-binary @/etc/caddy/Caddyfile

# GET — status upstream reverse proxy
curl http://localhost:2019/reverse_proxy/upstreams/

Mengamankan Admin API #

Secara default, Admin API hanya bisa diakses dari localhost. Ini adalah keputusan keamanan yang tepat. Tapi ada beberapa skenario di mana kamu mungkin perlu mengeksposnya:

{
    admin {
        # Default: hanya localhost
        # listen 127.0.0.1:2019

        # Ekspos ke jaringan internal (HATI-HATI)
        listen 10.0.0.1:2019

        # Atau nonaktifkan sama sekali jika tidak dibutuhkan
        # disabled
        
        # Tambahkan basic auth ke Admin API
        # (membutuhkan konfigurasi tambahan)
    }
}
Jangan pernah mengekspos Admin API ke internet publik. Admin API memberikan akses penuh untuk mengubah konfigurasi Caddy termasuk menambahkan reverse proxy ke mana saja, mengubah routing, dan berpotensi mengekspos service internal. Jika perlu diakses dari luar localhost, gunakan firewall rules yang ketat atau SSH tunneling.

Caddyfile sebagai Abstraksi di atas JSON #

Pemahaman yang sangat berguna: Caddyfile adalah bahasa yang dikompilasi ke JSON, bukan format konfigurasi native Caddy.

Caddyfile yang kamu tulis:
────────────────────────
example.com {
    reverse_proxy localhost:3000
    encode gzip
}

        │
        ▼  caddy adapt --config Caddyfile --adapter caddyfile

JSON native Caddy (disederhanakan):
────────────────────────────────────
{
  "apps": {
    "http": {
      "servers": {
        "srv0": {
          "listen": [":443", ":80"],
          "routes": [
            {
              "match": [{"host": ["example.com"]}],
              "handle": [
                {
                  "handler": "subroute",
                  "routes": [
                    {
                      "handle": [
                        {"handler": "encode", "encodings": {"gzip": {}}},
                        {
                          "handler": "reverse_proxy",
                          "upstreams": [{"dial": "localhost:3000"}]
                        }
                      ]
                    }
                  ]
                }
              ],
              "terminal": true
            }
          ]
        }
      }
    },
    "tls": {
      "automation": {
        "policies": [
          {"subjects": ["example.com"]}
        ]
      }
    }
  }
}

Melihat JSON yang dihasilkan sangat berguna untuk debugging:

# Lihat JSON yang akan dieksekusi dari Caddyfile kamu
caddy adapt --config /etc/caddy/Caddyfile --adapter caddyfile | jq .

# Ini juga berguna untuk memahami mengapa konfigurasi tertentu tidak bekerja
# sebagaimana yang diharapkan

Bagaimana Komponen Saling Terhubung #

Untuk melihat gambaran besar, mari kita trace apa yang terjadi dari awal saat kamu menjalankan caddy run --config Caddyfile:

1. Caddy membaca Caddyfile
        │
        ▼
2. Caddyfile adapter mengkonversi ke JSON
        │
        ▼
3. JSON divalidasi — semua module ID dicek apakah terdaftar
        │
        ▼
4. Module registry instantiasi semua modul:
   - HTTP App dibuat dan dikonfigurasi
   - TLS App dibuat dan dikonfigurasi
   - Routes, handlers, matchers dibuat
        │
        ▼
5. HTTP App mulai listen di :80 dan :443
        │
        ▼
6. TLS App mulai Certificate Manager:
   - Scan semua domain dari konfigurasi
   - Untuk setiap domain, cek sertifikat di storage
   - Mulai proses ACME untuk domain tanpa sertifikat
        │
        ▼
7. Admin API mulai listen di :2019
        │
        ▼
8. Caddy siap melayani request

Saat ada request masuk:
        │
        ▼
9. TLS listener terima koneksi, dekripsi TLS
        │
        ▼
10. HTTP server parse request
        │
        ▼
11. Route matching — temukan route yang cocok
        │
        ▼
12. Matchers dievaluasi — semua harus cocok
        │
        ▼
13. Handler chain dieksekusi — encode → auth → proxy → file_server
        │
        ▼
14. Response dikirim kembali ke client

Saat ada perubahan konfigurasi via Admin API:
        │
        ▼
15. JSON baru diterima dan divalidasi
        │
        ▼
16. Modul baru di-instantiasi
        │
        ▼
17. Hot-swap: modul lama diganti modul baru
    tanpa memutus koneksi yang sedang berjalan

Ringkasan #

  • Semua komponen adalah modul dengan namespace hierarkis (http.handlers.reverse_proxy) — bahkan HTTP server dan TLS manager. Modul mendaftarkan diri via init() dan bisa diganti atau diperluas.
  • Pipeline request mengalir: TLS Listener → HTTP Server → Route Matching → Matchers → Handler Chain → Response.
  • Urutan eksekusi handler ditentukan Caddy secara internal berdasarkan hierarki logis — bukan urutan penulisan di Caddyfile.
  • Certificate Manager berjalan sepenuhnya di background: deteksi domain baru, ACME challenge, penyimpanan, dan auto-renewal ~30 hari sebelum expire.
  • On-Demand TLS memungkinkan sertifikat untuk domain yang tidak diketahui saat konfigurasi dibuat — berguna untuk platform multi-tenant.
  • Admin API di :2019 memberikan akses ke konfigurasi JSON yang hidup — URL API mencerminkan struktur JSON secara langsung.
  • Caddyfile adalah abstraksi yang dikompilasi ke JSON native — gunakan caddy adapt untuk melihat JSON yang sebenarnya dieksekusi.

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

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