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.