Caddy ile Go Uygulaması ve API Sunumu
Modern web geliştirme dünyasında Go dilinin popülaritesi giderek artıyor. Yüksek performanslı API’lar ve web uygulamaları geliştirmek için mükemmel bir seçenek olan Go, doğru bir reverse proxy ile birleşince gerçekten güçlü bir altyapı oluşturuyor. Caddy ise bu altyapının ön yüzünde durmak için belki de en ideal web sunucusu. Otomatik HTTPS, sade konfigürasyon sözdizimi ve Go ile yazılmış olması nedeniyle bu ikilinin birlikte çalışması hem teknik hem de pratik açıdan çok mantıklı.
Bu yazıda gerçek dünya senaryolarıyla Caddy’yi bir Go uygulamasının ve REST API’ının önüne nasıl koyacağımızı adım adım inceleyeceğiz. Basit bir uygulamadan başlayıp çoklu servis yönetimi, sağlık kontrolleri ve üretim ortamına hazır yapılandırmaya kadar her şeyi ele alacağız.
Neden Caddy ve Go Birlikte?
Go uygulamaları genellikle kendi içinde bir HTTP sunucusu çalıştırır. Örneğin net/http paketi veya Gin, Echo, Fiber gibi framework’ler kullanılarak yazılmış bir uygulama, doğrudan bir port dinleyebilir. Ancak bu uygulamayı doğrudan 80 veya 443 portuna koymak güvenlik ve yönetim açısından pek akıllıca değil.
İşte burada Caddy devreye giriyor:
- Otomatik TLS: Let’s Encrypt sertifikası otomatik alır ve yeniler
- Sıfır konfigürasyon karmaşıklığı: Nginx veya Apache’ye kıyasla çok daha sade
- HTTP/2 ve HTTP/3 desteği: Kutudan çıkar çıkmaz aktif
- Güvenlik başlıkları: Kolayca eklenebilir middleware’ler
- Load balancing: Birden fazla Go instance’ını kolayca yönetebilirsin
Ortamı Hazırlama
Önce temel kurulumları yapalım. Ubuntu 22.04 veya Debian tabanlı bir sistem kullandığımızı varsayıyoruz.
Caddy Kurulumu
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
Kurulum sonrası Caddy’nin çalışıp çalışmadığını kontrol et:
systemctl status caddy
caddy version
Örnek Go Uygulaması Oluşturma
Gerçekçi bir senaryo için hem statik dosya sunan hem de REST API endpoint’leri içeren bir Go uygulaması yazalım. Bu uygulama bir ürün yönetim API’si olacak.
mkdir -p /opt/myapp
cd /opt/myapp
go mod init myapp
Ardından main.go dosyasını oluştur:
cat > /opt/myapp/main.go << 'EOF'
package main
import (
"encoding/json"
"log"
"net/http"
"os"
"time"
)
type Product struct {
ID int `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
CreatedAt time.Time `json:"created_at"`
}
type HealthResponse struct {
Status string `json:"status"`
Timestamp time.Time `json:"timestamp"`
Version string `json:"version"`
}
var products = []Product{
{ID: 1, Name: "Laptop", Price: 1299.99, CreatedAt: time.Now()},
{ID: 2, Name: "Mouse", Price: 29.99, CreatedAt: time.Now()},
{ID: 3, Name: "Keyboard", Price: 79.99, CreatedAt: time.Now()},
}
func healthHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(HealthResponse{
Status: "healthy",
Timestamp: time.Now(),
Version: "1.0.0",
})
}
func productsHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(products)
}
func main() {
port := os.Getenv("APP_PORT")
if port == "" {
port = "8080"
}
mux := http.NewServeMux()
mux.HandleFunc("/health", healthHandler)
mux.HandleFunc("/api/products", productsHandler)
log.Printf("Server starting on port %s", port)
if err := http.ListenAndServe(":"+port, mux); err != nil {
log.Fatal(err)
}
}
EOF
go build -o myapp .
Uygulamayı systemd ile yönetelim ki sunucu yeniden başladığında otomatik devreye girsin:
cat > /etc/systemd/system/myapp.service << 'EOF'
[Unit]
Description=My Go Application
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/myapp
Restart=on-failure
RestartSec=5
Environment=APP_PORT=8080
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now myapp
systemctl status myapp
Temel Caddyfile Yapılandırması
Go uygulamamız 8080 portunda çalışıyor. Şimdi Caddy’yi bu uygulamanın önüne koyalım.
cat > /etc/caddy/Caddyfile << 'EOF'
api.sirketim.com {
reverse_proxy localhost:8080
}
EOF
systemctl reload caddy
Bu kadar. Evet, gerçekten bu kadar. Caddy otomatik olarak Let’s Encrypt’ten sertifika alacak ve HTTPS üzerinden trafiği Go uygulamanıza yönlendirecek. Nginx’te bu işlem için en az 20-30 satır konfigürasyon gerekirdi.
Gelişmiş Reverse Proxy Yapılandırması
Temel yapılandırma işe yarasa da üretim ortamı için daha fazlasına ihtiyacımız var. Header yönetimi, timeout değerleri ve sağlık kontrolleri ekleyelim.
cat > /etc/caddy/Caddyfile << 'EOF'
api.sirketim.com {
# Reverse proxy ana yapılandırması
reverse_proxy localhost:8080 {
# Timeout değerleri
transport http {
dial_timeout 5s
response_header_timeout 30s
read_timeout 60s
write_timeout 60s
}
# Sağlık kontrolü
health_uri /health
health_interval 10s
health_timeout 5s
health_status 200
# Header yönetimi - upstream'e gönderilecekler
header_up X-Real-IP {remote_host}
header_up X-Forwarded-Proto {scheme}
header_up X-Request-ID {uuid}
}
# Güvenlik başlıkları
header {
X-Content-Type-Options nosniff
X-Frame-Options DENY
X-XSS-Protection "1; mode=block"
Referrer-Policy strict-origin-when-cross-origin
-Server
}
# Rate limiting - brute force koruması
rate_limit {remote_host} 100r/m
# Erişim logları
log {
output file /var/log/caddy/api.sirketim.com.log {
roll_size 50mb
roll_keep 10
}
format json
}
}
EOF
systemctl reload caddy
Buradaki health_uri direktifi çok önemli. Caddy düzenli olarak /health endpoint’ini kontrol eder ve uygulama yanıt vermiyorsa 502 döndürmek yerine beklemeye geçer.
Çoklu Go Servisi Yönetimi
Gerçek dünya senaryolarında genellikle birden fazla mikro servisiniz olur. Diyelim ki bir e-ticaret platformu kuruyorsunuz: ürün servisi, kullanıcı servisi ve sipariş servisi ayrı Go uygulamaları olarak çalışıyor.
Önce her servis için systemd unit dosyaları oluşturalım:
# Kullanıcı servisi için
cat > /etc/systemd/system/user-service.service << 'EOF'
[Unit]
Description=User Microservice
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/user-service
ExecStart=/opt/user-service/user-service
Restart=on-failure
Environment=APP_PORT=8081
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
# Sipariş servisi için
cat > /etc/systemd/system/order-service.service << 'EOF'
[Unit]
Description=Order Microservice
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/order-service
ExecStart=/opt/order-service/order-service
Restart=on-failure
Environment=APP_PORT=8082
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now user-service order-service
Şimdi Caddyfile’ı path bazlı yönlendirme yapacak şekilde güncelleyelim:
cat > /etc/caddy/Caddyfile << 'EOF'
# Ana API gateway
api.sirketim.com {
# Ürün servisi
handle /api/products* {
reverse_proxy localhost:8080 {
health_uri /health
health_interval 15s
header_up X-Service "product"
}
}
# Kullanıcı servisi
handle /api/users* {
reverse_proxy localhost:8081 {
health_uri /health
health_interval 15s
header_up X-Service "user"
}
}
# Sipariş servisi
handle /api/orders* {
reverse_proxy localhost:8082 {
health_uri /health
health_interval 15s
header_up X-Service "order"
}
}
# API dokümantasyonu için statik dosyalar
handle /docs* {
root * /opt/api-docs
file_server
}
# Tanımsız path'ler için 404
handle {
respond "Not Found" 404
}
header {
X-Content-Type-Options nosniff
X-Frame-Options DENY
-Server
}
log {
output file /var/log/caddy/api.log
format json
}
}
EOF
systemctl reload caddy
Bu yapılandırma tek bir domain üzerinden birden fazla mikro servisi yönetmeni sağlıyor. Path bazlı yönlendirme sayesinde istemciler farklı portları bilmek zorunda kalmıyor.
Load Balancing ile Yüksek Erişilebilirlik
Kritik uygulamalarda tek instance yetmez. Caddy’nin load balancing özelliğini kullanarak aynı Go uygulamasının birden fazla instance’ını çalıştırabilirsin.
# İkinci ve üçüncü instance için servis dosyaları
for port in 8083 8084; do
cat > /etc/systemd/system/myapp-${port}.service << EOF
[Unit]
Description=My App Instance ${port}
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/myapp
Restart=on-failure
Environment=APP_PORT=${port}
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
done
systemctl daemon-reload
systemctl enable --now myapp-8083 myapp-8084
Caddyfile’ı load balancing için güncelleyelim:
cat > /etc/caddy/Caddyfile << 'EOF'
api.sirketim.com {
reverse_proxy localhost:8080 localhost:8083 localhost:8084 {
# Load balancing stratejisi
lb_policy least_conn
# Sağlık kontrolü
health_uri /health
health_interval 10s
health_timeout 3s
health_status 200
# Başarısız upstream'i devre dışı bırakma süresi
fail_duration 30s
unhealthy_request_count 3
# Aktif pasif failover
max_fails 3
transport http {
dial_timeout 3s
response_header_timeout 25s
}
}
log {
output file /var/log/caddy/api.log
format json
}
}
EOF
systemctl reload caddy
lb_policy direktifi için seçenekler şunlar:
- round_robin: Sırayla dağıt, varsayılan seçenek
- least_conn: En az bağlantısı olan upstream’e yönlendir
- ip_hash: Aynı IP her zaman aynı backend’e gitsin, session affinity için kullanışlı
- random: Rastgele seç
- first: Sağlıklı olduğu sürece hep ilk backend’i kullan, aktif-pasif senaryosu için ideal
CORS Yapılandırması
Modern Single Page Application’lar için CORS başlıklarını doğru ayarlamak şart. Caddy’de bunu birkaç satırla halledebilirsin.
cat > /etc/caddy/Caddyfile << 'EOF'
api.sirketim.com {
@cors_preflight {
method OPTIONS
header Origin *
}
@cors {
header Origin *
}
handle @cors_preflight {
header Access-Control-Allow-Origin "{header.origin}"
header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
header Access-Control-Allow-Headers "Authorization, Content-Type, X-Request-ID"
header Access-Control-Max-Age "3600"
respond "" 204
}
handle @cors {
header Access-Control-Allow-Origin "{header.origin}"
header Access-Control-Allow-Credentials "true"
reverse_proxy localhost:8080
}
handle {
reverse_proxy localhost:8080
}
}
EOF
Eğer sadece belirli domain’lerden gelen isteklere izin vermek istiyorsan daha kısıtlayıcı bir yaklaşım kullanabilirsin:
@allowed_origins {
header Origin "https://app.sirketim.com"
header Origin "https://admin.sirketim.com"
}
Authentication Middleware
API’larını JWT ile korumak yaygın bir senaryo. Caddy’de forward_auth direktifi ile harici bir auth servisi kullanabilirsin.
cat > /etc/caddy/Caddyfile << 'EOF'
api.sirketim.com {
# Public endpoint'ler auth gerektirmiyor
handle /api/auth* {
reverse_proxy localhost:8080
}
handle /health {
reverse_proxy localhost:8080
}
# Korumalı endpoint'ler
handle /api/* {
forward_auth localhost:9000 {
uri /verify
copy_headers X-User-ID X-User-Role X-Tenant-ID
header_up Authorization {http.request.header.Authorization}
}
reverse_proxy localhost:8080
}
}
EOF
Bu yapılandırmada 9000 portundaki bir auth servis /verify endpoint’ini kontrol eder. 200 dönerse istek Go uygulamasına geçer, başka bir kod dönerse istemciye iletilir. copy_headers ile auth servisin set ettiği başlıklar downstream uygulamaya iletilir.
Statik Dosya Sunumu ile Hibrit Yapı
Pek çok Go uygulaması hem API hem de statik dosya sunar. Tek sayfalık uygulamalarda React veya Vue ile derlenen dosyaları Caddy üzerinden sunmak, Go uygulamasının yükünü azaltır.
cat > /etc/caddy/Caddyfile << 'EOF'
sirketim.com {
# API isteklerini Go'ya yönlendir
handle /api/* {
reverse_proxy localhost:8080 {
health_uri /health
health_interval 10s
transport http {
dial_timeout 3s
}
}
}
# WebSocket desteği
handle /ws/* {
reverse_proxy localhost:8080 {
header_up Upgrade {http.request.header.Upgrade}
header_up Connection "Upgrade"
}
}
# Statik dosyalar - React/Vue build output
handle {
root * /var/www/sirketim.com
try_files {path} /index.html
file_server
}
# Sıkıştırma
encode gzip zstd
# Cache başlıkları
@static {
path *.js *.css *.png *.jpg *.svg *.woff2
}
header @static Cache-Control "public, max-age=31536000, immutable"
log {
output file /var/log/caddy/sirketim.com.log
format json
}
}
EOF
systemctl reload caddy
try_files {path} /index.html satırı SPA’lar için kritik. Client-side routing kullandığında, örneğin /dashboard/settings gibi bir path’e direkt erişildiğinde dosya bulunamaz. Bu direktif sayesinde Caddy bulunamayan path’ler için index.html‘i döndürür ve React/Vue router devralır.
Caddy API ile Dinamik Yapılandırma
Caddy’nin en güçlü özelliklerinden biri çalışırken konfigürasyon değişikliği yapabilmesi. Bunun için Caddy Admin API’sini kullanabilirsin.
# Mevcut yapılandırmayı JSON olarak al
curl -s localhost:2019/config/ | python3 -m json.tool
# Caddy'nin durumunu kontrol et
curl -s localhost:2019/load
# Bir route'u dinamik olarak ekle
curl -X POST localhost:2019/config/apps/http/servers/srv0/routes
-H "Content-Type: application/json"
-d '{
"match": [{"host": ["yeni.sirketim.com"]}],
"handle": [{
"handler": "reverse_proxy",
"upstreams": [{"dial": "localhost:9090"}]
}]
}'
Bu özellik özellikle otomatik deployment pipeline’larında çok işe yarıyor. Yeni bir servis deploy ettiğinde CI/CD sisteminiz Caddy API’sini çağırarak routing kurallarını güncelleyebilir, herhangi bir reload yapmadan.
Loglama ve Monitoring
Üretimde neler döndüğünü anlamak için iyi bir loglama stratejisi şart.
cat > /etc/caddy/Caddyfile << 'EOF'
{
# Global log seviyesi
log {
level INFO
}
}
api.sirketim.com {
reverse_proxy localhost:8080
log {
output file /var/log/caddy/access.log {
roll_size 100mb
roll_keep 14
roll_keep_for 720h
}
format json {
time_format iso8601
}
# Sağlık kontrolü loglarını filtrele
except_fields request>uri
}
}
EOF
JSON formatındaki logları analiz etmek için birkaç pratik komut:
# Son 100 isteği görüntüle
tail -n 100 /var/log/caddy/access.log | python3 -m json.tool
# 5xx hatalarını filtrele
cat /var/log/caddy/access.log |
jq 'select(.status >= 500)' |
jq '{time: .ts, status: .status, uri: .request.uri, duration: .duration}'
# Yavaş istekleri bul (1 saniyeden uzun)
cat /var/log/caddy/access.log |
jq 'select(.duration > 1.0)' |
jq '{uri: .request.uri, duration: .duration, status: .status}'
# IP bazlı istek sayısı
cat /var/log/caddy/access.log |
jq -r '.request.remote_addr' |
cut -d: -f1 | sort | uniq -c | sort -rn | head -20
Güvenlik Sertleştirme
Üretim ortamına geçmeden önce birkaç önemli güvenlik ayarı daha yapalım:
cat > /etc/caddy/Caddyfile << 'EOF'
{
# Admin API'yi sadece localhost'ta aç
admin localhost:2019
# Email adresi Let's Encrypt için
email [email protected]
# Global timeout
servers {
timeouts {
read_body 10s
read_header 10s
write 30s
idle 120s
}
}
}
api.sirketim.com {
reverse_proxy localhost:8080
# İstek boyutu limiti (10MB)
request_body {
max_size 10MB
}
# Belirli IP'leri engelle
@blocked_ips {
remote_ip 192.0.2.0/24 203.0.113.50
}
abort @blocked_ips
# Sadece belirli metodlara izin ver
@allowed_methods {
not method GET POST PUT DELETE PATCH OPTIONS HEAD
}
respond @allowed_methods "Method Not Allowed" 405
header {
X-Content-Type-Options nosniff
X-Frame-Options DENY
X-XSS-Protection "1; mode=block"
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Content-Security-Policy "default-src 'self'"
Referrer-Policy strict-origin-when-cross-origin
Permissions-Policy "camera=(), microphone=(), geolocation=()"
-Server
-X-Powered-By
}
}
EOF
systemctl reload caddy
Troubleshooting İpuçları
Bir şeyler ters gittiğinde ilk başvuru noktaların bunlar olmalı:
# Caddyfile sözdizimini kontrol et
caddy validate --config /etc/caddy/Caddyfile
# Caddy loglarını canlı izle
journalctl -u caddy -f
# Caddy'nin aldığı konfigürasyonu kontrol et
caddy adapt --config /etc/caddy/Caddyfile --pretty
# TLS sertifika durumunu kontrol et
curl -s localhost:2019/config/apps/tls | python3 -m json.tool
# Upstream bağlantısını test et
curl -v http://localhost:8080/health
# Caddy üzerinden test et
curl -v https://api.sirketim.com/health
Sık karşılaşılan sorunlar ve çözümleri:
- 502 Bad Gateway: Go uygulaması çalışmıyor veya yanlış portta.
systemctl status myappvess -tlnp | grep 8080ile kontrol et. - Sertifika hatası: DNS kaydı doğru mu?
dig api.sirketim.comile kontrol et. Caddy sertifikayı alabilmek için 80 ve 443 portlarının açık olması gerekiyor. - Rate limit sorunları: Geliştirme sırasında kendi IP’nin engellenip engellenmediğini
caddy logsile kontrol et. - WebSocket bağlantı kopması:
keep_aliveve timeout değerlerini artır,header_up Connection "Upgrade"direktifinin var olduğundan emin ol.
Sonuç
Caddy ve Go kombinasyonu modern bir web altyapısı için gerçekten güçlü bir seçenek. Bu yazıda ele aldığımız konuları özetleyecek olursak: temel reverse proxy kurulumundan başlayıp çoklu mikro servis yönetimi, load balancing, CORS, authentication middleware, statik dosya sunumu ve güvenlik sertleştirmeye kadar pek çok konuyu işledik.
Nginx veya Apache’den geçiş yapmayı düşünüyorsan Caddy’nin en büyük avantajının konfigürasyon sadeliği olduğunu unutma. 200 satır Nginx konfigürasyonuyla yapacağın şeyi Caddy’de 20 satırda yapabiliyorsun. Otomatik TLS yönetimi ise tek başına büyük bir zaman tasarrufu sağlıyor.
Go uygulamalarıyla ilgili önemli bir hatırlatma: uygulamanın /health endpoint’ini mutlaka implement et. Caddy’nin sağlık kontrolü mekanizması hem load balancing hem de monitoring için bu endpoint’e güveniyor. Graceful shutdown için de Go’nun sinyal yakalama mekanizmasını doğru kullanmak, sıfır downtime deployment’lar için kritik öneme sahip.
Üretim ortamına geçmeden önce mutlaka caddy validate ile konfigürasyonunu test et ve tüm timeout değerlerini uygulamanın gerçek performans karakteristiklerine göre ayarla. İyi sunucular dalgın yöneticileri affeder ama dalgın sysadminleri iyi yapılandırma kurtarır.
