Config API

Config API #

Kemampuan Caddy untuk beradaptasi dengan perubahan infrastruktur secara dinamis terwujud secara maksimal melalui Config API. Berbeda dengan metode pengelolaan server konvensional yang memperlakukan konfigurasi sebagai dokumen statis yang kaku, Config API dari Caddy memungkinkan kita memanipulasi setiap simpul (node) dalam pohon konfigurasi secara langsung dan granular menggunakan protokol RESTful standar. Dengan memanggil endpoint HTTP menggunakan berbagai metode seperti GET, POST, PUT, PATCH, dan DELETE, kita dapat menambah domain baru, memperbarui daftar server backend (upstream), mengubah header keamanan, atau menghapus layanan tertentu secara real-time tanpa memutus satu pun koneksi aktif pengguna. Kita akan membahas secara mendalam pemetaan semantik metode HTTP, cara memanipulasi data konfigurasi, skrip otomatisasi deployment terintegrasi, orkestrasi multi-tenant menggunakan Node.js, serta metode penanganan kesalahan (error handling) secara terprogram.


HTTP Methods dan Semantiknya di Caddy #

Untuk mengelola konfigurasi Caddy secara efektif melalui Config API, kita harus memahami bagaimana Caddy menerjemahkan metode HTTP standar ke dalam operasi manipulasi data pada pohon konfigurasi memorinya. Caddy mengikuti prinsip REST yang sangat konsisten:

+----------------------------------------------------------------------------------+
|                            Semantik Metode HTTP di Caddy                         |
+----------------------------------------------------------------------------------+
|  METODE  | PATH URL                  | OPERASI INTERNAL DI MEMORI CADDY          |
|----------|---------------------------|-------------------------------------------|
|  GET     | /config/[path]            | Membaca data simpul atau array            |
|  POST    | /config/[path] (ke array) | Menambahkan (append) elemen baru ke array  |
|  PUT     | /config/[path]            | Mengganti (replace) atau membuat simpul   |
|  PATCH   | /config/[path] (ke objek) | Menggabungkan (merge) properti baru       |
|  DELETE  | /config/[path]            | Menghapus simpul, properti, atau indeks   |
+----------------------------------------------------------------------------------+

Aturan Penting Perbedaan POST dan PUT #

Salah satu kesalahan paling umum saat berinteraksi dengan Config API adalah tertukarnya penggunaan metode POST dan PUT:

  • POST: Digunakan khusus untuk menambahkan (append) elemen baru ke dalam suatu daftar (array). Misalnya, ketika kita mengirim request POST ke endpoint /config/apps/http/servers/srv0/routes/, Caddy akan menyisipkan objek rute baru tersebut di bagian paling akhir dari array rute yang sudah ada.
  • PUT: Digunakan untuk menimpa secara total (replace) nilai dari suatu properti atau objek pada path tertentu. Jika path tersebut belum ada, PUT akan membuatnya. Jika kita mengirim PUT ke path array, seluruh isi array lama akan dihapus dan diganti dengan data baru yang kita kirim.

Membaca Konfigurasi (GET) #

Metode GET digunakan untuk menginspeksi kondisi konfigurasi aktif saat ini. Kita dapat membaca seluruh dokumen konfigurasi secara utuh, atau menargetkan simpul tertentu secara sangat spesifik menggunakan struktur jalur JSON (JSON Path).

Berikut adalah contoh-contoh perintah curl untuk membaca konfigurasi Caddy, lengkap dengan visualisasi pemotongan data menggunakan utilitas jq:

# 1. Membaca seluruh berkas konfigurasi Caddy
curl -s http://localhost:2019/config/ | jq .

# 2. Menginspeksi aplikasi apa saja yang sedang aktif berjalan
curl -s http://localhost:2019/config/apps/ | jq 'keys'
# Contoh Output: ["http", "tls"]

# 3. Membaca konfigurasi server HTTP secara mendalam
curl -s http://localhost:2019/config/apps/http/servers/ | jq .

