Modern web uygulamalarında WebSocket artık bir lüks değil, neredeyse zorunluluk haline geldi. Chat uygulamaları, gerçek zamanlı dashboard’lar, online oyunlar, canlı bildirim sistemleri… Bunların hepsi WebSocket’e ihtiyaç duyuyor. Nginx veya Apache ile WebSocket proxy yapılandırması yaparken elle upgrade header’ları yazmak, timeout ayarlarını tek tek düzenlemek zorunda kalıyorduk. Caddy ise bu işi büyük ölçüde otomatikleştiriyor. Bu yazıda Caddy ile WebSocket proxy yapılandırmasını gerçek dünya senaryolarıyla birlikte detaylıca ele alacağız.
WebSocket ve Reverse Proxy Mantığı
Önce temeli oturtmak gerekiyor. WebSocket, HTTP üzerinden başlayan ama sonra kalıcı bir TCP bağlantısına yükseltilen (upgrade) bir protokol. Tarayıcı sunucuya önce HTTP isteği atıyor, Upgrade: websocket header’ı ile “hadi bunu WebSocket’e çevirelim” diyor. Sunucu kabul ederse bağlantı sürekli açık kalıyor ve iki taraf birbirine veri itebiliyor.
Bir reverse proxy bu senaryoda ortada durunca işler biraz karmaşıklaşıyor. Proxy’nin:
UpgradeveConnectionheader’larını doğru iletmesi- Bağlantının uzun süre açık kalmasına izin vermesi
- Timeout değerlerini akıllıca yönetmesi
- TLS termination yapıyorsa
wss://ilews://arasındaki dönüşümü halletmesi
gerekiyor. Nginx’te bunu manuel yapmak zorundaydık. Caddy ise reverse_proxy direktifi ile bunların büyük bölümünü otomatik hallediyor.
Temel Caddy WebSocket Proxy Yapılandırması
En basit senaryodan başlayalım. Diyelim ki localhost:3000 adresinde çalışan bir Node.js uygulamanız var ve bu uygulama hem HTTP hem de WebSocket istekleri alıyor.
# /etc/caddy/Caddyfile
myapp.example.com {
reverse_proxy localhost:3000
}
Evet, bu kadar. Caddy, gelen isteğin WebSocket upgrade isteği olduğunu otomatik algılıyor ve gerekli header manipülasyonlarını yapıyor. Arka planda Connection: Upgrade ve Upgrade: websocket header’larını backend’e iletiyor.
Peki ya farklı path’lerde farklı backend’leriniz varsa? Örneğin /api normal HTTP, /ws ise WebSocket endpoint’i olsun:
myapp.example.com {
handle /ws* {
reverse_proxy localhost:3001
}
handle /api* {
reverse_proxy localhost:3000
}
handle {
root * /var/www/myapp
file_server
}
}
Bu yapıda /ws ile başlayan tüm istekler 3001 portundaki WebSocket sunucusuna, /api istekleri 3000’e, geri kalanlar ise statik dosya sunucusuna gidiyor.
Header Yönetimi ve Güvenlik
Caddy, varsayılan olarak bazı header’ları backend’e iletirken bazılarını düzenliyor. WebSocket bağlantılarında özellikle dikkat etmemiz gereken birkaç header var.
myapp.example.com {
reverse_proxy localhost:3000 {
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
header_up Host: Backend sunucusunun doğru Host değerini görmesini sağlıyor. Özellikle sanal host kullanan uygulamalarda kritik.
header_up X-Real-IP: Gerçek istemci IP’sini backend’e iletmek için. Chat uygulamalarında rate limiting, güvenlik logları için şart.
header_up X-Forwarded-Proto: İstemcinin HTTPS üzerinden mi yoksa HTTP üzerinden mi bağlandığını backend’e bildiriyor. WebSocket açısından bu wss:// mi ws:// mi sorusunun cevabı demek.
Node.js tarafında bu header’lara erişmek için:
# Node.js uygulamanızda Express kullanıyorsanız
# trust proxy ayarını yapmayı unutmayın
# app.set('trust proxy', true)
# Bağlantı logunu test etmek için
curl -v
-H "Connection: Upgrade"
-H "Upgrade: websocket"
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ=="
-H "Sec-WebSocket-Version: 13"
https://myapp.example.com/ws
Timeout ve Bağlantı Yönetimi
WebSocket bağlantılarının en kritik özelliği uzun süre açık kalmalarıdır. Bir chat uygulamasında kullanıcı saatlerce bağlı kalabilir. Caddy’nin varsayılan timeout değerleri bu senaryolar için biraz kısa gelebilir.
myapp.example.com {
reverse_proxy localhost:3000 {
# Backend'e bağlantı timeout'u
transport http {
dial_timeout 10s
response_header_timeout 30s
# WebSocket için keep-alive
keepalive 30s
keepalive_idle_conns 100
}
# Sağlık kontrolü
health_uri /health
health_interval 30s
health_timeout 10s
}
}
dial_timeout: Caddy’nin backend’e bağlanmak için beklediği süre. 10 saniye makul bir değer.
response_header_timeout: Backend’in header döndürmesi için beklenen süre. WebSocket handshake için 30 saniye genellikle yeterli.
keepalive: HTTP/1.1 keep-alive süresi. WebSocket bağlantıları bundan bağımsız ama HTTP istekler için önemli.
Bir gerçek dünya senaryosu düşünelim: Canlı kripto fiyatları gösteren bir dashboard. Kullanıcılar ortalama 15-20 dakika sayfada kalıyor ve sürekli fiyat güncellemesi alıyor. Bu senaryo için:
crypto-dashboard.example.com {
encode gzip
reverse_proxy /ws/* localhost:8080 {
transport http {
dial_timeout 5s
response_header_timeout 10s
}
# Aktif bağlantı sayısını sınırla
max_conns_per_host 1000
}
reverse_proxy /api/* localhost:8081
handle {
root * /var/www/crypto-dashboard
file_server
}
}
Load Balancing ile WebSocket
Birden fazla backend sunucunuz varsa ve bunlar arasında load balancing yapmak istiyorsanız, WebSocket bağlantıları için dikkatli olmanız gerekiyor. WebSocket bağlantısı kurulduktan sonra aynı backend üzerinde kalmak zorunda. Caddy bunu sticky sessions veya doğru load balancing politikasıyla çözüyor.
websocket-farm.example.com {
reverse_proxy {
to localhost:3001 localhost:3002 localhost:3003
# IP hash ile aynı kullanıcı hep aynı backend'e gider
lb_policy ip_hash
# Sağlık kontrolü
health_uri /health
health_interval 15s
health_timeout 5s
health_status 200
# Pasif sağlık kontrolü
fail_duration 30s
max_fails 3
unhealthy_status 429 500 502 503 504
}
}
lb_policy ip_hash: Aynı IP adresinden gelen tüm istekleri aynı backend’e yönlendirir. WebSocket için ideal çünkü bağlantı kurulduktan sonra kullanıcı aynı sunucuya bağlı kalır.
Alternatif load balancing politikaları:
- round_robin: Sırayla her backend’e istek gönderir. Stateless HTTP için iyi ama WebSocket için sorunlu.
- least_conn: En az aktif bağlantısı olan backend’e yönlendirir. Yük dengeleme açısından adil.
- random: Rastgele seçim. Basit ama WebSocket için pek uygun değil.
- first: Sağlıklı ilk backend’i kullanır. Failover senaryoları için.
- ip_hash: WebSocket için en mantıklı seçenek.
Redis ile Session Paylaşımı Senaryosu
Gerçek dünya uygulamalarında birden fazla WebSocket sunucusu çalıştırıyorsanız ve kullanıcı state’ini yönetmeniz gerekiyorsa, genellikle Redis gibi bir pub/sub sistemi kullanılıyor. Caddy tarafında yapılandırma şöyle görünüyor:
# Birden fazla Node.js WebSocket sunucusu
# Redis üzerinden birbirleriyle konuşuyor
# Caddy bunları load balance ediyor
chat.example.com {
reverse_proxy /socket.io/* {
to 10.0.0.1:3000 10.0.0.2:3000 10.0.0.3:3000
lb_policy ip_hash
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
health_uri /health
health_interval 20s
health_timeout 5s
}
handle {
root * /var/www/chat
file_server
}
}
TLS ve WSS Yapılandırması
Caddy’nin en büyük avantajlarından biri TLS sertifikalarını otomatik yönetmesi. Bir domain tanımladığınızda Caddy Let’s Encrypt’ten sertifika alıyor ve yeniliyor. WebSocket bağlantıları wss:// protokolü üzerinden güvenli şekilde çalışıyor.
# Caddy TLS'yi otomatik hallediyor
# İstemci wss://realtime.example.com/ws bağlantısı kuruyor
# Caddy TLS'yi sonlandırıyor ve ws://localhost:3000/ws olarak iletiyor
realtime.example.com {
# TLS otomatik, ekstra yapılandırmaya gerek yok
log {
output file /var/log/caddy/realtime-access.log {
roll_size 100mb
roll_keep 10
}
format json
}
reverse_proxy /ws {
to localhost:3000
header_up X-Forwarded-Proto {scheme}
header_up X-Real-IP {remote_host}
}
}
Eğer özel sertifika kullanmak istiyorsanız:
realtime.example.com {
tls /etc/ssl/certs/realtime.crt /etc/ssl/private/realtime.key
reverse_proxy /ws localhost:3000 {
header_up X-Forwarded-Proto {scheme}
}
}
Socket.IO Özel Yapılandırması
Socket.IO yaygın kullanılan bir WebSocket kütüphanesi ve bazı özel davranışları var. Hem HTTP long-polling hem de WebSocket kullanabiliyor. Caddy ile Socket.IO kullanıyorsanız path matching’e dikkat etmek gerekiyor:
socketio-app.example.com {
# Socket.IO hem /socket.io/ path'ini hem de WebSocket hem de HTTP polling kullanıyor
reverse_proxy /socket.io/* localhost:3000 {
header_up Host {upstream_hostport}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
# Socket.IO polling için timeout biraz daha uzun olabilir
transport http {
response_header_timeout 60s
}
}
handle {
root * /var/www/app
file_server
}
}
Socket.IO v4 ile Caddy’yi test etmek için hızlı bir Node.js script:
# Test için basit bir WebSocket sunucusu başlatmak
# node ws-test-server.js
cat > /tmp/ws-test.js << 'EOF'
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 3000, path: '/ws' });
wss.on('connection', (ws, req) => {
const clientIP = req.headers['x-real-ip'] || req.socket.remoteAddress;
console.log(`Yeni bağlantı: ${clientIP}`);
ws.send(JSON.stringify({ type: 'welcome', message: 'Caddy üzerinden bağlandınız!' }));
ws.on('message', (data) => {
console.log(`Mesaj alındı: ${data}`);
ws.send(JSON.stringify({ type: 'echo', data: data.toString() }));
});
ws.on('close', () => {
console.log(`Bağlantı kapandı: ${clientIP}`);
});
});
console.log('WebSocket sunucusu port 3000 üzerinde çalışıyor...');
EOF
node /tmp/ws-test.js &
Bağlantıyı test etmek için:
# wscat ile test
npm install -g wscat
wscat -c wss://realtime.example.com/ws
# Veya websocat ile
websocat wss://realtime.example.com/ws
Hata Ayıklama ve Loglama
WebSocket bağlantılarında sorun yaşıyorsanız Caddy’nin log sistemi çok işe yarıyor. Detaylı loglama için:
{
# Global log seviyesi
debug
}
realtime.example.com {
log {
output file /var/log/caddy/ws-debug.log
level DEBUG
format json
}
reverse_proxy /ws localhost:3000
}
Caddy servisini yeniden başlatıp logları izlemek için:
# Caddy yapılandırmasını test et
caddy validate --config /etc/caddy/Caddyfile
# Reload yap (downtime olmadan)
caddy reload --config /etc/caddy/Caddyfile
# Logları canlı izle
tail -f /var/log/caddy/ws-debug.log | jq .
# Sadece WebSocket bağlantılarını filtrele
tail -f /var/log/caddy/ws-debug.log | jq 'select(.request.headers.Upgrade != null)'
Sık karşılaşılan sorunlar ve çözümleri:
- 502 Bad Gateway: Backend çalışmıyor veya port yanlış.
ss -tlnp | grep 3000ile kontrol edin. - 400 Bad Request: WebSocket handshake başarısız. Header’ların doğru iletilip iletilmediğini kontrol edin.
- Connection timeout: Backend yavaş yanıt veriyor veya firewall blokluyor.
dial_timeoutdeğerini artırın. - WebSocket connection closed: Backend çöktü veya bağlantı limiti aşıldı.
max_conns_per_hostdeğerini kontrol edin.
Rate Limiting ile WebSocket Koruması
WebSocket bağlantıları potansiyel olarak kötüye kullanılabilir. Her bağlantı sunucu kaynaklarını tüketiyor. Caddy’nin rate limiting plugin’i ile bunu kontrol altına alabilirsiniz:
{
order rate_limit before reverse_proxy
}
realtime.example.com {
rate_limit {
zone ws_connections {
key {remote_host}
events 10
window 1m
}
}
reverse_proxy /ws localhost:3000 {
header_up X-Real-IP {remote_host}
max_conns_per_host 500
}
}
Rate limiting plugin’ini yüklemek için:
# xcaddy ile rate limiting modülü ekleyerek derle
xcaddy build --with github.com/mholt/caddy-ratelimit
# Veya resmi xcaddy ile
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build
--with github.com/mholt/caddy-ratelimit
--output /usr/local/bin/caddy
Monitoring ve Metrik Toplama
Üretim ortamında WebSocket bağlantı sayısını, bağlantı sürelerini ve hata oranlarını izlemek kritik. Caddy’nin Prometheus metrik endpoint’ini aktif edelim:
{
metrics
}
realtime.example.com {
reverse_proxy /ws localhost:3000
handle /metrics {
# Sadece dahili ağdan erişim izni ver
@internal {
remote_ip 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
}
handle @internal {
metrics
}
respond 403
}
}
Prometheus scrape config:
# prometheus.yml
scrape_configs:
- job_name: 'caddy'
static_configs:
- targets: ['realtime.example.com:2019']
metrics_path: '/metrics'
Caddy admin API ile anlık bağlantı durumuna bakmak için:
# Admin API varsayılan olarak localhost:2019'da çalışır
curl http://localhost:2019/metrics | grep caddy_reverse_proxy
# Aktif upstream'leri listele
curl http://localhost:2019/config/ | jq '.apps.http.servers'
Gerçek Dünya Örneği: Online Oyun Sunucusu
Bir online tahta oyunu düşünün. Oyuncular eşleşiyor, gerçek zamanlı hamle gönderiyor, chat yapıyor. Birden fazla oyun sunucusu var ve her sunucu belirli sayıda aktif oyun yönetiyor.
{
email [email protected]
}
game.example.com {
encode gzip
log {
output file /var/log/caddy/game-access.log {
roll_size 50mb
roll_keep 7
}
format json
}
# Oyun WebSocket bağlantıları
reverse_proxy /game/ws/* {
to 10.0.1.10:4000 10.0.1.11:4000 10.0.1.12:4000
lb_policy ip_hash
health_uri /health
health_interval 10s
health_timeout 3s
health_status 200
fail_duration 60s
max_fails 2
header_up X-Real-IP {remote_host}
header_up X-Forwarded-Proto {scheme}
transport http {
dial_timeout 5s
response_header_timeout 15s
keepalive 60s
}
}
# Eşleştirme API'si
reverse_proxy /api/* 10.0.1.20:8080 {
header_up X-Real-IP {remote_host}
}
# Statik dosyalar
handle {
root * /var/www/boardgame
file_server
}
}
Bu yapılandırmada üç ayrı oyun sunucusu var. IP hash ile aynı oyuncu hep aynı sunucuya bağlanıyor. Sunucu düşerse Caddy otomatik olarak diğerine yönlendiriyor. Sağlık kontrolü her 10 saniyede bir yapılıyor, 2 ardışık başarısızlıkta sunucu 60 saniyeliğine devre dışı bırakılıyor.
Sonuç
Caddy, WebSocket proxy yapılandırmasını gerçekten basitleştiriyor. Nginx’te el ile yazmak zorunda kaldığımız proxy_http_version 1.1, proxy_set_header Upgrade, proxy_set_header Connection satırları Caddy ile tamamen ortadan kalkıyor. Otomasyon sadece burada bitmiyor; TLS sertifika yönetimi, otomatik HTTPS yönlendirmesi, akıllı varsayılan güvenlik ayarları da işin içinde geliyor.
Üretime alırken şunlara dikkat etmek gerekiyor: Load balancing yapıyorsanız ip_hash politikasını tercih edin. Backend timeout değerlerini uygulamanızın beklenen bağlantı süresine göre ayarlayın. Rate limiting’i ihmal etmeyin çünkü açık WebSocket bağlantıları saldırı vektörü olabilir. Loglama ve monitoring’i baştan kurun, sorun çıktığında neyin ne zaman olduğunu görebilmek paha biçilmez.
Caddy’nin dokümantasyonu oldukça iyi ama bazen özellikle transport http bloğu içindeki gelişmiş ayarlar için biraz araştırma yapmak gerekebiliyor. Topluluk forumu ve GitHub discussions bu boşluğu dolduruyor. Nginx’ten geçiş yapıyorsanız başlangıçta her şeyi elle yazmadığınız için garip hissedebilirsiniz; ama zamanla bu basitliğin ne kadar değerli olduğunu anlayacaksınız.