Site açılıyor ama tüm kullanıcılar uygulama logunda tek bir IP ile kaydediliyor ya da ağır sayfalar 60 saniye sonra 504 Gateway Timeout ile düşüyor. Nginx'in arkasında bir Node.js uygulaması, bir Python backend'i veya başka bir sunucu varsa, bu iki belirtinin neredeyse her zaman tek bir kökü vardır: eksik yapılandırılmış ters proxy. Bu yazıda üretimde işe yarayan bloğu yazacağız ve insanların hata yaptığı yerleri işaretleyeceğiz.
Gerçekten çalışan temel ters proxy bloğu
Uygulamanın 127.0.0.1:3000 üzerinde dinlediğini ve Nginx'in example.com trafiğini ona iletmesi gerektiğini varsayalım. Hem doğru hem de sürdürülebilir en az yapılandırma:
upstream app_backend {
server 127.0.0.1:3000;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name example.com;
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Connection "";
}
}
Bu bloktan üç satırı silerseniz sorun geri gelir. proxy_http_version 1.1 olmadan Nginx backend'e HTTP/1.0 ile bağlanır ve upstream'deki keepalive pratikte etkisiz kalır. Connection "" satırı Connection: close başlığının gönderilmesini engeller; bırakırsanız her istek yeni bir TCP bağlantısı oluşturur ve yüksek yükte TIME_WAIT portları birikir. X-Forwarded-Proto ise uygulamanın kullanıcının HTTPS üzerinde olup olmadığını anladığı şeydir; o olmadan dahili yönlendirmeler http://'ye döner ve yönlendirme döngüsü oluşturur.
X-Forwarded-For neden yeterli değil
X-Forwarded-For başlığı bir liste, tek bir IP değil. Nginx'in önünde Cloudflare veya başka bir yük dengeleyici olduğunda bu başlığın değeri 203.0.113.5, 198.51.100.7 gibi bir şey olur ve uygulama hangisinin geçerli olduğunu bilmelidir. Kodunuz yalnızca split(",")[0] alıyorsa, herkes sahte başlık göndererek kendi IP'sini taklit edebilir. Doğru yol, güvenilir IP aralıklarını tanımlamaktır:
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
real_ip_header CF-Connecting-IP;
Bundan sonra $remote_addr kendisi kullanıcının gerçek IP'sidir ve uygulamanın bir şey ayrıştırmasına gerek yoktur. Bu bloğu koymazsanız ve aynı zamanda başlığa güvenirseniz, güvenlik loglarınız değersiz hale gelir ve rate limit yanlış IP'ye uygulanır.
Zaman aşımları: bilinçli seçilmesi gereken sayı
Nginx'te proxy_read_timeout varsayılan değeri altmış saniyedir. Normal bir API için bu sayı cömerttir; veritabanı üzerinde ağır çalışan bir rapor oluşturucu için azdır. stream ile yanıt veren bir endpoint için anlamsızdır. Üç ayrı zaman aşımı vardır ve insanlar genellikle yalnızca ilkini görür:
| Direktif | Varsayılan | Nerede sorun çıkarır |
|---|---|---|
proxy_connect_timeout | 60s | Backend ayağa kalkmamış veya port yanlış |
proxy_send_timeout | 60s | Büyük yükleme veya gövdeyi okumada yavaş backend |
proxy_read_timeout | 60s | Uzun yanıt, ağır rapor, iş kuyruğu |
Normal bir web uygulaması için proxy_connect_timeout 5s koyarım ve proxy_read_timeout'u 120 saniyeye ayarlarım. Bağlantı sayısını kısa tutun çünkü backend ölmüşse kullanıcı sitenin bozuk olduğunu anlamak için beş dakika beklememelidir. Ancak okuma zaman aşımını uzatın, çünkü yarım kalmış bir isteği kesmek genellikle beklemekten daha kötüdür.
Pratik bir nokta: Zaman aşımını Nginx'te artırdıysanız ama önündeki yük dengeleyicide artırmadıysanız, aynı 504 döner ve siz uygulamada hata ararsınız. Zaman aşımlarını dışarıdan içeriye doğru azalan şekilde düzenleyin.
Yanıtı tamponlama ve hata yaptıkları yer
Nginx varsayılan olarak backend yanıtını bellekte veya diskte tamponlar. Bu, statik dosyalar ve küçük yanıtlar için mükemmeldir: backend hızlıca serbest kalır ve yavaş istemci sunucunuzu meşgul etmez. Ancak Server-Sent Events, WebSocket veya herhangi bir streaming yanıt için tamponlama, tüm yanıt bitene kadar kullanıcının hiçbir şey görmemesi anlamına gelir.
Burada hata yapıyorlar: WebSocket için yalnızca proxy_set_header Upgrade $http_upgrade ekliyorlar ve proxy_buffering off'u unutuyorlar. Sonuç, bağlantının kurulması, logların sağlıklı olması ama mesajların tuhaf bir gecikmeyle gelmesi veya hiç gelmemesidir. Eksiksiz WebSocket yapılandırması şudur:
location /ws/ {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_buffering off;
proxy_read_timeout 3600s;
}
Dikkat edin, burada Connection "upgrade"'tir, boş dize değil. Her iki bloğu bir dosyaya kopyalar ve /ws/ yolu için Connection "" koyarsanız, handshake başarısız olur ve tarayıcı konsolunda WebSocket connection failed hatası görürsünüz.
Diskte tamponlama ve /var'ın dolması
Yanıt bellek tamponundan büyük olduğunda, Nginx onu proxy_temp_path yolunda diske yazar. Varsayılan /var/lib/nginx/proxy'dir. Biri proxy üzerinden büyük dosyalar indirirse ve proxy_max_temp_file_size'ı sınırlamadıysanız, /var bölümü dolar ve sonrasında ne log yazabilirsiniz ne de session. Belirtisi de şudur: her şey bir noktaya kadar sağlıklıdır ve sonra hiçbir uygulama hatası olmadan aniden 500 alırsınız.
Host başlığı ve yanlış yönlendirmeler
proxy_set_header Host $host koymazsanız, Nginx Host değerini orijinal istekten korur ki bu çoğu durumda doğrudur; ancak upstream'in dahili bir adı olduğunda backend o dahili ada yönlendirme yapabilir ve kullanıcı erişilemez bir adrese yönlendirilir. Uygulama birden fazla alan adında hizmet veriyorsa $host doğrudur; her zaman belirli bir alan adının görülmesi gerekiyorsa sabit değer koyun.
DNS'i henüz tam oturmamış alan adları için her şeyden önce doğru kaydın seçildiğinden emin olun; CNAME ve A record arasındaki fark tam burada kendini gösterir ve yanlışsa sağlıklı Nginx bile yanlış siteye hizmet verir.
Kullanıcı haber vermeden önce test etmek
Reload'dan önce her zaman nginx -t çalıştırın. Sonra curl -I -H "Host: example.com" https://127.0.0.1/ ile yanıtı sunucunun kendisinden görün ki sorunun DNS veya CDN'den olmadığından emin olun. Backend'e gerçekten giden başlıkları görmek için backend'de başlıkları yazdıran geçici bir endpoint oluşturun; bu, tahmin etmekten daha hızlıdır.
502 Bad Gateway hatası alıyorsanız, önce ss -ltnp | grep 3000 ile backend'in o portta dinleyip dinlemediğine bakın. 504 ise zaman aşımlarını kontrol edin. Nginx logunda 499 görüyorsanız, kullanıcının kendisi bağlantıyı kapatmıştır ve sorun başka yerdedir. Bu kodların tam anlamını HTTP durum kodu rehberinde derledik.
Açıkça söylemem gereken bir ödünleşim: Yanıtı tamponlamak throughput'u artırır ve backend'in bellek tüketimini azaltır, ancak küçük yanıtlar için ilk bayt gecikmesini artırır. Uygulamanız birkaç kilobaytlık yanıtlar veren bir API ise tamponlamayı açık bırakın. Streaming veya SSE'niz varsa kapatın ve bedelini kabul edin.
WordPress projeleri için genellikle bu bloğu sıfırdan yazmanız gerekmez; yönetilen bir WordPress hosting bu katmanı önceden ayarlamıştır ve siz yalnızca önbellek ve zaman aşımlarının eklentilerinizle çakışmadığından emin olmalısınız. Kendi özel sunucunuzda çalışıyorsanız, dokümantasyon ve bilgi tabanında daha fazla hazır örnek vardır.
Sık sorulan sorular
Tüm kullanıcılar neden uygulama logunda sunucu IP'si ile kaydediliyor?
Çünkü uygulama IP'yi TCP bağlantısından okur ve o bağlantı Nginx tarafından gelir. Nginx'te X-Forwarded-For başlığını ayarlamalı ve uygulamayı ona güvenecek şekilde yapılandırmalısınız. Nginx'in önünde başka bir proxy de varsa, $remote_addr'ın kendisinin doğru olması için set_real_ip_from kullanın.
proxy_pass'ın sonundaki eğik çizgi ile olmadan farkı nedir?
proxy_pass http://backend/'de konum yolu eğik çizgi ile değiştirilir, ancak proxy_pass http://backend'de tüm orijinal URI değişmeden backend'e gider. Uygulamanız bir alt yol bekliyorsa ve bunu yanlış ayarlarsanız, Nginx logunda hiçbir şey görünmeden tüm istekler 404 ile karşılaşır.
proxy_buffering'i her şey için kapatmalı mıyım?
Hayır. Genel olarak kapatmak, backend'i yavaş istemcilere karşı savunmasız hale getirir ve bellek tüketimini artırır. Yalnızca gerçekten streaming olan yollar için kapatın ve gerisini varsayılan tamponlamayla bırakın.
Yapılandırmayı değiştirdikten sonra ne yapmak gerekir?
Önce nginx -t, sonra systemctl reload nginx. Gerekmedikçe restart kullanmayın çünkü aktif bağlantıları kapatır. Site bir yük dengeleyici arkasında birden fazla sunucuda ise, hizmet kesintisi olmaması için reload'u tek tek yapın.
Yorumlar 0
Henüz yorum yok — ilk siz olun!