# 4. Membaca alamat port dengar (listen port) yang aktif pada server srv0
curl -s http://localhost:2019/config/apps/http/servers/srv0/listen/ | jq .
# Contoh Output: [":443", ":80"]

# 5. Membaca daftar rute (routing table) secara spesifik
curl -s http://localhost:2019/config/apps/http/servers/srv0/routes/ | jq .

Pembacaan konfigurasi secara spesifik ini sangat berguna dalam otomatisasi sistem karena menghemat lebar pita (bandwidth) jaringan dan mempermudah pemrosesan data di sisi aplikasi pengontrol kita.


Menambah Rute Baru secara Dinamis (POST) #

Skenario produksi yang paling sering memanfaatkan Config API adalah platform Software-as-a-Service (SaaS) multi-tenant atau aplikasi web dinamis yang perlu mendaftarkan nama domain (host) pelanggan baru ke server web secara instan tanpa mengganggu lalu lintas pelanggan lain yang sedang berjalan.

Dengan menggunakan metode POST ke arah array rute, kita dapat menambahkan entri domain baru beserta backend tujuan proxy-nya secara aman:

# Mengirimkan objek rute baru menggunakan metode POST
curl -s -X POST http://localhost:2019/config/apps/http/servers/srv0/routes/ \
  -H "Content-Type: application/json" \
  -d '{
    "@id": "site-pelanggan-baru",
    "match": [
      {
        "host": ["pelangganbaru.example.com"]
      }
    ],
    "handle": [
      {
        "handler": "subroute",
        "routes": [
          {
            "handle": [
              {
                "handler": "reverse_proxy",
                "upstreams": [
                  {"dial": "localhost:8081"}
                ]
              }
            ]
          }
        ]
      }
    ],
    "terminal": true
  }'

Mengapa Kita Harus Menggunakan terminal: true? #

Di dalam modul HTTP Caddy, rute dievaluasi secara berurutan dari atas ke bawah. Secara bawaan, Caddy akan terus mengevaluasi rute berikutnya bahkan setelah menemukan kecocokan (match). Dengan menetapkan "terminal": true pada objek rute di atas, kita menginstruksikan Caddy untuk segera menghentikan evaluasi rute lainnya begitu domain pelangganbaru.example.com cocok. Ini menghemat siklus CPU server dan mencegah konflik dengan aturan rute umum di bawahnya.

Begitu request HTTP POST ini berhasil dieksekusi dengan status 200 OK, Caddy secara instan langsung mengaktifkan rute tersebut. Jika ada pengguna mengakses pelangganbaru.example.com, Caddy akan langsung meneruskannya ke port 8081 dan secara otomatis menginisialisasi pembuatan sertifikat SSL/TLS untuk domain tersebut di latar belakang.


Mengubah Upstream secara Real-Time (PUT/PATCH) #

Ketika aplikasi backend kita melakukan proses skala dinamis (auto-scaling)—misalnya, bertambahnya container baru akibat lonjakan beban—kita perlu memperbarui daftar server tujuan proxy di Caddy agar lalu lintas terbagi rata.

1. Mengubah Seluruh Upstream pada Rute Tertentu (PUT) #

Jika kita mengetahui letak indeks rute kita (misalnya rute pertama di indeks 0), kita dapat menggunakan metode PUT untuk mengganti array upstreams lama secara total:

# ANTI-PATTERN: Menggunakan jalur indeks numerik yang rentan bergeser
curl -s -X PUT \
  "http://localhost:2019/config/apps/http/servers/srv0/routes/0/handle/0/routes/0/handle/0/upstreams" \
  -H "Content-Type: application/json" \
  -d '[
    {"dial": "10.0.1.10:3000"},
    {"dial": "10.0.1.11:3000"},
    {"dial": "10.0.1.12:3000"}
  ]'

2. Mengubah Upstream secara Aman Menggunakan ID Unik (PATCH) #

