Nginx veya Apache ile reverse proxy kurarken saatlerce uğraştıktan sonra Caddy’yi keşfettiğimde inanılmaz bir kolaylıkla karşılaştım. Caddy, modern web altyapısının ihtiyaç duyduğu reverse proxy ve load balancing özelliklerini neredeyse sıfır konfigürasyonla sunuyor. Bu yazıda Caddy’nin bu güçlü özelliklerini gerçek dünya senaryolarıyla birlikte inceleyeceğiz.
Caddy’yi Neden Reverse Proxy İçin Tercih Etmeli?
Önce temel soruyu cevaplayalım: Elimizde Nginx ve HAProxy gibi olgun araçlar varken neden Caddy?
Otomatik HTTPS en büyük avantaj. Let’s Encrypt entegrasyonu kutudan çıktığı gibi geliyor ve reverse proxy senaryolarında da sorunsuz çalışıyor. Sertifika yenileme, OCSP stapling, HTTP/2 ve HTTP/3 desteği otomatik olarak halloluyor.
Caddyfile söz dizimi son derece okunabilir ve bakımı kolay. Üç satırla çalışan bir reverse proxy konfigürasyonu yazabilirsiniz. Nginx’teki gibi upstream, location, proxy_pass bloklarını iç içe geçirmenize gerek yok.
JSON API ile çalışma zamanında konfigürasyon değişikliği yapabilirsiniz. Servis yeniden başlatmadan yeni backend sunucu ekleyebilir, mevcut kuralları güncelleyebilirsiniz. Bu özellikle dinamik ortamlarda büyük avantaj.
Son olarak Go ile yazılmış olması sayesinde tek bir binary dosyası her şeyi hallediyor. Dependency sorunları, modül çakışmaları, versiyon uyumsuzlukları ile boğuşmuyorsunuz.
Kurulum ve Temel Hazırlık
Caddy kurulumunu hızlıca geçelim. Ubuntu/Debian üzerinde:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy
Caddy servisini başlatalım ve durumunu kontrol edelim:
sudo systemctl enable caddy
sudo systemctl start caddy
sudo systemctl status caddy
# Caddy versiyonunu kontrol et
caddy version
# Caddyfile sözdizimini doğrula
caddy validate --config /etc/caddy/Caddyfile
Konfigürasyon dosyamız /etc/caddy/Caddyfile konumunda bulunuyor. Değişiklik yaptıktan sonra servisi yeniden başlatmak yerine reload kullanın, bu daha güvenli:
sudo systemctl reload caddy
# veya
caddy reload --config /etc/caddy/Caddyfile
Temel Reverse Proxy Konfigürasyonu
En basit reverse proxy senaryosundan başlayalım. Diyelim ki app.sirketim.com adresine gelen istekleri lokal makinede 3000 portunda çalışan Node.js uygulamasına yönlendireceğiz.
app.sirketim.com {
reverse_proxy localhost:3000
}
Bu üç satır şunları otomatik yapıyor: Let’s Encrypt’ten SSL sertifikası alıyor, HTTP’yi HTTPS’e yönlendiriyor, HTTP/2 etkinleştiriyor ve istekleri backend’e iletirken gerekli header’ları (X-Forwarded-For, X-Forwarded-Host, X-Forwarded-Proto) ekliyor.
Şimdi biraz daha gerçekçi bir senaryo. Birden fazla uygulama aynı sunucuda çalışıyor ve hepsini Caddy üzerinden yönetmek istiyoruz:
app1.sirketim.com {
reverse_proxy localhost:3000
}
app2.sirketim.com {
reverse_proxy localhost:4000
}
api.sirketim.com {
reverse_proxy localhost:8080
# API loglarını ayrı dosyaya yaz
log {
output file /var/log/caddy/api-access.log
format json
}
}
Wildcard subdomain kullanmak istiyorsanız DNS provider’ınızın API’si üzerinden DNS challenge yapmanız gerekiyor. Bunu ileride göreceğiz, şimdi load balancing konusuna geçelim.
Load Balancing: Teori ve Pratik
Load balancing’in özü basit: Gelen trafiği birden fazla backend sunucusuna dağıtmak. Ama “nasıl dağıtıyoruz” sorusu işin kritik kısmı. Caddy birkaç farklı algoritma sunuyor.
round_robin: İstekleri sırayla backend’lere dağıtır. Varsayılan algoritmadır. Tüm sunucularınız benzer kapasitedeyse idealdir.
least_conn: O an en az aktif bağlantısı olan sunucuya yönlendirir. Uzun süren isteklerin olduğu uygulamalarda (dosya upload, video stream) tercih edilir.
ip_hash: İstemcinin IP adresine göre hangi backend’e gideceğini belirler. Aynı kullanıcı her seferinde aynı sunucuya düşer. Session bilgisini backend’ler arasında paylaşmıyorsanız bu kritik önem taşır.
random: Rastgele seçim yapar. Basit ve hızlıdır.
first: Sağlıklı olan ilk sunucuya yönlendirir. Aktif-pasif yapılar için kullanışlıdır.
uri: URI hash’ine göre dağıtım yapar. Cache tutarlılığı için tercih edilir.
header: Belirli bir header değerine göre dağıtım yapar.
Temel load balancing konfigürasyonu:
app.sirketim.com {
reverse_proxy {
to backend1.internal:8080
to backend2.internal:8080
to backend3.internal:8080
lb_policy round_robin
# Health check ayarları
health_uri /health
health_interval 10s
health_timeout 5s
health_status 200
}
}
Gerçek Dünya Senaryosu: E-ticaret Uygulaması
Orta ölçekli bir e-ticaret sitesi düşünün. Frontend Next.js ile yazılmış, backend API ayrı bir servis, ödemeler için özel bir mikro servis var ve statik dosyalar için CDN kullanılıyor. Bu yapıyı Caddy ile nasıl yönetirsiniz?
sirketim.com, www.sirketim.com {
# Statik dosyaları önbellekle
@static path *.css *.js *.png *.jpg *.ico *.woff2
header @static Cache-Control "public, max-age=31536000, immutable"
# API isteklerini backend cluster'a yönlendir
@api path /api/*
reverse_proxy @api {
to api1.internal:8080
to api2.internal:8080
to api3.internal:8080
lb_policy least_conn
health_uri /api/health
health_interval 15s
health_timeout 3s
# Backend yanıt vermezse retry yap
lb_retries 2
# Timeout ayarları
transport http {
dial_timeout 5s
response_header_timeout 30s
read_timeout 60s
write_timeout 60s
}
}
# Ödeme servisi ayrı backend'e
@payment path /api/payment/*
reverse_proxy @payment {
to payment1.internal:9090
to payment2.internal:9090
lb_policy ip_hash
# Ödeme servisinde header doğrulama
header_up X-Real-IP {remote_host}
header_up X-Request-ID {uuid}
}
# Frontend Next.js uygulaması
reverse_proxy {
to frontend1.internal:3000
to frontend2.internal:3000
lb_policy round_robin
}
}
Bu konfigürasyonda dikkat edilmesi gereken noktalar:
/api/*isteklerileast_connile API backend’lerine gidiyor çünkü API isteklerinin süresi değişken- Ödeme servisi
ip_hashkullanıyor, kullanıcı ödeme akışı boyunca aynı sunucuda kalıyor - Frontend için
round_robinyeterli çünkü Next.js SSR stateless
Health Check Derinlemesine
Health check mekanizması load balancing’in en kritik parçası. Bir backend sunucu yanıt vermez veya hatalı yanıt verirse Caddy onu otomatik olarak devre dışı bırakmalı.
backend.sirketim.com {
reverse_proxy {
to web1.internal:8080
to web2.internal:8080
to web3.internal:8080
# Aktif health check
health_uri /healthz
health_interval 10s
health_timeout 5s
health_status 200
# Pasif health check (gerçek trafikten öğrenir)
fail_duration 30s
max_fails 3
unhealthy_status 500 502 503
unhealthy_latency 2s
}
}
health_uri: Caddy’nin belirli aralıklarla kontrol edeceği endpoint. Bu endpoint’in basit bir JSON veya düz metin döndürmesi yeterli.
fail_duration: Bir backend başarısız olduğunda ne kadar süre devre dışı tutulacağı. Bu süre sonunda Caddy tekrar deneyecek.
max_fails: Pasif modda, bir backend kaç kez başarısız olursa devre dışı bırakılacağı.
unhealthy_latency: Backend yanıtı bu süreden uzun sürerse sağlıksız sayılır.
Uygulamanızın /healthz endpoint’i nasıl görünmeli? Basit bir Node.js örneği:
# Express.js uygulamasında health check endpoint'i
cat << 'EOF' > healthcheck.js
app.get('/healthz', async (req, res) => {
try {
// Veritabanı bağlantısını kontrol et
await db.query('SELECT 1');
// Redis bağlantısını kontrol et
await redis.ping();
res.status(200).json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime()
});
} catch (error) {
res.status(503).json({
status: 'unhealthy',
error: error.message
});
}
});
EOF
Header Manipülasyonu ve Güvenlik
Reverse proxy’den geçen isteklerde header’ları yönetmek hem güvenlik hem de uygulama uyumluluğu açısından önemli.
guvenli.sirketim.com {
reverse_proxy localhost:8080 {
# Backend'e iletilecek header'lar
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Request-ID {uuid}
header_up Host {upstream_hostport}
# Backend'den gelen hassas header'ları kaldır
header_down -Server
header_down -X-Powered-By
}
# İstemciye gönderilecek güvenlik header'ları
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'"
-Server
}
# Rate limiting (Caddy modülü gerektirir)
rate_limit {
zone dynamic {
key {remote_host}
events 100
window 1m
}
}
}
{uuid} direktifi her istek için benzersiz bir ID üretiyor. Bu ID’yi hem loglara yazabilir hem de backend uygulamanıza iletebilirsiniz. Dağıtık sistemlerde hata takibini çok kolaylaştırır.
Path-Based Routing
Aynı domain altında farklı path’leri farklı servislere yönlendirmek microservice mimarisinin temel ihtiyacı. Caddy bunu matcher sistemiyle çözüyor.
platform.sirketim.com {
# Kullanıcı servisi
handle /users/* {
reverse_proxy user-service:8001
}
# Ürün servisi
handle /products/* {
reverse_proxy {
to product-service-1:8002
to product-service-2:8002
lb_policy round_robin
}
}
# Sipariş servisi
handle /orders/* {
reverse_proxy order-service:8003
}
# Bildirim servisi WebSocket desteğiyle
handle /notifications/* {
reverse_proxy notification-service:8004 {
# WebSocket desteği otomatik, ama açık belirtebiliriz
transport http {
versions h1 h2
}
}
}
# Varsayılan: API Gateway'e gönder
handle {
reverse_proxy api-gateway:8000
}
}
handle direktifi ile handle_path arasındaki farka dikkat edin. handle path prefix’i olduğu gibi backend’e iletirken handle_path prefix’i soyarak iletir. Yani handle_path /api/* { reverse_proxy backend:8080 } kullandığınızda /api/users isteği backend’e /users olarak gider.
WebSocket ve Uzun Bağlantı Desteği
Modern uygulamaların büyük çoğunluğu WebSocket kullanıyor. Chat uygulamaları, gerçek zamanlı dashboard’lar, live notification sistemleri… Caddy WebSocket’i otomatik olarak destekliyor ama bazı ayarlamaların yapılması gerekiyor.
realtime.sirketim.com {
reverse_proxy {
to ws-server-1:8080
to ws-server-2:8080
to ws-server-3:8080
# WebSocket için ip_hash kullanmak genelde daha güvenli
lb_policy ip_hash
# WebSocket bağlantıları uzun sürebilir
transport http {
# Timeout'ları artır
dial_timeout 10s
# WebSocket bağlantıları için 0 (sınırsız) veya yüksek değer
read_timeout 0
write_timeout 0
response_header_timeout 10s
# HTTP/1.1 zorla (WebSocket h1 gerektirir)
versions h1
}
}
}
Caddy API ile Dinamik Konfigürasyon
Caddy’nin en güçlü özelliklerinden biri runtime’da konfigürasyonu değiştirebilme yeteneği. Kubernetes ortamında veya dinamik scaling yaptığınız durumlarda bu özellik hayat kurtarır.
# Mevcut konfigürasyonu görüntüle
curl -s http://localhost:2019/config/ | python3 -m json.tool
# Yeni bir backend ekle (JSON API üzerinden)
curl -X POST http://localhost:2019/config/apps/http/servers/srv0/routes
-H "Content-Type: application/json"
-d '{
"match": [{"host": ["yeni-servis.sirketim.com"]}],
"handle": [{
"handler": "reverse_proxy",
"upstreams": [{"dial": "localhost:9000"}]
}]
}'
# Çalışma zamanında upstream ekle veya kaldır
curl -X PUT http://localhost:2019/config/apps/http/servers/srv0/routes/0/handle/0/upstreams/3
-H "Content-Type: application/json"
-d '{"dial": "yeni-backend:8080"}'
# Caddy'nin sağlık durumunu kontrol et
curl -s http://localhost:2019/metrics
Bu API özellikle CI/CD pipeline’larında çok işe yarıyor. Blue-green deployment yaparken eski backend’leri kaldırıp yenilerini ekleyebilirsiniz, hiçbir kesinti olmadan.
Logging ve Monitoring
Production ortamında her şeyi loglamak ve izlemek şart. Caddy bu konuda da güçlü araçlar sunuyor.
sirketim.com {
reverse_proxy backend:8080
log {
output file /var/log/caddy/access.log {
roll_size 100mb
roll_keep 10
roll_keep_for 720h
}
format json {
time_format iso8601
}
level INFO
}
}
JSON formatındaki logları jq ile analiz edebilirsiniz:
# Son 100 isteği göster
tail -100 /var/log/caddy/access.log | jq '.'
# Sadece 5xx hatalarını filtrele
cat /var/log/caddy/access.log | jq 'select(.status >= 500)'
# Yavaş istekleri bul (1 saniyeden uzun)
cat /var/log/caddy/access.log | jq 'select(.duration > 1.0) | {uri: .request.uri, duration: .duration, status: .status}'
# IP başına istek sayısı
cat /var/log/caddy/access.log | jq -r '.request.remote_ip' | sort | uniq -c | sort -rn | head -20
# Caddy servis logları
journalctl -u caddy -f --no-pager
Prometheus ile entegrasyon için Caddy’nin metrics endpoint’ini etkinleştirin:
{
metrics {
per_host
}
}
sirketim.com {
reverse_proxy backend:8080
}
Bu yapılandırmayla Caddy localhost:2019/metrics adresinde Prometheus formatında metrikler sunuyor. Grafana ile birleştirince güzel bir dashboard elde edebilirsiniz.
Sorun Giderme İpuçları
Production’da karşılaşılan yaygın sorunlar ve çözümleri:
502 Bad Gateway hatası: Backend’in ayakta olup olmadığını kontrol edin. Caddy’nin backend’e ulaşıp ulaşamadığını test etmek için:
# Backend erişilebilirlik testi
curl -v http://backend-sunucu:8080/health
# Caddy konfigürasyonunu doğrula
caddy validate --config /etc/caddy/Caddyfile
# Caddy hata loglarını izle
journalctl -u caddy -f | grep -i error
# Aktif bağlantıları göster
ss -tulpn | grep caddy
SSL sertifika sorunları: Let’s Encrypt rate limit’e takılmış olabilirsiniz. Staging ortamı kullanın:
# Let's Encrypt staging ortamı
{
acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
}
sirketim.com {
reverse_proxy localhost:8080
}
Yüksek gecikme: Backend’deki sorun mu yoksa Caddy’deki mi? transport ayarlarını ve timeout değerlerini inceleyin. unhealthy_latency değerini gerçekçi bir eşiğe çekin.
WebSocket bağlantıları kopuyor: read_timeout ve write_timeout değerlerini artırın. Load balancer önünde başka bir proxy varsa (AWS ALB gibi) idle connection timeout’larına dikkat edin.
Sonuç
Caddy ile reverse proxy ve load balancing kurulumu, Nginx veya HAProxy ile aynı işi yapmaktan kat kat daha hızlı ve keyifli. Özellikle küçük ve orta ölçekli production ortamları için Caddy’nin sunduğu otomatik SSL, temiz söz dizimi ve güçlü API kombinasyonu benzersiz.
Dikkat etmeniz gereken kritik noktaları özetleyeyim:
- Health check’leri doğru konfigüre edin. Uygulama gerçekten sağlıklı mı diye soran endpoint’ler yazın, sadece HTTP 200 dönen sahte endpoint’ler değil.
- Timeout değerlerini uygulamanıza göre ayarlayın. Varsayılan değerler her senaryo için uygun değil.
- Load balancing algoritmasını iş yüküne göre seçin. Session gerektiren uygulamalar için
ip_hash, değişken süreli istekler içinleast_conngenelde doğru tercih. - JSON API’yi öğrenin. Dinamik ortamlarda Caddyfile yeniden yüklemek yerine API üzerinden değişiklik yapmak çok daha güvenli.
- Logları JSON formatında tutun ve log rotation ayarlarını ihmal etmeyin.
Caddy hâlâ Nginx kadar yaygın kullanılmasa da özellikle yeni projelerde ve containerized ortamlarda hızla yaygınlaşıyor. Eğer henüz denemediyseniz, bir sonraki projenizde Caddy’ye şans verin, pişman olmayacaksınız.