Nginx Reverse Proxy
Zeus 2.0 artık iki domain üzerinden servis edilir:
enerji.kepmark.com— canonical (varsayılan)energy.turef.ai— ikinci domain
Kullanıcı hangi domain'e girdiyse orada kalır (domain zıplaması yoktur). Detay için aşağıdaki Çift Domain Mimarisi bölümüne bakın.
Genel Ayarlar
worker_processes auto;
client_max_body_size 100M; # OTA firmware (~16MB)
keepalive_timeout 65s;
gzip on; # Level 6, min 1KB
Rate Limiting
| Zone | Rate | Açıklama |
|---|---|---|
api_limit | 30 req/s | API endpoint'leri |
login_limit | 5 req/m | Brute-force koruması |
HTTPS (Port 443)
Port 443'te iki ayrı server bloğu vardır — her domain için bir tane. İkinci blok birincinin birebir kopyasıdır; yalnız server_name + sertifika yolları farklıdır. Tüm location kuralları, header'lar ve TLS parametreleri iki blokta da aynıdır.
# Blok 1 — enerji.kepmark.com (canonical, default_server)
server_name enerji.kepmark.com;
ssl_certificate /etc/letsencrypt/live/enerji.kepmark.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/enerji.kepmark.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/enerji.kepmark.com/chain.pem; # OCSP (chain.pem mevcut)
# Blok 2 — energy.turef.ai (ikinci domain)
server_name energy.turef.ai;
ssl_certificate /etc/letsencrypt/live/energy.turef.ai/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/energy.turef.ai/privkey.pem;
# NOT: ssl_trusted_certificate YOK — turef cert'inde chain.pem üretilmemiş (manuel
# kopya). OCSP stapling fullchain.pem içindeki intermediate ile çalışır; eksik dosya
# referansı olsaydı nginx start edemez ve deploy + rollback ikisi de fail ederdi.
İki blokta da ortak olan ayarlar:
ssl_protocols TLSv1.2 TLSv1.3;
ssl_stapling on;
ssl_stapling_verify on;
# Security Headers
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
Her domain'in 443 bloğu kendi sertifika dosyalarını referans alır; ssl_certificate / ssl_certificate_key eksikse nginx start edemez (tek config / tek container olduğundan diğer domain de düşer):
| Domain | Beklenen yol | Gerekli dosyalar | Yönetim |
|---|---|---|---|
enerji.kepmark.com | /etc/letsencrypt/live/enerji.kepmark.com/ | fullchain.pem, privkey.pem, chain.pem | certbot (otomatik yenileme) |
energy.turef.ai | /etc/letsencrypt/live/energy.turef.ai/ | fullchain.pem, privkey.pem | manuel kopya (renewal config yok) |
ssl_trusted_certificate (OCSP chain.pem) yalnız dosya mevcutsa referans edilir; turef bloğunda chain.pem üretilmediği için bu satır atlanmıştır.
energy.turef.ai sertifikası şu an manuel kopyadır (/etc/letsencrypt/renewal/ config'i yok, flat dosyalar) ve Eyl 2026'da expire olunca otomatik yenilenmez. Kalıcı çözüm: sunucuda turef için certbot ile düzgün issuance (certbot certonly -d energy.turef.ai) — bu sunucu/DevOps işidir, cert dosyaları git'te tutulmaz.
İki server bloğu manuel kopyalanmıştır. Yeni bir location eklerken veya mevcut bir kuralı değiştirirken mutlaka iki blokta da aynı değişikliği yap — yoksa iki domain farklı davranır.
Location Kuralları
| Path | Backend | Timeout | Rate Limit | Not |
|---|---|---|---|---|
/api/ | backend:8000 | 120s | 30r/s | API |
/api/auth/login | backend:8000 | — | 5r/m | Brute-force koruması |
/docs, /redoc | backend:8000 | — | localhost only | Swagger UI |
/ws | backend:8000 | 4 saat | — | WebSocket |
/health | backend:8000 | — | — | Health check |
/_next/static/ | frontend:3000 | — | Cache 1 yıl | Statik dosyalar |
/ | frontend:3000 | 60s | — | React uygulaması |
Çift Domain Mimarisi
Zeus 2.0 iki domain'den servis edilir ve kullanıcı girdiği domain'de kalır.
HTTP → HTTPS yönlendirme (domain korunur)
Port 80'de tek server bloğu iki domain'i de dinler ve $host kullanarak yönlendirir — kullanıcı hangi domain'e geldiyse o domain'de kalır, domain zıplaması olmaz:
server {
listen 80;
server_name enerji.kepmark.com energy.turef.ai;
location /.well-known/acme-challenge/ { # certbot ACME challenge
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri; # $host → gelen domain'i korur
}
}
default_server = kepmark
İlk tanımlı 443 bloğu enerji.kepmark.com olduğundan, SNI göndermeyen veya bilinmeyen Host ile gelen istekler bu bloğa düşer. Sahadaki OCPP şarj istasyonları ve MQTT cihazları canonical enerji.kepmark.com kullandığından bu davranış kasıtlıdır.
KRİTİK: CORS_ORIGINS her iki origin'i içermeli
CORS_ORIGINS (GitHub secret + sunucu .env) her iki origin'i içermek zorundadır:
CORS_ORIGINS=https://enerji.kepmark.com,https://energy.turef.ai
Neden zorunlu? WebSocket (canlı dashboard / telemetri) CSWSH korumasına tabidir: tarayıcı handshake'te Origin header gönderir ve backend bunu CORS_ORIGINS listesiyle karşılaştırır. energy.turef.ai listede yoksa, bu domain'den açılan tarayıcı WS bağlantısı Origin: https://energy.turef.ai gönderir ve 4003 Forbidden origin ile reddedilir — kullanıcı canlı veri göremez.
| Trafik | Mekanizma | Çift domain etkisi |
|---|---|---|
REST /api | Same-origin (tarayıcı aynı domain'e çağırır) | Etkilenmez — CORS_ORIGINS'e bağlı değil |
WebSocket /ws | Origin header → CORS_ORIGINS kontrolü (CSWSH guard) | Liste eksikse 4003 red |
Backend guard referansı:
backend/app/core/websocket/security.py+core/websocket/router.py. OCPP/MQTT gibi native cihazlarOrigingöndermez, bu kontrole tabi değildir (yalnız tarayıcı-yüzü WS).
nginx/nginx.conf git-tracked'tir ve deploy.yml rsync ile sunucuya yazar. Sunucuda elle yapılan her nginx değişikliği bir sonraki deploy'da ezilir. Tüm değişiklikler localhost → git push → CI/CD ile yapılır (yeni domain, location, sertifika yolu dahil). Yeni domain eklerken CORS_ORIGINS secret'ını da güncellemeyi unutma.
Dinamik DNS Resolution (Issue #258)
Docker Compose ortamında container yeniden başlatıldığında IP adresi değişebilir. Nginx, upstream blok'unu başlangıçta bir kez resolve edip cache'ler; bu durumda backend container restart sonrası nginx 502 dönmeye başlar ve düzelmesi için manuel nginx -s reload gerekir.
Eski Yöntem (Sorunlu)
upstream backend {
server backend:8000;
}
location /api/ {
proxy_pass http://backend;
}
Bu yapıda nginx, backend DNS adını bir kez çözer ve sonucu kalıcı olarak tutar. Container restart sonrası eski IP kullanılmaya devam eder.
Yeni Yöntem (Runtime Resolve)
location /api/ {
resolver 127.0.0.11 valid=30s ipv6=off;
set $backend_upstream backend:8000;
proxy_pass http://$backend_upstream;
}
Önemli noktalar:
resolver 127.0.0.11— Docker embedded DNS server'ı kullanılırvalid=30s— DNS yanıtları 30 saniye cache'lenir, sonrasında yeniden soruluripv6=off— Docker IPv6 desteklemez, DNS sorgusu hızlanırset $backend_upstream— Değişken kullanımı nginx'i upstream'i runtime'da resolve etmeye zorlarproxy_pass http://$backend_upstream— Variable interpolation ile dinamik resolve
Bu pattern /api/, /ws, /, /_next/static/ gibi tüm proxy_pass blok'larında uygulanır. Container restart sonrası en geç 30 saniye içinde yeni IP otomatik kullanılır; nginx reload gerekmez.