Menggunakan indeks numerik seperti contoh di atas sangat rentan memicu kegagalan jika ada skrip lain yang menyisipkan rute baru di depan sehingga posisinya bergeser. Solusi terbaik yang sangat direkomendasikan adalah memanfaatkan pengalamatan ID unik (@id) dikombinasikan dengan metode PATCH untuk melakukan penggabungan data secara terfokus:

# BENAR: Menggunakan ID unik dan PATCH untuk modifikasi presisi
curl -s -X PATCH http://localhost:2019/id/site-pelanggan-baru \
  -H "Content-Type: application/json" \
  -d '{
    "handle": [
      {
        "handler": "subroute",
        "routes": [
          {
            "handle": [
              {
                "handler": "reverse_proxy",
                "upstreams": [
                  {"dial": "10.0.1.20:3000"},
                  {"dial": "10.0.1.21:3000"}
                ]
              }
            ]
          }
        ]
      }
    ]
  }'

Dengan metode PATCH ke /id/site-pelanggan-baru, Caddy hanya akan memperbarui struktur properti yang kita kirimkan di dalam objek rute ber-ID tersebut, tanpa menyentuh properti lain seperti kriteria kecocokan domain (host matchers) yang sudah aktif berjalan.


Menghapus Rute secara Aman (DELETE) #

Ketika masa sewa pelanggan habis atau suatu aplikasi dinonaktifkan, kita harus menghapus rute tersebut dari memori Caddy agar tidak mengonsumsi sumber daya komputasi. Kita dapat mengirimkan request dengan metode DELETE ke endpoint ID unik rute tersebut:

# Menghapus rute pelanggan secara instan dan aman dari memori
curl -s -X DELETE http://localhost:2019/id/site-pelanggan-baru

Dengan menggunakan pengalamatan berbasis ID, Caddy akan mencari objek ber-ID tersebut di manapun ia berada di dalam pohon konfigurasi, lalu menghapusnya secara instan dan merapikan kembali indeks array yang ditinggalkan secara otomatis.


Skrip Otomatisasi Deployment Terintegrasi #

Untuk mengintegrasikan Caddy ke dalam alur kerja CI/CD atau orkestrasi container Docker, kita dapat membuat skrip deployment otomatis. Skrip di bawah ini menunjukkan cara merilis versi aplikasi baru (rolling release) secara zero-downtime dengan memanfaatkan verifikasi kesehatan (health check) container dan pembaruan upstream Caddy secara dinamis:

#!/bin/bash
# zero-downtime-deploy.sh
# Skrip deployment zero-downtime terintegrasi dengan Caddy Config API

set -e

APP_NAME="$1"          # Contoh: "web-app"
NEW_IMAGE="$2"         # Contoh: "node-app:v2.0"
DOMAIN="$3"            # Contoh: "app.example.com"
NEW_PORT="$4"          # Port target untuk container baru (misal: 8082)

CADDY_API="http://localhost:2019"
ROUTE_ID="${APP_NAME}-route"

echo "[1/5] Memulai pembuatan container baru..."
docker run -d \
  --name "${APP_NAME}-new" \
  -p "${NEW_PORT}:3000" \
  --network app-network \
  "${NEW_IMAGE}"

echo "[2/5] Menunggu container baru siap menerima lalu lintas (Health Check)..."
# Melakukan polling ke endpoint kesehatan aplikasi di port baru selama maksimal 60 detik
for i in {1..30}; do
    STATUS=$(curl -sf "http://localhost:${NEW_PORT}/healthz" \
        -o /dev/null -w "%{http_code}" 2>/dev/null || echo "0")
    if [ "$STATUS" = "200" ]; then
        echo "  ✓ Container baru sehat dan siap!"
        break
    fi
    if [ "$i" -eq 30 ]; then
        echo "  ✗ ERROR: Timeout pemeriksaan kesehatan tercapai. Membatalkan deployment."
        docker rm -f "${APP_NAME}-new"
        exit 1
    fi
    echo "  Menunggu aplikasi siap... ($i/30)"
    sleep 2
done

