Caddyfile Sözdizimi Tam Rehberi ve Direktifler

Caddy web sunucusunu ilk kez gördüğünde “bu kadar mı basit?” diye düşünürsün. Sonra biraz daha derine inince anlarsın ki basitlik, güçten ödün vermeden elde edilmiş. Caddyfile sözdizimi tam da bu felsefeyi yansıtıyor: okunabilir, sezgisel ama bir o kadar da esnek. Bu yazıda Caddyfile’ın tüm anatomisini inceleyeceğiz, gerçek dünya senaryolarıyla direktifleri pekiştireceğiz ve sık yapılan hataların üzerinden geçeceğiz.

Caddyfile’ın Temel Yapısı

Caddyfile, Caddy’nin ana yapılandırma dosyasıdır ve genellikle /etc/caddy/Caddyfile yolunda bulunur. Nginx’in nginx.confu veya Apache’nin httpd.confu gibi düşünebilirsin ama çok daha az gürültüyle.

Bir Caddyfile’ın iskelet yapısı şöyle görünür:

# Global seçenekler bloğu (opsiyonel)
{
    email [email protected]
    admin off
}

# Site bloğu
example.com {
    root * /var/www/html
    file_server
}

Temel kavramlar:

  • Site adresi: Her bloğun başında gelir, hangi alan adı veya IP için kuralların geçerli olduğunu belirtir
  • Direktifler: Site bloğunun içinde yer alır, sunucunun nasıl davranacağını tanımlar
  • Global blok: { ile başlar, site adı olmadan, tüm sunucu genelindeki ayarları barındırır
  • Etiketler (snippets): Tekrar eden yapılandırmaları tek yerde toplamanı sağlar

Site Adresi Sözdizimi

Site adresi birçok farklı formatta yazılabilir. Bu konuda Caddy oldukça esnek davranıyor.

# Sadece alan adı - HTTPS otomatik devreye girer
example.com {
    respond "Merhaba Dünya"
}

# Şema ile birlikte
https://example.com {
    file_server
}

# HTTP'yi zorlamak için
http://example.com {
    file_server
}

# Wildcard alan adı
*.example.com {
    file_server
}

# IP adresi ve port
:8080 {
    respond "Port 8080 üzerinden çalışıyor"
}

# Localhost
localhost {
    file_server
}

Birden fazla alan adını aynı blokta ele almak istersen virgülle ayırırsın:

example.com, www.example.com {
    root * /var/www/html
    file_server
}

Global Blok Seçenekleri

Global blok, tüm site bloklarından önce gelir ve sunucu genelinde geçerli olan ayarları içerir. Boş küme parantezleri {} ile tanımlanır.

{
    # Let's Encrypt için e-posta adresi
    email [email protected]
    
    # Admin API'yi kapat (prodüksyon için önerilir)
    admin off
    
    # Varsayılan SNI
    default_sni example.com
    
    # HTTP portu (varsayılan: 80)
    http_port 8080
    
    # HTTPS portu (varsayılan: 443)
    https_port 8443
    
    # Sertifika depolama dizini
    storage file_system {
        root /var/lib/caddy/certificates
    }
    
    # Debug modu
    debug
    
    # Let's Encrypt staging sunucusu (test için)
    acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}

Prodüksiyon ortamında admin off direktifini kullanman önemli. Varsayılan olarak Caddy, localhost:2019 üzerinde bir API sunucu açıyor. Bu API üzerinden yapılandırma değiştirilebiliyor, güvenlik açısından bunu kapatmak iyi bir pratik.

Temel Direktifler

root ve file_server

Statik dosya sunmak için en çok kullanılan iki direktif bunlar.

example.com {
    # Dosya kökünü belirle
    root * /var/www/html
    
    # Statik dosya sunucusunu etkinleştir
    file_server
    
    # Dizin listelemeyi etkinleştirmek için
    file_server browse
}

root direktifindeki * işareti bir matcher (eşleştirici). Tüm isteklerin bu kök dizinden servis edilmesini söylüyor. İleride matcher konusuna ayrıntılı döneceğiz.

reverse_proxy

Caddy’nin en güçlü özelliklerinden biri reverse proxy işlemleri. Node.js, Python veya Java uygulamalarını önüne koyduğunda hem HTTPS hem de proxy işlemini hallediveriyor.

api.example.com {
    reverse_proxy localhost:3000
}

Birden fazla backend ve yük dengeleme:

