Matcher

Matcher #

Matcher adalah mekanisme di Caddy yang memungkinkan kamu menerapkan directive hanya pada subset request tertentu — bukan ke semua request yang masuk ke sebuah site. Tanpa matcher, setiap directive berlaku untuk semua request. Dengan matcher, kamu bisa membuat logika seperti: “aktifkan basicauth hanya untuk path /admin/*”, atau “blokir request yang bukan dari IP jaringan internal”, atau “redirect hanya jika request menggunakan method POST dengan header tertentu”.

Matcher adalah fitur yang membuat Caddyfile tetap bersih dan ekspresif bahkan untuk konfigurasi yang sangat kompleks.

Dua Cara Menggunakan Matcher #

1. Named Matcher (Didefinisikan Terpisah, Digunakan Berkali-kali) #

Named matcher didefinisikan dengan blok @nama dan bisa dirujuk dari directive manapun:

example.com {
    # Definisi named matcher
    @admin {
        path /admin/*
    }
    
    @api {
        path /api/*
        method GET POST
    }
    
    @internal {
        remote_ip 10.0.0.0/8 192.168.0.0/16
    }
    
    # Gunakan matcher dalam directive
    basicauth @admin {
        admin $2a$14$hash...
    }
    
    header @api Content-Type "application/json"
    
    respond @internal "Hanya untuk internal" 200
}

2. Inline Matcher (Langsung di Directive) #

Untuk kasus sederhana, matcher bisa ditulis langsung dalam directive:

example.com {
    # Path matcher inline — hanya untuk path /api/*
    reverse_proxy /api/* localhost:8080
    
    # Method matcher inline
    respond /health 200
    
    # Semua request lain
    file_server
}
Gunakan named matcher ketika kondisi yang sama digunakan di beberapa directive — ini menghindari duplikasi dan membuat konfigurasi lebih mudah dipahami. Gunakan inline matcher untuk kondisi sederhana yang hanya digunakan sekali.

Jenis Matcher yang Tersedia #

path — Cocokkan Berdasarkan URL Path #

example.com {
    # Exact match
    @exact path /login
    
    # Prefix match (trailing *) — cocok dengan /api, /api/, /api/users, dsb
    @api path /api/*
    
    # Multiple path dalam satu matcher
    @assets path /images/* /css/* /js/*
    
    # Cocok dengan ekstensi file tertentu
    @phpFiles path *.php
    
    # Glob pattern
    @docs path /**   # ** cocok dengan path separator juga
    
    respond @exact "Halaman login" 200
    reverse_proxy @api localhost:8080
    file_server @assets
}

host — Cocokkan Berdasarkan Hostname #

# Berguna di dalam blok site yang handle multiple domain
example.com, api.example.com, admin.example.com {
    @isApi {
        host api.example.com
    }
    
    @isAdmin {
        host admin.example.com
    }
    
    # Routing berdasarkan host
    reverse_proxy @isApi localhost:8080
    reverse_proxy @isAdmin localhost:9000
    
    # Default untuk example.com
    file_server
}

method — Cocokkan Berdasarkan HTTP Method #

example.com {
    # Single method
    @post method POST
    
    # Multiple methods
    @readOnly method GET HEAD
    
    # OPTIONS untuk CORS preflight
    @preflight method OPTIONS
    
    # Handle preflight request
    header @preflight {
        Access-Control-Allow-Origin "*"
        Access-Control-Allow-Methods "GET, POST, PUT, DELETE"
        Access-Control-Allow-Headers "Content-Type, Authorization"
    }
    respond @preflight "" 204
    
    # Rate limit write operations
    # reverse_proxy @post localhost:8080 ... (dengan rate limit)
}

header — Cocokkan Berdasarkan Request Header #

example.com {
    # Cocok jika header ada (dengan nilai apapun)
    @hasAuth {
        header Authorization *
    }
    
    # Cocok jika header bernilai tepat
    @isJSON {
        header Content-Type application/json
    }
    
    # Cocok jika header mengandung nilai (substring match)
    @isChrome {
        header User-Agent *Chrome*
    }
    
    # Cocok jika header TIDAK ada
    @noCache {
        not header Cache-Control *
    }
    
    # Header dengan wildcard
    @apiRequest {
        header X-API-Version v*
    }
    
    respond @hasAuth "Autentikasi diterima"
}

remote_ip — Cocokkan Berdasarkan IP Client #

example.com {
    # Single IP
    @specificIP {
        remote_ip 203.0.113.42
    }
    
    # CIDR range
    @internal {
        remote_ip 10.0.0.0/8
    }
    
    # Multiple range sekaligus
    @trusted {
        remote_ip 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
    }
    
    # Forwarded IP (dari X-Forwarded-For) — tambahkan 'forwarded'
    # Gunakan ini jika Caddy berada di belakang load balancer lain
    @trustedForwarded {
        remote_ip forwarded 10.0.0.0/8
    }
    
    # Blokir IP tertentu
    @blocked {
        remote_ip 198.51.100.0/24
    }
    respond @blocked 403
}

query — Cocokkan Berdasarkan Query String #

example.com {
    # Cocok jika parameter ada (dengan nilai apapun)
    @hasDebug {
        query debug=*
    }
    
    # Cocok jika parameter bernilai tepat
    @versionV2 {
        query version=2
    }
    
    # Multiple query parameter (keduanya harus ada — AND)
    @specificRequest {
        query page=1 sort=asc
    }
    
    # Handle debug mode
    header @hasDebug X-Debug-Mode "true"
}

protocol — Cocokkan Berdasarkan Protokol #

example.com {
    # Cocok jika menggunakan HTTPS
    @isHTTPS {
        protocol https
    }
    
    # Cocok jika menggunakan HTTP/2
    @isHTTP2 {
        protocol h2
    }
    
    # Cocok jika WebSocket
    @isWS {
        protocol http
        header Upgrade websocket
    }
}

file — Cocokkan Berdasarkan Keberadaan File #

example.com {
    root * /var/www/html
    
    # Cocok jika file yang diminta ADA di filesystem
    @fileExists {
        file {path}
    }
    
    # Cocok jika file TIDAK ada (untuk SPA routing)
    @noFile {
        not file {path}
    }
    
    # Rewrite ke index.html jika file tidak ditemukan
    rewrite @noFile /index.html
    
    file_server
}

expression — Ekspresi CEL (Paling Fleksibel) #

Matcher expression menggunakan CEL (Common Expression Language) untuk kondisi yang sangat kompleks:

example.com {
    # Ekspresi CEL yang fleksibel
    @complexCondition {
        expression {
            # CEL expression
            # Tersedia variabel: host, path, method, headers, remote_ip, dll
            {http.request.method} == "POST" &&
            {http.request.header.Content-Type} == "application/json" &&
            {http.request.uri.path}.startsWith("/api/")
        }
    }
    
    # Contoh lain: cek user-agent untuk bot
    @isBot {
        expression {
            {http.request.header.User-Agent}.contains("bot") ||
            {http.request.header.User-Agent}.contains("crawler") ||
            {http.request.header.User-Agent}.contains("spider")
        }
    }
    
    respond @isBot 403
}

Operator Logika: NOT, AND, OR #

NOT — Negasi Matcher #

example.com {
    # NOT: cocok jika kondisi TIDAK terpenuhi
    @notAdmin {
        not path /admin/*
    }
    
    # NOT untuk IP: blokir semua kecuali internal
    @notInternal {
        not remote_ip 10.0.0.0/8 192.168.0.0/16
    }
    respond @notInternal 403
    
    # NOT dengan multiple kondisi
    @notCached {
        not header Cache-Control no-cache
        not header Pragma no-cache
    }
}

AND — Semua Kondisi Harus Terpenuhi #

Dalam satu blok named matcher, semua kondisi yang ditulis adalah AND — semuanya harus terpenuhi:

example.com {
    # AND: path /api/* DAN method POST DAN ada header Authorization
    @secureApiPost {
        path /api/*
        method POST
        header Authorization *
    }
    
    # AND: IP internal DAN path bukan /public/*
    @internalPrivate {
        remote_ip 10.0.0.0/8
        not path /public/*
    }
}

OR — Salah Satu Kondisi Cukup #

Untuk OR, kamu perlu mendefinisikan multiple named matcher dan menggunakannya secara terpisah, atau menggunakan expression:

example.com {
    # OR menggunakan expression
    @adminOrApi {
        expression {
            {http.request.uri.path}.startsWith("/admin") ||
            {http.request.uri.path}.startsWith("/api")
        }
    }
    
    # Atau: definisikan dua matcher terpisah
    @isAdmin path /admin/*
    @isApi path /api/*
    
    # Terapkan directive yang sama ke keduanya
    header @isAdmin X-Area "admin"
    header @isApi X-Area "api"
}

Pola Matcher untuk Use Case Production #

Routing API vs Frontend #

example.com {
    @api path /api/*
    @static path /static/* /assets/*
    @health path /health /ready /live
    
    # API ke backend
    reverse_proxy @api localhost:8080 {
        header_up X-Real-IP {remote_host}
    }
    
    # Static assets dari CDN atau langsung dari disk
    file_server @static {
        root /var/www/assets
    }
    
    # Health checks — langsung respond tanpa log
    respond @health `{"status":"ok"}` 200
    
    # Semua request lain — SPA frontend
    rewrite * /index.html
    file_server {
        root /var/www/app
    }
}

Keamanan Berlapis #

admin.example.com {
    # Layer 1: Blokir akses dari luar jaringan internal
    @external {
        not remote_ip 10.0.0.0/8 192.168.0.0/16
    }
    respond @external 403
    
    # Layer 2: Blokir path sensitif tertentu
    @forbidden {
        path /.env /config.yaml /secrets/*
    }
    respond @forbidden 404
    
    # Layer 3: Basic auth untuk seluruh admin area
    basicauth {
        admin $2a$14$...
    }
    
    # Baru setelah semua layer aman, teruskan ke backend
    reverse_proxy localhost:9000
}

WebSocket Support #

app.example.com {
    @ws {
        header Upgrade websocket
    }
    
    @http {
        not header Upgrade websocket
    }
    
    # WebSocket dengan timeout yang lebih lama
    reverse_proxy @ws localhost:3000 {
        transport http {
            dial_timeout 5s
            # Tidak ada response_header_timeout untuk WebSocket
        }
    }
    
    # Request HTTP biasa
    reverse_proxy @http localhost:3000
}

Geoblocking (dengan Plugin) #

example.com {
    # Dengan plugin geoip (perlu compile dengan xcaddy)
    @blocked_country {
        expression {
            {geoip.country_code} in ["CN", "RU", "KP"]
        }
    }
    respond @blocked_country 403
    
    reverse_proxy localhost:3000
}

Bot Detection #

example.com {
    @goodBots {
        header User-Agent *Googlebot*
        header User-Agent *Bingbot*
        header User-Agent *DuckDuckBot*
    }
    
    @badBots {
        expression {
            {http.request.header.User-Agent} == "" ||
            {http.request.header.User-Agent}.contains("scrapy") ||
            {http.request.header.User-Agent}.contains("masscan")
        }
    }
    
    # Izinkan good bots lewat
    # Blokir bad bots
    respond @badBots 403
    
    # Semua request lain (termasuk good bots) dilayani normal
    file_server
}

Debug Matcher #

Ketika matcher tidak bekerja sesuai ekspektasi, ada beberapa cara untuk debug:

# Lihat JSON yang dihasilkan dari Caddyfile
# Ini menunjukkan bagaimana matcher di-compile ke JSON internal
caddy adapt --config /etc/caddy/Caddyfile | jq '.apps.http.servers'

# Tambahkan logging sementara untuk melihat apakah matcher bekerja
# di Caddyfile:
# log {
#     output stderr
#     level DEBUG
# }
# Teknik debug: tambahkan respond sementara untuk test matcher
example.com {
    @myMatcher {
        path /test/*
        method GET
    }
    
    # Sementara: respond langsung untuk test apakah matcher bekerja
    respond @myMatcher "Matcher bekerja!" 200
    
    # (setelah test berhasil, ganti dengan konfigurasi yang sebenarnya)
}

Ringkasan #

  • Named matcher (@nama) didefinisikan terpisah dan bisa digunakan berkali-kali — lebih bersih untuk kondisi yang kompleks atau digunakan di banyak tempat.
  • Inline matcher (path langsung di directive) untuk kondisi sederhana yang hanya digunakan sekali.
  • Dalam satu blok named matcher, semua kondisi adalah AND — semuanya harus terpenuhi.
  • Gunakan not untuk negasi, dan expression dengan CEL untuk kondisi OR atau logika yang sangat kompleks.
  • path, method, header, remote_ip adalah matcher yang paling sering digunakan.
  • expression dengan CEL adalah matcher paling fleksibel untuk kondisi yang tidak bisa diekspresikan dengan matcher standar.
  • Debug matcher dengan caddy adapt untuk melihat JSON output atau tambahkan respond sementara untuk test.

← Sebelumnya: Directive   Berikutnya: Snippet & Import →

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