echo "[3/5] Memperbarui arah upstream Caddy secara dinamis..."
# Kita menggunakan PATCH ke endpoint ID untuk mengganti target upstream secara instan
curl -s -f -X PATCH "${CADDY_API}/id/${ROUTE_ID}" \
  -H "Content-Type: application/json" \
  -d "{
    \"handle\": [{
      \"handler\": \"subroute\",
      \"routes\": [{
        \"handle\": [{
          \"handler\": \"reverse_proxy\",
          \"upstreams\": [{\"dial\": \"localhost:${NEW_PORT}\"}],
          \"health_checks\": {
            \"active\": {
              \"uri\": \"/healthz\",
              \"interval\": \"10s\",
              \"timeout\": \"3s\"
            }
          }
        }]
      }]
    }]
  }"

echo "  ✓ Target upstream Caddy berhasil dialihkan ke port ${NEW_PORT}"

echo "[4/5] Mengosongkan koneksi aktif pada container lama (Connection Draining)..."
# Memberikan jeda waktu agar request yang sedang diproses di container lama selesai dikerjakan
sleep 10

echo "[5/5] Membersihkan container lama..."
docker rm -f "${APP_NAME}-old" 2>/dev/null || true
docker rename "${APP_NAME}-old" "${APP_NAME}-trash" 2>/dev/null || true
docker rename "${APP_NAME}-new" "${APP_NAME}-old"
docker rm -f "${APP_NAME}-trash" 2>/dev/null || true

echo "✓ Proses deployment selesai secara zero-downtime!"

Dalam skrip di atas, kita melakukan mitigasi kesalahan secara proaktif. Jika container baru gagal melewati uji kesehatan (health check), skrip langsung berhenti dan menghapus container baru tersebut tanpa pernah menyentuh konfigurasi Caddy yang sedang berjalan. Hal ini menjamin lalu lintas pengguna tetap terarah ke container lama yang stabil.


Manipulasi Header HTTP secara Dinamis #

Selain mengarahkan lalu lintas upstream, kita sering kali perlu menambahkan atau menghapus header HTTP pada respons secara dinamis—misalnya untuk mengaktifkan aturan keamanan tambahan atau menghapus header bawaan framework backend yang membocorkan informasi sensitif.

Berikut adalah contoh menambahkan penanganan header (headers handler) ke dalam rute Caddy secara dinamis melalui API:

# Menambahkan header Strict-Transport-Security dan menghapus header Server
curl -s -X POST \
  "http://localhost:2019/config/apps/http/servers/srv0/routes/0/handle/0/routes" \
  -H "Content-Type: application/json" \
  -d '{
    "@id": "headers-keamanan-global",
    "handle": [{
      "handler": "headers",
      "response": {
        "set": {
          "Strict-Transport-Security": ["max-age=31536000; includeSubDomains; preload"],
          "X-Frame-Options": ["SAMEORIGIN"],
          "X-Content-Type-Options": ["nosniff"]
        },
        "delete": ["Server", "X-Powered-By", "X-AspNet-Version"]
      }
    }]
  }'

Dengan menyisipkan handler headers ini di bagian atas rantai rute (routing chain), Caddy akan secara otomatis memproses manipulasi header ini sebelum meneruskan atau mengembalikan respons ke pengguna.


Orkestrasi Multi-Site menggunakan Node.js #

Untuk platform SaaS atau sistem manajemen berbasis web, kita dapat menulis modul pengontrol Caddy menggunakan bahasa pemrograman seperti JavaScript/Node.js.

Berikut adalah contoh modul pengontrol lengkap yang menggunakan pustaka axios untuk menambah, memperbarui, dan menghapus domain pelanggan secara terprogram:

// caddy-orchestrator.js
// Modul orkestrasi Caddy Config API untuk platform SaaS

const axios = require('axios');

const CADDY_API = 'http://localhost:2019';

/**
 * Mendaftarkan domain tenant baru di Caddy
 * @param {string} tenantId - ID unik pelanggan
 * @param {string} domain - Domain kustom pelanggan
 * @param {number} backendPort - Port lokal aplikasi pelanggan
 */