app.example.com {
    reverse_proxy {
        to localhost:3001
        to localhost:3002
        to localhost:3003
        
        # Round-robin (varsayılan)
        lb_policy round_robin
        
        # Sağlık kontrolü
        health_path /health
        health_interval 30s
        
        # Zaman aşımı ayarları
        transport http {
            dial_timeout 5s
            response_header_timeout 30s
        }
    }
}

Yük dengeleme politikaları:

  • round_robin: Sırayla her backend’e istek gönderir
  • least_conn: En az bağlantıya sahip backend’i seçer
  • ip_hash: İstemci IP’sine göre sabit backend atar
  • random: Rastgele backend seçer
  • first: Listedeki ilk sağlıklı backend’i kullanır

respond

Basit yanıtlar döndürmek için kullanılır. Test ve hızlı kurulumlar için çok işlevsel.

example.com {
    respond /health "OK" 200
    respond /version "v1.0.0" 200
    respond "Site bakımda" 503
}

redir

Yönlendirme işlemleri için kullanılır.

www.example.com {
    redir https://example.com{uri} permanent
}

# HTTP'den HTTPS'e yönlendirme
http://example.com {
    redir https://example.com{uri} permanent
}

{uri} nedir? Caddy’nin yerleşik değişkenlerinden (placeholders) biri. İstek yolunu ve query string’i tam olarak kopyalar. Yani http://example.com/blog?page=2 adresi https://example.com/blog?page=2 adresine doğru şekilde yönlenir.

Matcher Sistemi

Matcher sistemi Caddyfile’ı gerçekten güçlü kılan özellik. Direktiflerin belirli koşullarda çalışmasını sağlar.

