Modern web geliştirme dünyasında Python uygulamalarını production ortamına taşımak her zaman biraz karmaşık olabiliyor. Nginx veya Apache gibi köklü web sunucuları bu iş için yaygın tercih olsa da, son yıllarda Caddy ciddi bir alternatif olarak öne çıkıyor. Özellikle otomatik HTTPS, sade konfigürasyon sözdizimi ve yerleşik reverse proxy desteğiyle Caddy, Django ve FastAPI gibi Python ASGI uygulamalarını serve etmek için oldukça güçlü bir platform haline geldi. Bu yazıda gerçek bir production senaryosu üzerinden Caddy ile Django/ASGI kurulumunu adım adım ele alacağız.
Neden Caddy ve ASGI?
Önce temel kavramları netleştirelim. ASGI (Asynchronous Server Gateway Interface), Python’un async özelliklerini tam anlamıyla kullanan modern bir web uygulama arayüzü standardı. Django 3.0 ile birlikte ASGI desteği geldi ve Django Channels, WebSocket desteği için bu standarda dayanıyor. FastAPI ise doğası gereği ASGI üzerine inşa edilmiş bir framework.
Caddy’nin bu senaryodaki avantajları şunlar:
- Otomatik TLS: Let’s Encrypt ve ZeroSSL entegrasyonu kutudan çıkar çıkmaz çalışıyor
- Sade Caddyfile sözdizimi: Nginx konfigürasyonuyla kıyaslandığında çok daha okunabilir
- HTTP/2 ve HTTP/3 desteği: Varsayılan olarak etkin
- Reverse proxy: Static dosya serving dahil her şey tek yerden yönetiliyor
- Sıfır downtime reload: Konfigürasyon değişikliklerinde servis kesintisi yok
Senaryomuz şu şekilde: Bir Django uygulaması var, Channels ile WebSocket desteği de eklenmiş. ASGI sunucusu olarak Uvicorn kullanıyoruz, process yönetimi için Gunicorn (workers olarak Uvicorn’u çalıştırıyor). Caddy ise önde duran reverse proxy ve static file sunucusu görevini üstleniyor.
Sistem Hazırlığı ve Kurulum
Ubuntu 22.04 LTS üzerinde çalışıyoruz. Önce sistemi güncelleyelim ve gerekli bağımlılıkları kuralım.
sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-pip python3-venv git curl
Caddy Kurulumu
Caddy’yi resmi paket deposundan kuruyoruz:
sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https
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 tamamlandıktan sonra Caddy servisinin durumuna bakalım:
sudo systemctl status caddy
sudo systemctl enable caddy
Caddy varsayılan olarak /etc/caddy/Caddyfile dosyasını okur. Binary ise /usr/bin/caddy altında bulunur.
Python Ortamının Hazırlanması
Uygulamamız için ayrı bir kullanıcı oluşturalım. Production’da her zaman dedicated bir servis kullanıcısı kullanın:
sudo useradd -m -s /bin/bash djangoapp
sudo su - djangoapp
Virtual environment oluşturup gerekli paketleri kuralım:
python3 -m venv /home/djangoapp/venv
source /home/djangoapp/venv/bin/activate
pip install django channels channels-redis gunicorn uvicorn[standard] whitenoise daphne
Burada uvicorn[standard] kurduğumuza dikkat edin. Bu, WebSocket desteği için gerekli olan websockets ve yüksek performanslı HTTP işleme için httptools ve uvloop paketlerini de beraberinde getiriyor.
Django Projesini ASGI için Yapılandırma
Mevcut bir Django projesini ASGI uyumlu hale getiriyoruz. Proje yapısı şöyle:
/home/djangoapp/myproject/
├── myproject/
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ ├── asgi.py
│ └── wsgi.py
├── static/
├── media/
├── manage.py
└── requirements.txt
asgi.py dosyasını Django Channels ile birlikte çalışacak şekilde düzenliyoruz:
# myproject/asgi.py
import os
from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from channels.security.websocket import AllowedHostsOriginValidator
import myapp.routing
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
django_asgi_app = get_asgi_application()
application = ProtocolTypeRouter({
"http": django_asgi_app,
"websocket": AllowedHostsOriginValidator(
AuthMiddlewareStack(
URLRouter(
myapp.routing.websocket_urlpatterns
)
)
),
})
settings.py dosyasına ASGI ve Channels ile ilgili ayarları ekliyoruz:
# myproject/settings.py içine eklenecek bölümler
INSTALLED_APPS = [
# ... mevcut uygulamalar ...
'channels',
'whitenoise.runserver_nostatic',
]
# ASGI uygulama tanımı
ASGI_APPLICATION = 'myproject.asgi.application'
# Channel layer - Redis backend
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
# Static dosyalar için WhiteNoise
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'whitenoise.middleware.WhiteNoiseMiddleware', # SecurityMiddleware'den hemen sonra
# ... diğer middleware'ler ...
]
STATIC_ROOT = '/home/djangoapp/myproject/staticfiles'
STATIC_URL = '/static/'
MEDIA_ROOT = '/home/djangoapp/myproject/media'
MEDIA_URL = '/media/'
# Allowed hosts
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
# CSRF ve güvenlik ayarları
CSRF_TRUSTED_ORIGINS = ['https://yourdomain.com', 'https://www.yourdomain.com']
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
Gunicorn ile ASGI Sunucusu Yapılandırması
Gunicorn’u Uvicorn worker sınıfıyla çalıştırmak en yaygın production yaklaşımı. Bu kombinasyon hem process yönetimi hem de ASGI desteği konusunda sağlam bir zemin sunuyor.
Gunicorn konfigürasyon dosyası oluşturalım:
# /home/djangoapp/myproject/gunicorn.conf.py
import multiprocessing
# Bağlantı ayarları - Unix socket kullanıyoruz (TCP'den daha hızlı)
bind = "unix:/run/djangoapp/gunicorn.sock"
umask = 0o007
# Worker ayarları
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "uvicorn.workers.UvicornWorker"
worker_connections = 1000
timeout = 30
keepalive = 5
# Process yönetimi
daemon = False
pidfile = "/run/djangoapp/gunicorn.pid"
user = "djangoapp"
group = "djangoapp"
# Logging
accesslog = "/var/log/djangoapp/access.log"
errorlog = "/var/log/djangoapp/error.log"
loglevel = "info"
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(D)s'
# Graceful restart
graceful_timeout = 30
max_requests = 1000
max_requests_jitter = 50
max_requests ve max_requests_jitter parametreleri önemli: her worker belirli sayıda istek karşıladıktan sonra yeniden başlıyor. Bu, olası memory leak sorunlarını önlemek için güzel bir pratik.
Gerekli dizinleri oluşturalım:
sudo mkdir -p /run/djangoapp /var/log/djangoapp
sudo chown djangoapp:djangoapp /run/djangoapp /var/log/djangoapp
Systemd Servis Dosyası
Gunicorn’u systemd servisi olarak tanımlıyoruz:
sudo nano /etc/systemd/system/djangoapp.service
[Unit]
Description=Django ASGI Application (Gunicorn + Uvicorn)
After=network.target redis.service
Requires=redis.service
[Service]
Type=notify
User=djangoapp
Group=djangoapp
RuntimeDirectory=djangoapp
WorkingDirectory=/home/djangoapp/myproject
# Ortam değişkenleri
Environment="DJANGO_SETTINGS_MODULE=myproject.settings"
Environment="DJANGO_ENV=production"
EnvironmentFile=/home/djangoapp/myproject/.env
# Komut
ExecStart=/home/djangoapp/venv/bin/gunicorn
--config /home/djangoapp/myproject/gunicorn.conf.py
myproject.asgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Hassas bilgileri (database şifresi, secret key vb.) .env dosyasında tutuyoruz:
# /home/djangoapp/myproject/.env
DJANGO_SECRET_KEY=your-super-secret-key-here
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
REDIS_URL=redis://127.0.0.1:6379/0
sudo systemctl daemon-reload
sudo systemctl enable djangoapp
sudo systemctl start djangoapp
sudo systemctl status djangoapp
Caddyfile Yapılandırması
İşte konunun en kritik bölümü. /etc/caddy/Caddyfile dosyasını açıp yapılandırmayı yazıyoruz:
yourdomain.com, www.yourdomain.com {
# TLS otomatik olarak yönetiliyor, ekstra ayar gerekmez
# Encode middleware - gzip ve zstd sıkıştırma
encode gzip zstd
# Güvenlik header'ları
header {
X-Content-Type-Options nosniff
X-Frame-Options DENY
X-XSS-Protection "1; mode=block"
Referrer-Policy strict-origin-when-cross-origin
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
-Server
}
# Static dosyalar - doğrudan Caddy serve ediyor (Gunicorn'a gitmiyor)
handle /static/* {
root * /home/djangoapp/myproject
file_server
header Cache-Control "public, max-age=31536000, immutable"
}
# Media dosyaları
handle /media/* {
root * /home/djangoapp/myproject
file_server
header Cache-Control "public, max-age=86400"
}
# WebSocket bağlantıları - özel timeout ayarları
handle /ws/* {
reverse_proxy unix//run/djangoapp/gunicorn.sock {
transport http {
versions 1.1
}
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
flush_interval -1
}
}
# Django uygulaması - tüm diğer istekler
handle {
reverse_proxy unix//run/djangoapp/gunicorn.sock {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
# Health check
health_uri /health/
health_interval 30s
health_timeout 5s
# Load balancing (birden fazla worker socket için)
lb_policy least_conn
# Timeout ayarları
transport http {
dial_timeout 10s
response_header_timeout 60s
keepalive 120s
}
}
}
# Access log
log {
output file /var/log/caddy/yourdomain.access.log {
roll_size 100mb
roll_keep 5
}
format json
}
}
WebSocket handle /ws/* bloğunda flush_interval -1 ayarı kritik. Bu değer Caddy’ye response’u buffer’lamadan anında iletmesini söylüyor, streaming ve WebSocket için zorunlu.
Caddy’nin Unix socket üzerinden iletişim kurabilmesi için socket dosyasının Caddy kullanıcısı tarafından erişilebilir olması gerekiyor:
sudo usermod -aG djangoapp caddy
# veya socket permission'larını ayarla
sudo chmod 660 /run/djangoapp/gunicorn.sock
Caddyfile syntax kontrolü ve reload:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
Çoklu Uygulama Senaryosu
Gerçek dünyada tek bir sunucuda birden fazla Python uygulaması çalıştırmak gerekebiliyor. Mesela hem bir Django uygulaması hem de ayrı bir FastAPI servisi var. Caddyfile bunu zarif şekilde hallediyor:
# Ana Django uygulaması
app.yourdomain.com {
encode gzip zstd
handle /static/* {
root * /home/djangoapp/myproject
file_server
}
handle {
reverse_proxy unix//run/djangoapp/gunicorn.sock {
header_up Host {host}
header_up X-Forwarded-Proto {scheme}
}
}
}
# FastAPI mikroservisi - aynı sunucuda farklı socket
api.yourdomain.com {
encode gzip zstd
reverse_proxy unix//run/fastapiapp/uvicorn.sock {
header_up Host {host}
header_up X-Forwarded-Proto {scheme}
}
}
# Admin paneli - IP kısıtlaması ile
admin.yourdomain.com {
@allowed_ips {
remote_ip 192.168.1.0/24 10.0.0.0/8
}
handle @allowed_ips {
reverse_proxy unix//run/djangoapp/gunicorn.sock
}
respond "Erişim reddedildi" 403
}
Rate Limiting ve Güvenlik Katmanı
Production ortamında rate limiting ve temel güvenlik önlemleri şart. Caddy’nin rate_limit eklentisi için önce Caddy’yi custom build etmek gerekiyor, ama yerleşik özelliklerle de epey şey yapabiliyoruz:
# xcaddy ile rate_limit modülü dahil özel build
sudo apt install golang-go
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build --with github.com/mholt/caddy-ratelimit
Rate limiting dahil gelişmiş Caddyfile:
yourdomain.com {
# Bot ve tarayıcı filtreleme
@blocked_agents {
header User-Agent *bot*
header User-Agent *crawler*
header User-Agent *scanner*
}
respond @blocked_agents 403
# API endpoint'leri için rate limiting
rate_limit {
zone api_zone {
match {
path /api/*
}
key {remote_host}
events 100
window 1m
}
zone login_zone {
match {
path /accounts/login/
method POST
}
key {remote_host}
events 5
window 1m
}
}
# Django uygulaması
reverse_proxy unix//run/djangoapp/gunicorn.sock {
header_up Host {host}
header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
}
}
Performans İzleme ve Log Yönetimi
Caddy JSON log formatı log yönetim araçlarıyla mükemmel entegre oluyor. journalctl ile temel takip:
# Caddy logları
sudo journalctl -u caddy -f
# Django uygulama logları
sudo journalctl -u djangoapp -f
# Access log'u gerçek zamanlı izle
sudo tail -f /var/log/caddy/yourdomain.access.log | python3 -m json.tool
# Gunicorn error logları
sudo tail -f /var/log/djangoapp/error.log
Caddy’nin admin API’si ile anlık metrik okuma da mümkün. Varsayılan olarak localhost:2019 adresinde dinliyor:
# Caddy konfigürasyonunu JSON olarak görüntüle
curl -s http://localhost:2019/config/ | python3 -m json.tool
# Aktif bağlantı ve upstream durumu
curl -s http://localhost:2019/reverse_proxy/upstreams | python3 -m json.tool
Static Dosya Optimizasyonu
Django’nun collectstatic komutunu çalıştırdıktan sonra Caddy’nin static file serving’ini optimize edelim:
# djangoapp kullanıcısı olarak
source /home/djangoapp/venv/bin/activate
cd /home/djangoapp/myproject
python manage.py collectstatic --noinput
Caddyfile’a dosya türüne göre cache header ekleyebiliriz:
handle /static/* {
root * /home/djangoapp/myproject
file_server
@fonts path *.woff *.woff2 *.ttf *.eot
header @fonts Cache-Control "public, max-age=31536000, immutable"
@images path *.jpg *.jpeg *.png *.gif *.svg *.ico *.webp
header @images Cache-Control "public, max-age=2592000"
@scripts path *.js *.css
header @scripts Cache-Control "public, max-age=31536000, immutable"
}
Sık Karşılaşılan Sorunlar
Permission hatası – Unix socket erişimi: Caddy www-data veya caddy kullanıcısı olarak çalışıyor, socket ise djangoapp grubuna ait. Gunicorn konfigürasyonunda umask = 0o007 ayarı ve Caddy kullanıcısını djangoapp grubuna eklemek bu sorunu çözüyor.
WebSocket bağlantısı kopuyor: flush_interval -1 ayarını unutmak en yaygın neden. Ayrıca Caddy’nin default timeout değerleri WebSocket için yeterince uzun olmayabiliyor. transport http bloğuna keepalive değerini artırmak gerekebilir.
HTTPS yönlendirmesi çalışmıyor: Caddy HTTP’den HTTPS’e otomatik yönlendirme yapıyor, ama Django tarafında SECURE_PROXY_SSL_HEADER ayarı olmadan uygulama kendini HTTP üzerinde sanıyor. Bu özellikle request.build_absolute_uri() gibi metodlarda beklenmedik http:// URL’leri üretiyor.
Large file upload sorunları: Caddy’nin varsayılan request body limiti var. Büyük dosya yüklemeleri için:
yourdomain.com {
request_body {
max_size 100mb
}
# ...
}
Sonuç
Caddy ile Django/ASGI kombinasyonu, modern Python web uygulamaları için hem kurulumu kolay hem de production’da güvenilir bir stack sunuyor. Nginx’e kıyasla konfigürasyon sadeliği gerçekten dikkat çekici; özellikle otomatik TLS yönetimi DevOps yükünü ciddi ölçüde azaltıyor.
Bu yazıda ele aldığımız mimari şu katmanlardan oluşuyor: Caddy (reverse proxy + static files + TLS), Gunicorn (process manager), Uvicorn workers (ASGI runtime) ve Django (uygulama). Her katmanın sorumluluğu net şekilde ayrılmış durumda.
Dikkat edilmesi gereken kritik noktalar şunlar: Unix socket kullanımı TCP’ye göre performans avantajı sağlıyor, WebSocket trafiği için flush_interval -1 zorunlu, static dosyaları Caddy’nin doğrudan serve etmesi Gunicorn’un üzerindeki yükü azaltıyor ve SECURE_PROXY_SSL_HEADER ayarı Django’nun HTTPS’yi doğru algılaması için şart.
Uygulamanız büyüdükçe aynı Caddyfile içinde birden fazla upstream eklemek, load balancing yapmak veya farklı subdomain’leri farklı servislere yönlendirmek çok kolay hale geliyor. Bu esneklik Caddy’yi microservice mimarileri için de cazip bir seçenek yapıyor.