async function registerTenant(tenantId, domain, backendPort) {
    const routePayload = {
        "@id": `tenant-${tenantId}`,
        "match": [{ "host": [domain] }],
        "handle": [{
            "handler": "subroute",
            "routes": [{
                "handle": [{
                    "handler": "reverse_proxy",
                    "upstreams": [{ "dial": `localhost:${backendPort}` }],
                    "health_checks": {
                        "active": {
                            "uri": "/healthz",
                            "interval": "15s",
                            "timeout": "3s"
                        }
                    }
                }]
            }]
        }],
        "terminal": true
    };

    try {
        const response = await axios.post(
            `${CADDY_API}/config/apps/http/servers/srv0/routes/`,
            routePayload,
            { headers: { 'Content-Type': 'application/json' } }
        );
        if (response.status === 200) {
            console.log(`✓ Tenant [${tenantId}] berhasil terdaftar dengan domain: ${domain}`);
        }
    } catch (error) {
        console.error(`✗ Gagal mendaftarkan tenant [${tenantId}]:`, error.response ? error.response.data : error.message);
    }
}

/**
 * Menghapus domain tenant dari Caddy
 * @param {string} tenantId - ID unik pelanggan yang akan dihapus
 */
async function unregisterTenant(tenantId) {
    try {
        const response = await axios.delete(`${CADDY_API}/id/tenant-${tenantId}`);
        if (response.status === 200) {
            console.log(`✓ Domain tenant [${tenantId}] berhasil dihapus dari memori Caddy.`);
        }
    } catch (error) {
        console.error(`✗ Gagal menghapus tenant [${tenantId}]:`, error.response ? error.response.data : error.message);
    }
}

/**
 * Mengubah port backend upstream untuk tenant yang ada
 * @param {string} tenantId - ID unik pelanggan
 * @param {number} newPort - Port backend baru
 */
async function updateTenantBackend(tenantId, newPort) {
    try {
        // 1. Ambil data konfigurasi rute tenant saat ini
        const getResponse = await axios.get(`${CADDY_API}/id/tenant-${tenantId}`);
        const currentRoute = getResponse.data;

        // 2. Modifikasi properti target dial pada struktur data JSON
        currentRoute.handle[0].routes[0].handle[0].upstreams[0].dial = `localhost:${newPort}`;

        // 3. Kirim kembali konfigurasi yang diperbarui menggunakan PUT ke endpoint ID
        const putResponse = await axios.put(
            `${CADDY_API}/id/tenant-${tenantId}`,
            currentRoute,
            { headers: { 'Content-Type': 'application/json' } }
        );

        if (putResponse.status === 200) {
            console.log(`✓ Port backend tenant [${tenantId}] berhasil dialihkan ke: ${newPort}`);
        }
    } catch (error) {
        console.error(`✗ Gagal memperbarui backend tenant [${tenantId}]:`, error.response ? error.response.data : error.message);
    }
}

// ==========================================
// Contoh Simulasi Eksekusi Orkestrasi:
// ==========================================
async function runDemo() {
    console.log("Memulai simulasi orkestrasi...");
    await registerTenant('user-100', 'client-a.myplatform.com', 8081);
    await registerTenant('user-200', 'client-b.myplatform.com', 8082);
    
    // Simulasi pembaruan backend server
    await updateTenantBackend('user-100', 9091);
    
    // Simulasi penghapusan layanan
    await unregisterTenant('user-200');
}

runDemo();

Modul Node.js di atas memberikan gambaran bagaimana kita dapat mengintegrasikan Caddy secara total ke dalam sistem administrasi backend aplikasi kita, menghilangkan kebutuhan untuk menyentuh terminal server secara manual.


Penanganan Kesalahan (Error Handling) secara Terprogram #

Ketika membangun sistem otomatisasi menggunakan Config API, kita wajib menangani kesalahan secara defensif. Caddy mengembalikan kode status HTTP kesalahan (HTTP error status code) yang presisi disertai dengan payload JSON yang menjelaskan penyebab kegagalan:

