Modern web altyapılarında tek bir sunucu üzerinde onlarca farklı servis çalıştırmak artık olağan bir durum. Microservice mimarileri, API gateway’ler, statik site hosting ve reverse proxy kombinasyonları… Bunların hepsini tek bir yapılandırma dosyasında yönetmek ciddi bir karmaşıklık yaratıyor. İşte tam bu noktada Caddy’nin handle ve route direktifleri devreye giriyor. Bu iki direktif, trafik yönlendirme mantığını son derece okunabilir ve esnek bir şekilde kurmanıza imkan tanıyor. Bu yazıda gerçek dünya senaryoları üzerinden bu direktifleri derinlemesine inceleyeceğiz.
Caddy’nin Yönlendirme Mantığını Anlamak
Caddy’yi ilk kez yapılandırmaya başlayanların en çok kafasının karıştığı konu, direktiflerin nasıl önceliklendirildiğidir. Nginx’te location bloklarına alışkın olanlar için Caddy’nin yaklaşımı başta biraz garip gelebilir.
Caddy, Caddyfile’daki direktifleri yazıldıkları sıraya göre değil, kendi iç öncelik sırasına göre işler. Bu tasarım kararı aslında çok mantıklı; çünkü yazım sırasına bağımlı yapılandırmalar bakımı zorlaştırır ve ince hatalara davetiye çıkarır. Ancak bu durum, bazen istediğiniz sırayı zorla belirlemeniz gerektiğinde sorun yaratır. İşte route direktifi tam olarak bu problemi çözüyor.
handle direktifi ise bir tür “eğer bu path’e gelirse, şunu yap ve dur” mantığı kurmanızı sağlıyor. Nginx’teki location bloklarına en yakın Caddy karşılığı olarak düşünebilirsiniz.
Handle Direktifi: Temel Kullanım
handle direktifinin en basit formu bir path matcher ile birlikte kullanılır. Bir isteği yakaladığınızda, o blok içindeki direktifler uygulanır ve Caddy başka bir handle bloğuna geçmez.
# /etc/caddy/Caddyfile
example.com {
handle /api/* {
reverse_proxy localhost:8080
}
handle /static/* {
root * /var/www/static
file_server
}
handle {
reverse_proxy localhost:3000
}
}
Burada dikkat edilmesi gereken önemli bir nokta var: En sonda yer alan handle bloğu herhangi bir path matcher almamış. Bu, “diğer hiçbir blok bu isteği yakalamamışsa buraya gel” anlamına geliyor. Nginx’teki location / ile aynı rolü üstleniyor.
Handle ile Varsayılan Fallback Mantığı
Gerçek bir senaryoya bakalım. Diyelim ki bir e-ticaret sitesi yönetiyorsunuz. Ana frontend React uygulaması, /api altı Go ile yazılmış bir backend, /admin ise ayrı bir Node.js uygulaması. Tüm bunları tek bir Caddyfile’da şöyle yönetebilirsiniz:
shop.example.com {
# Admin paneli - sadece belirli IP'lerden erişilebilir
handle /admin* {
@allowed_ips remote_ip 10.0.0.0/8 192.168.1.0/24
handle @allowed_ips {
reverse_proxy localhost:4000
}
handle {
respond "Erisim Reddedildi" 403
}
}
# API backend
handle /api/* {
header Cache-Control no-store
reverse_proxy localhost:8080
}
# Statik dosyalar
handle /assets/* {
root * /var/www/shop/dist
file_server {
precompressed gzip br
}
}
# React SPA - tüm diğer istekler
handle {
root * /var/www/shop/dist
try_files {path} /index.html
file_server
}
}
Bu yapıda /admin bloğu içinde iç içe handle kullandığımıza dikkat edin. Bu Caddy’nin güçlü özelliklerinden biri; handle bloklarını birbirine geçirebiliyorsunuz.
Route Direktifi: Sıralı İşleme
route direktifi, içindeki direktiflerin kesinlikle yazıldıkları sırada işlenmesini garantiler. Bu, Caddy’nin normal öncelik sistemini bypass etmenin resmi yolu.
Neden buna ihtiyaç duyarsınız? Şöyle bir senaryo düşünün: Hem rewrite hem de reverse_proxy kullanmak istiyorsunuz. Caddy’nin normal akışında reverse_proxy‘nin önce çalışması gerekebilir, ama siz URL’i önce rewrite edip sonra proxy’lemek istiyorsunuz.
api-gateway.example.com {
route /v1/* {
# Önce URL'i dönüştür
rewrite /v1/* /api/v1{path}
# Sonra proxy'le
reverse_proxy localhost:8081
}
route /v2/* {
rewrite /v2/* /api/v2{path}
reverse_proxy localhost:8082
}
route /v3/* {
rewrite /v3/* /api/v3{path}
reverse_proxy localhost:8083
}
}
Route ile Middleware Zinciri
route direktifinin gerçek gücü, middleware zinciri oluştururken ortaya çıkıyor. Bir request’in belirli adımlardan sırayla geçmesini istediğinizde bu yapı çok işe yarıyor:
secure-api.example.com {
route /api/* {
# 1. Adım: Rate limiting
rate_limit {
zone api_zone {
key {remote_host}
events 100
window 1m
}
}
# 2. Adım: JWT doğrulama header'ı ekle
header X-Real-IP {remote_host}
header X-Forwarded-Proto {scheme}
# 3. Adım: İsteği backend'e ilet
reverse_proxy {
to localhost:9000
lb_policy round_robin
health_uri /health
health_interval 10s
}
}
# API dışındaki her şeyi reddet
route {
respond "Bu endpoint mevcut degil" 404
}
}
Handle vs Route: Hangisini Ne Zaman Kullanmalı?
Bu iki direktif arasındaki seçim çoğu zaman kafa karıştırıcı olabiliyor. Temel ayrımı şöyle özetleyebilirim:
- handle: Birbirini dışlayan (mutually exclusive) path’ler için kullanın. Bir blok eşleştiğinde diğerleri çalışmaz.
- route: Sıralı işleme garantisi istediğinizde ve birden fazla direktifin belirli bir sırada çalışması gerektiğinde kullanın.
Pratik bir karşılaştırma yapalım. Aynı senaryoyu her iki yöntemle yazalım:
# Handle ile yaklaşım
example.com {
handle /api/* {
reverse_proxy localhost:8080
}
handle /docs/* {
reverse_proxy localhost:8081
}
handle {
file_server
}
}
# Route ile aynı senaryo
example.com {
route {
handle /api/* {
reverse_proxy localhost:8080
}
handle /docs/* {
reverse_proxy localhost:8081
}
handle {
file_server
}
}
}
İkinci yaklaşımda route içine handle blokları koyduk. Bu kombinasyon çok yaygın kullanılıyor ve sıralama ile karşılıklı dışlama özelliklerini birleştiriyor.
Gerçek Dünya Senaryosu: Multi-Tenant SaaS Uygulaması
Şimdi daha karmaşık bir senaryoya geçelim. Bir SaaS platformu yönettiğinizi düşünün. Her müşterinin farklı bir subdomain’i var, bazı müşteriler premium plan kullanıyor ve farklı servislere erişebiliyor.
# Ana uygulama
app.saas.com {
route {
# Sağlık kontrolü - her zaman önce
handle /health {
respond "OK" 200
}
# Premium API endpoint'leri
@premium_api {
path /api/premium/*
header X-Customer-Plan premium
}
handle @premium_api {
reverse_proxy premium-api-cluster:8080
}
# Standart API
handle /api/* {
reverse_proxy standard-api-cluster:8080
}
# WebSocket bağlantıları
@websocket {
path /ws/*
header Connection *Upgrade*
header Upgrade websocket
}
handle @websocket {
reverse_proxy ws-server:8090 {
transport http {
versions h1
}
}
}
# Frontend uygulaması
handle {
root * /var/www/saas/dist
try_files {path} /index.html
file_server
}
}
}
# Müşteri subdomain'leri
*.saas.com {
route {
handle /api/* {
reverse_proxy tenant-api:8080 {
header_up X-Tenant-ID {labels.3}
}
}
handle {
root * /var/www/saas/dist
file_server
}
}
}
Bu yapıda {labels.3} placeholder’ı subdomain adını alıp backend’e header olarak iletir. Tenant izolasyonunu bu şekilde Caddy seviyesinde yapabilirsiniz.
Hata Yönetimi ile Handle_Errors
handle_errors direktifi, yönlendirme hataları oluştuğunda devreye giren özel bir handle türüdür. Bu direktif sayesinde custom hata sayfaları sunabilir veya hataları başka bir servise yönlendirebilirsiniz.
example.com {
handle /api/* {
reverse_proxy localhost:8080
}
handle {
root * /var/www/html
file_server
}
handle_errors {
@404 expression {err.status_code} == 404
handle @404 {
root * /var/www/html
rewrite * /404.html
file_server
}
@5xx expression {err.status_code} >= 500
handle @5xx {
root * /var/www/html
rewrite * /500.html
file_server
}
handle {
respond "Bir hata olustu: {err.status_code}" {err.status_code}
}
}
}
Named Matcher’lar ile Handle Kombinasyonu
Caddy’de @isim syntax’ıyla named matcher tanımlayabilirsiniz. Bu matcher’ları handle direktifleriyle kombinleyince çok güçlü kurallar yazabilirsiniz:
media.example.com {
# Resim optimizasyonu isteyen istekler
@image_request {
path *.jpg *.jpeg *.png *.webp
query optimize=true
}
# Bot trafiği
@bot_traffic {
header_regexp User-Agent (bot|crawler|spider|scraper)
}
# Mobil cihazlar
@mobile {
header_regexp User-Agent (Android|iPhone|iPad|Mobile)
}
route {
# Bot trafiğini hızlıca yanıtla
handle @bot_traffic {
respond "Robot erisimi kisitlidir" 403
}
# Resim optimizasyonu
handle @image_request {
reverse_proxy image-optimizer:3000
}
# Mobil CDN
handle @mobile {
reverse_proxy mobile-cdn:8080
}
# Varsayılan
handle {
root * /var/www/media
file_server {
precompressed gzip br
}
}
}
}
Karmaşık Reverse Proxy Senaryosu: Blue-Green Deployment
Blue-green deployment yaparken Caddy’nin yönlendirme direktifleri hayat kurtarıcı olabiliyor. Traffic’i yeni sürüme kademeli olarak geçirebilirsiniz:
app.example.com {
# Yeni sürümü test eden kullanıcılar
@beta_testers {
cookie beta_access=true
}
# Canary release - %10 traffic
@canary_traffic {
expression {rand.float} < 0.1
}
route {
# Beta test kullanıcıları yeni sürüme
handle @beta_testers {
header X-App-Version green
reverse_proxy green-app:8080
}
# Canary traffic yeni sürüme
handle @canary_traffic {
header X-App-Version green
reverse_proxy green-app:8080
}
# Geri kalan tüm traffic eski sürüme
handle {
header X-App-Version blue
reverse_proxy blue-app:8080
}
}
}
Bu yapıda {rand.float} placeholder’ı 0 ile 1 arasında rastgele bir float değer üretiyor. Böylece yaklaşık %10 traffic yeni sürüme yönlendiriliyor.
Path Traversal ve Güvenlik Yapılandırması
Güvenlik açısından kritik uygulamalarda handle direktifleri ile katmanlı koruma oluşturabilirsiniz:
secure.example.com {
route {
# Hassas dosyalara erişimi engelle
@sensitive_files {
path *.env *.git* *.htaccess */.DS_Store */config.yml */secrets*
}
handle @sensitive_files {
respond "Yasakli dosya" 403
}
# Yönetim paneli - sadece VPN üzerinden
@admin_no_vpn {
path /admin/*
not remote_ip 10.8.0.0/24
}
handle @admin_no_vpn {
respond "VPN baglantisi gerekli" 403
}
# Normal admin erişimi
handle /admin/* {
basicauth {
admin $2a$14$hashlenmisBcryptSifresi
}
reverse_proxy admin-panel:4000
}
# Public API - rate limit ile
handle /api/public/* {
reverse_proxy public-api:8080
}
# Geri kalan
handle {
root * /var/www/html
file_server
}
}
}
Performans Optimizasyonu: Caching Katmanı
Handle direktiflerini cache mantığıyla birleştirerek performans optimizasyonu yapabilirsiniz:
cdn.example.com {
route {
# Statik assetler - uzun süreli cache
@static_assets {
path /assets/* /fonts/* /images/*
file
}
handle @static_assets {
header Cache-Control "public, max-age=31536000, immutable"
header Vary Accept-Encoding
root * /var/www/static
file_server {
precompressed gzip br
}
}
# API yanıtları - kısa süreli cache
@api_cacheable {
method GET
path /api/public/*
}
handle @api_cacheable {
header Cache-Control "public, max-age=60, stale-while-revalidate=30"
reverse_proxy api-server:8080
}
# Dinamik içerik - cache yok
handle {
header Cache-Control no-store
reverse_proxy app-server:3000
}
}
}
Logging ve Debugging için Handle Yapılandırması
Production ortamında sorun giderirken spesifik path’ler için detaylı loglama aktif etmeniz gerekebilir:
example.com {
log {
output file /var/log/caddy/access.log
format json
level INFO
}
route {
# Sorunlu endpoint için debug loglama
handle /problematic-endpoint* {
log {
output file /var/log/caddy/debug.log
format json
level DEBUG
}
reverse_proxy debug-backend:8080
}
# API için ayrı log dosyası
handle /api/* {
log {
output file /var/log/caddy/api.log
format json
}
reverse_proxy api-backend:8080
}
handle {
file_server
}
}
}
Yaygın Hatalar ve Çözümleri
Handle ve route direktiflerini kullanırken en sık karşılaşılan sorunları bilmek, hata ayıklama sürecinizi çok kısaltır.
Hatalı sıralama sorunu: handle bloklarında daha spesifik path’leri önce yazın. /api/v1/users path’ini /api/‘dan önce tanımlarsanız Caddy doğru şekilde çalışır, ama tersi durumda /api/ her şeyi yakalayacaktır.
Matcher çakışması: İki handle bloğu aynı path’i yakalıyorsa ilk eşleşen kazanır. Bu nedenle route içinde handle kullanırken sıralamaya dikkat edin.
handle_path vs handle farkı: handle_path direktifi path’i otomatik olarak strip eder. Yani /api/users isteği handle_path /api/* bloğuna girdiğinde backend /users olarak görür. Bunu bilinçli kullanın:
example.com {
# handle_path - prefix'i siler, backend /users görür
handle_path /api/* {
reverse_proxy localhost:8080
}
# handle - prefix kalır, backend /api/users görür
handle /static/* {
root * /var/www
file_server
}
}
Sonuç
Caddy’nin handle ve route direktifleri, modern web altyapılarının karmaşık yönlendirme ihtiyaçlarını karşılamak için tasarlanmış güçlü araçlardır. handle ile birbirini dışlayan path kuralları oluştururken, route ile işlem sırası üzerinde tam kontrol sahibi olursunuz.
Bu yazıda ele aldığımız senaryolar, gerçek prodüksiyon ortamlarında karşılaşacağınız durumların büyük bölümünü kapsıyor. Blue-green deployment’tan multi-tenant SaaS mimarisine, güvenlik katmanlamadan performans optimizasyonuna kadar bu direktifler her durumda esneklik sunuyor.
Pratik tavsiye olarak şunu söyleyeyim: Basit senaryolarda sadece handle kullanın, yeterli. Direktif sırasının önemli olduğu durumlarda route ekleyin. Karmaşık iç içe kurallarda route içinde handle kombinasyonu en okunabilir ve bakımı en kolay yapıyı oluşturuyor. Caddy’nin caddy validate komutunu yapılandırma değişikliklerinizden önce mutlaka çalıştırın; sözdizimi hatalarını üretime almadan yakalamanın en kolay yolu bu.