Caddy ile Reverse Proxy ve Load Balancing Yapılandırması

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/* istekleri least_conn ile API backend’lerine gidiyor çünkü API isteklerinin süresi değişken
  • Ödeme servisi ip_hash kullanıyor, kullanıcı ödeme akışı boyunca aynı sunucuda kalıyor
  • Frontend için round_robin yeterli çü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çin least_conn genelde 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.

Yorum yapın