example.com {
    # Belirli bir yol için
    handle /api/* {
        reverse_proxy localhost:3000
    }
    
    # Admin yollarını engelle
    respond /admin/* "Yetkisiz" 403
    
    # Belirli dosya uzantıları
    @images {
        path *.jpg *.jpeg *.png *.gif *.webp
    }
    
    header @images Cache-Control "public, max-age=2592000"
}

Named matcher syntax (isimli eşleştirici): @isim formatıyla tanımlanır ve birden fazla direktifte tekrar kullanılabilir.

example.com {
    @authenticated {
        header Authorization *
        method POST PUT DELETE PATCH
    }
    
    @static {
        path /static/*
        not method POST PUT DELETE
    }
    
    route @authenticated {
        reverse_proxy localhost:3000
    }
    
    header @static Cache-Control "public, max-age=86400"
}

Matcher türleri:

  • path: URL yoluna göre eşleştirir, wildcard * destekler
  • method: HTTP metoduna göre eşleştirir (GET, POST, PUT…)
  • header: İstek başlığına göre eşleştirir
  • query: Query parametresine göre eşleştirir
  • remote_ip: İstemci IP adresine göre eşleştirir
  • not: Koşulun tersini alır
  • expression: CEL (Common Expression Language) ifadesi kullanır

Header Yönetimi

HTTP başlıklarını yönetmek için header direktifi kullanılır.

example.com {
    # Başlık ekle
    header X-Frame-Options DENY
    header X-Content-Type-Options nosniff
    header X-XSS-Protection "1; mode=block"
    
    # Başlık sil
    header -Server
    
    # Güvenlik başlıkları paketi
    header {
        Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
        Content-Security-Policy "default-src 'self'"
        Referrer-Policy "strict-origin-when-cross-origin"
        Permissions-Policy "geolocation=(), microphone=()"
        
        # Sunucu bilgisini gizle
        -Server
    }
}

Encode ve Sıkıştırma

Bant genişliğini azaltmak ve performansı artırmak için gzip veya zstd sıkıştırması:

example.com {
    encode {
        zstd
        gzip 6
        
        # Sıkıştırılacak minimum boyut
        minimum_length 1024
        
        # Hangi içerik türleri sıkıştırılsın
        match {
            header Content-Type text/*
            header Content-Type application/json*
            header Content-Type application/javascript*
        }
    }
    
    file_server
}

Log Yapılandırması

Erişim loglarını yapılandırmak için log direktifi:

example.com {
    log {
        output file /var/log/caddy/access.log {
            roll_size 100mb
            roll_keep 5
            roll_keep_for 720h
        }
        
        format json {
            time_format "iso8601"
        }
        
        level INFO
    }
}

JSON log formatı kullanmak neden iyi? ELK Stack, Grafana Loki veya benzeri log analiz araçlarına kolayca entegre edebilirsin. jq ile terminal üzerinden de rahatça parse edilebiliyor.

TLS Yapılandırması

Caddy’nin en övülen özelliği otomatik HTTPS. Ama bazen elle TLS ayarı yapman gerekebilir.

example.com {
    # Kendi sertifikanı kullan
    tls /path/to/cert.pem /path/to/key.pem
    
    # Belirli Let's Encrypt hesabı
    tls [email protected]
    
    # Gelişmiş TLS ayarları
    tls {
        protocols tls1.2 tls1.3
        
        # Cipher suite'leri belirle (TLS 1.2 için)
        ciphers TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        
        # Client sertifikası doğrulama
        client_auth {
            mode require_and_verify
            trusted_ca_cert_file /etc/caddy/client-ca.pem
        }
    }
}

Wildcard sertifika için DNS challenge kullanmak gerekiyor. Cloudflare örneği:

*.example.com {
    tls {
        dns cloudflare {env.CLOUDFLARE_API_TOKEN}
    }
    
    file_server
}

Snippet’lar ve Import

Kod tekrarını önlemek için snippet’lar hayat kurtarır. Özellikle birden fazla site bloğu olan ortamlarda.

# Snippet tanımla
(common_headers) {
    header {
        X-Frame-Options DENY
        X-Content-Type-Options nosniff
        Strict-Transport-Security "max-age=31536000"
        -Server
    }
    encode gzip
}

(proxy_to_app) {
    reverse_proxy {args[0]} {
        health_path /health
        health_interval 30s
    }
}

# Snippet kullan
example.com {
    import common_headers
    import proxy_to_app localhost:3000
    log {
        output file /var/log/caddy/example.log
    }
}

api.example.com {
    import common_headers
    import proxy_to_app localhost:4000
}

{args[0]}, snippet’a geçirilen ilk argümanı temsil ediyor. {args[1]}, {args[2]} şeklinde devam eder.

Ayrı dosyalardaki yapılandırmaları dahil etmek için:

# /etc/caddy/Caddyfile
{
    email [email protected]
}

import /etc/caddy/sites/*.caddy

Gerçek Dünya Senaryo: WordPress Sunucusu

PHP-FPM ile çalışan bir WordPress kurulumu:

wordpress.example.com {
    root * /var/www/wordpress
    
    # PHP dosyalarını PHP-FPM'e gönder
    php_fastcgi unix//run/php/php8.2-fpm.sock
    
    # Güvenlik
    @sensitive {
        path /wp-config.php
        path /.env
        path /xmlrpc.php
    }
    respond @sensitive 403
    
    # WordPress kalıcı bağlantıları için
    @wordpress {
        not file
        not path /wp-admin/*
    }
    rewrite @wordpress /index.php?{query}
    
    # Statik dosyalar
    file_server
    
    # Sıkıştırma
    encode gzip
    
    # Güvenlik başlıkları
    header {
        X-Frame-Options SAMEORIGIN
        X-Content-Type-Options nosniff
        -Server
    }
    
    # Erişim logu
    log {
        output file /var/log/caddy/wordpress.log
    }
}

Gerçek Dünya Senaryo: Mikroservis API Gateway

Birden fazla mikroservisin önünde Caddy’yi API gateway olarak kullanmak:

{
    email [email protected]
}

(cors_headers) {
    header {
        Access-Control-Allow-Origin "https://app.company.com"
        Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
        Access-Control-Allow-Headers "Authorization, Content-Type"
        Access-Control-Max-Age "86400"
    }
}

(rate_limit_check) {
    # Gerçek prodüksiyonda caddy-rate-limit eklentisi kullanılır
    # Burada IP bazlı basit kontrol
    @blocked_ips {
        remote_ip 192.168.100.0/24
    }
    respond @blocked_ips 429
}

api.company.com {
    import cors_headers
    import rate_limit_check
    
    # OPTIONS preflight isteklerini yakala
    @options method OPTIONS
    respond @options 204
    
    # User servisi
    handle /v1/users/* {
        reverse_proxy {
            to user-service:8001
            to user-service-2:8001
            lb_policy least_conn
            health_path /health
        }
    }
    
    # Ürün servisi
    handle /v1/products/* {
        reverse_proxy product-service:8002
    }
    
    # Sipariş servisi
    handle /v1/orders/* {
        reverse_proxy order-service:8003
    }
    
    # Bilinmeyen rotalar
    handle {
        respond "Endpoint bulunamadı" 404
    }
    
    log {
        output file /var/log/caddy/api-gateway.log
        format json
    }
}

handle ve route Direktifleri

handle ve route direktifleri karmaşık yönlendirme mantığı için kullanılır.

handle: Eşleşen ilk bloğu çalıştırır, diğerlerini atlar (mutex davranışı). route: Direktiflerin sırasını kesin olarak belirler, Caddy’nin iç sıralama mantığını geçersiz kılar.

example.com {
    handle /static/* {
        root * /var/www/static
        file_server
    }
    
    handle /api/* {
        reverse_proxy localhost:3000
    }
    
    # Yukarıdaki handle bloklarından hiçbiri eşleşmezse
    handle {
        root * /var/www/html
        file_server
    }
}

route kullanımı:

example.com {
    route {
        # Sıra önemli: önce auth kontrolü
        forward_auth localhost:4181 {
            uri /auth/verify
            copy_headers X-User-Id X-User-Role
        }
        
        # Sonra proxy
        reverse_proxy localhost:3000
    }
}

Yerleşik Değişkenler (Placeholders)

Caddy’nin güçlü bir placeholder sistemi var. Direktiflerin içinde dinamik değerler kullanmanı sağlıyor.

Sık kullanılan placeholder’lar:

  • {uri}: Tam URI (yol + query string)
  • {path}: Sadece URL yolu
  • {query}: Query string
  • {host}: İstekteki Host başlığı
  • {method}: HTTP metodu
  • {remote_host}: İstemci IP adresi
  • {scheme}: http veya https
  • {header.X-Custom}: Belirli bir istek başlığının değeri
  • {time.now.unix}: Unix timestamp
  • {env.VARIABLE_NAME}: Ortam değişkeni
example.com {
    # Ortam değişkeninden API anahtarı
    header X-API-Version {env.API_VERSION}
    
    # Host'a göre farklı backend
    reverse_proxy {header.X-Target-Host}
    
    # Loglarda özel format
    log {
        format json {
            message_key "msg"
            level_key "level"
        }
    }
}

Yapılandırmayı Test Etme ve Yeniden Yükleme

# Sözdizimi kontrolü
caddy validate --config /etc/caddy/Caddyfile

# Yapılandırmayı yeniden formatla (otomatik düzeltme)
caddy fmt --overwrite /etc/caddy/Caddyfile

# Caddy'yi yeniden başlatmadan yeniden yükle (graceful reload)
caddy reload --config /etc/caddy/Caddyfile

# systemd ile
sudo systemctl reload caddy

# Mevcut yapılandırmayı görüntüle (JSON formatında)
caddy adapt --config /etc/caddy/Caddyfile

# Caddy'yi doğrudan test modunda başlat
caddy run --config /etc/caddy/Caddyfile --watch

--watch bayrağı geliştirme ortamında çok kullanışlı. Dosya değişikliklerini izleyerek Caddy’yi otomatik yeniden yüklüyor.

Sık Yapılan Hatalar

1. root direktifini yanlış kullanmak root /var/www/html yerine root /var/www/html yazmak gerekiyor. Matcher () zorunlu.

2. Handle sırası handle blokları mutex davranışı gösterir. En spesifik yolları en üste yaz.

3. TLS ile HTTP karışıklığı http:// şemasını açıkça belirtmezsen Caddy HTTPS’i otomatik devreye alır. Sadece HTTP’ye ihtiyaç duyduğun senaryolarda şemayı açık yaz.

4. PHP-FPM socket yolu php_fastcgi unix//run/php/php8.2-fpm.sock yazımında unix// kısmını atlama. İkili slash // Unix socket’i ifade ediyor.

5. Snippet argümanları {args.0} eski sözdizimi, yeni versiyonlarda {args[0]} kullanılıyor.

Sonuç

Caddyfile sözdizimi ilk bakışta çok basit görünebilir ama bu basitliğin altında son derece düşünülmüş bir tasarım yatıyor. Matcher sistemi, snippet yapısı, placeholder’lar ve direktiflerin modüler yapısı bir araya gelince karmaşık senaryoları bile temiz ve okunabilir şekilde ifade edebiliyorsun.

Nginx’ten geçiş yapıyorsan özellikle reverse proxy ve TLS otomasyonu konusunda ciddi zaman kazancı yaşarsın. WordPress’ten mikroservis mimarisine, statik site sunumundan API gateway kurulumuna kadar Caddyfile tek bir dosyada bütün bu senaryoları rahatlıkla kapsıyor.

Bir sonraki adım olarak Caddy’nin JSON API’sini incelemeyi ve dinamik yapılandırma değişikliklerini API üzerinden yapmayı öneriyorum. Özellikle container ortamlarında, her yeniden başlatmada Caddy’yi yapılandırmayı programatik şekilde güncellemek büyük esneklik sağlıyor.

Yorum yapın