Ana içeriğe geç

Nginx Reverse Proxy

Çift domain

Zeus 2.0 artık iki domain üzerinden servis edilir:

  • enerji.kepmark.comcanonical (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

ZoneRateAçıklama
api_limit30 req/sAPI endpoint'leri
login_limit5 req/mBrute-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;
İki sertifika da sunucuda gerekli

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):

DomainBeklenen yolGerekli dosyalarYönetim
enerji.kepmark.com/etc/letsencrypt/live/enerji.kepmark.com/fullchain.pem, privkey.pem, chain.pemcertbot (otomatik yenileme)
energy.turef.ai/etc/letsencrypt/live/energy.turef.ai/fullchain.pem, privkey.pemmanuel 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.

turef sertifikası otomatik yenilenmiyor

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.

location değişikliğinde İKİ bloğu da güncelle

İ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ı

PathBackendTimeoutRate LimitNot
/api/backend:8000120s30r/sAPI
/api/auth/loginbackend:80005r/mBrute-force koruması
/docs, /redocbackend:8000localhost onlySwagger UI
/wsbackend:80004 saatWebSocket
/healthbackend:8000Health check
/_next/static/frontend:3000Cache 1 yılStatik dosyalar
/frontend:300060sReact 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.

TrafikMekanizmaÇift domain etkisi
REST /apiSame-origin (tarayıcı aynı domain'e çağırır)Etkilenmez — CORS_ORIGINS'e bağlı değil
WebSocket /wsOrigin 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 cihazlar Origin göndermez, bu kontrole tabi değildir (yalnız tarayıcı-yüzü WS).

Sunucuda elle nginx düzenlemesi EZİLİR

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ır
  • valid=30s — DNS yanıtları 30 saniye cache'lenir, sonrasında yeniden sorulur
  • ipv6=off — Docker IPv6 desteklemez, DNS sorgusu hızlanır
  • set $backend_upstream — Değişken kullanımı nginx'i upstream'i runtime'da resolve etmeye zorlar
  • proxy_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.