1. Kesalahan 400 Bad Request (JSON Rusak / Skema Salah) #

Jika payload JSON yang kita kirimkan memiliki kesalahan ketik sintaksis atau properti yang tidak sesuai dengan spesifikasi skema modul Caddy, Caddy akan menolak request tersebut dan mengembalikan detail error:

# Mengirim JSON yang sengaja dirusak sintaksisnya
curl -i -X POST http://localhost:2019/config/apps/http/servers/srv0/routes/ \
  -H "Content-Type: application/json" \
  -d '{ "match": [ { "host": "app.com" } ] }' # Seharusnya host bernilai array string, bukan string biasa

Caddy akan mengembalikan respons kesalahan seperti berikut:

HTTP/1.1 400 Bad Request
Content-Type: application/json
Date: Tue, 16 Jun 2026 10:45:00 GMT
Connection: close

{"error":"loading new config: http app: server srv0: route 0: matching host: decoding host value: json: cannot unmarshal string into Go struct field host of type []string"}

2. Kesalahan 404 Not Found (Path Tidak Dikenal) #

Jika kita mengirimkan request GET/POST/PUT ke jalur path yang tidak terdaftar di pohon konfigurasi aktif saat ini, Caddy akan memberikan respons 404:

# Mengakses simpul server yang tidak pernah ada
curl -i -X GET http://localhost:2019/config/apps/http/servers/server-fiktif/

Keluaran respons dari Caddy:

HTTP/1.1 404 Not Found
Content-Type: application/json

{"error":"unknown object key: server-fiktif"}

Taktik Validasi Status Code di Skrip Otomatisasi #

Untuk memastikan sistem orkestrasi kita tidak melanjutkan proses ke tahap berikutnya jika Caddy menolak pembaruan konfigurasi, kita harus selalu menangkap kode status respons HTTP dari panggilan API:

# Menangkap kode status HTTP dari response API
HTTP_RESPONSE=$(curl -s -w "%{http_code}" -o response.json \
  -X POST http://localhost:2019/config/apps/http/servers/srv0/routes/ \
  -H "Content-Type: application/json" \
  -d @route.json)

if [ "$HTTP_RESPONSE" -ne 200 ]; then
    ERROR_MSG=$(jq -r '.error' response.json)
    echo "✗ ERROR: Kegagalan API Caddy (HTTP $HTTP_RESPONSE)!"
    echo "Detail Kesalahan: $ERROR_MSG"
    rm -f response.json
    exit 1
fi

echo "✓ Konfigurasi Caddy berhasil diperbarui!"
rm -f response.json

Ringkasan #

  • Semantik Metode HTTP: Config API mematuhi prinsip REST dengan ketat: GET membaca, POST menyisipkan ke array, PUT mengganti total, PATCH menggabungkan properti, dan DELETE menghapus simpul.
  • Keunggulan @id: Selalu gunakan properti unik @id pada objek konfigurasi yang sering dikelola agar terhindar dari pergeseran indeks numerik yang memicu kegagalan logika skrip.
  • Otomatisasi SaaS: Gunakan orkestrasi dinamis menggunakan Node.js/Python untuk mendaftarkan domain pelanggan secara otomatis begitu mereka melakukan registrasi pada platform kita.
  • Dampak Perubahan Sementara: Modifikasi konfigurasi yang dilakukan melalui Config API bersifat langsung (in-memory). Pastikan aplikasi pengontrol kita menyimpan status konfigurasi tersebut di database eksternal untuk persistensi jangka panjang.
  • Penanganan Error Ketat: Selalu validasi kode respons HTTP (mengharapkan status 200 OK) dan baca string properti "error" pada JSON respons untuk mendiagnosis kegagalan validasi skema.
  • Terminal Rute Cepat: Setel parameter terminal: true pada rute dinamis agar Caddy langsung menghentikan pencarian rute lain begitu menemukan domain pelanggan yang cocok.

← Sebelumnya: Admin Endpoint   Berikutnya: Reload Config →

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