Bu makalede8 bölüm
- Teknoloji
- Nginx, ngx_http_proxy_module, ngx_http_realip_module, WebSocket, PHP-FPM, Docker
- Konu
- Uygulamaları Nginx arkasında doğru yayınlamak
- Seviye
- Orta–ileri
- Ön koşullar
- Linux sunucuda Nginx kurulu ve temel server bloğu bilgisi
- Arka uç uygulamanın dinlediği adres ve port
- Yapılandırmayı test edip yeniden yükleme yetkisi
Uygulamayı Nginx arkasına aldınız, site açılıyor. Birkaç gün sonra loglarda bütün ziyaretçiler 127.0.0.1’den geliyor, ödeme dönüşü HTTP’ye yönleniyor, WebSocket bağlantısı tam 60. saniyede kopuyor, yoğun saatte 502 düşüyor. Bunların neredeyse hepsi varsayılan değerlerle bırakılmış bir reverse proxy’nin sonucudur. Node.js, Python, Java ya da container içindeki bir uygulamanın önüne Nginx koymak TLS sonlandırma, sıkıştırma, statik dosya ve erişim kontrolünü tek noktada toplar; ama o noktadaki her direktif bilinçli yazılmalı. Aşağıda üretimde kullanılan iskeleti direktif direktif açıyor, 502/504 teşhisinde konsolda izlenecek sırayı veriyoruz.
Reverse proxy’nin görevleri
İstemci yalnızca reverse proxy ile konuşur; arkadaki uygulama sunucularını görmez. Kurumsal web altyapısında Nginx bu rolde şu işleri üstlenir:
- TLS sonlandırma: Sertifika ve protokol ayarları uygulamadan bağımsız yönetilir.
- Bağlantı tamponlama: Yavaş istemcilerin uygulama işçilerini meşgul etmesini engeller; yanıt Nginx’e hızla aktarılır, istemciye Nginx gönderir.
- Yönlendirme: Aynı alan adında
/api/bir servise,/başka bir servise gidebilir. - Yük dağıtımı ve sağlık: Birden fazla arka uç arasında dağıtım ve hatalı sunucuyu geçici olarak devreden çıkarma.
- Politika noktası: İstek boyutu sınırı, rate limit, IP kısıtlaması, güvenlik başlıkları.
Bu işlerin her biri ayrı bir arıza noktasıdır. Web sunucusu seçimi ise ayrı bir karar; onu Nginx, Apache ve IIS karşılaştırması yazısında tartıştık. Buradan sonrası Nginx üzerine.
Temel proxy yapılandırması
Aşağıdaki iskelet 127.0.0.1:3000’de dinleyen bir uygulamayı yayınlar. Arka ucu baştan upstream bloğunda tanımlayın; ikinci sunucuyu eklemek ve keepalive açmak sonradan tek satırlık iş olur.
upstream app_backend {
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
keepalive 32;
}
server {
listen 443 ssl;
http2 on;
server_name app.example.com;
# ssl_certificate ve ssl_certificate_key satırları burada
client_max_body_size 20m;
location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection "";
include /etc/nginx/snippets/proxy-headers.conf;
}
}
http2 on; direktifi Nginx 1.25.1 ile geldi; daha eski sürümlerde listen 443 ssl http2; sözdizimi kullanılır. client_max_body_size varsayılan olarak 1 MB’tır. Dosya yükleyen bir uygulamayı Nginx arkasına taşıdıktan sonra gelen ilk şikâyet genelde budur: 413 Request Entity Too Large. Değeri uygulamanın kendi yükleme sınırıyla eşitleyin.
proxy_pass ve eğik çizgi kuralı
Taşıma sonrası açıklanamayan 404’lerin baş şüphelisi proxy_pass değerinin sonundaki URI parçasıdır:
| Yapılandırma | İstek | Arka uca giden yol |
|---|---|---|
location /api/ { proxy_pass http://app; } |
/api/users | /api/users |
location /api/ { proxy_pass http://app/; } |
/api/users | /users |
location /api/ { proxy_pass http://app/v2/; } |
/api/users | /v2/users |
Kural şudur: proxy_pass içinde bir URI (en az tek bir /) varsa, location ile eşleşen önek o URI ile değiştirilir. Regex ile tanımlanmış location’larda URI içeren proxy_pass kullanılamaz (nginx -t hata verir); rewrite … break ile URI değiştirildiğinde ise proxy_pass içindeki URI yok sayılır ve değiştirilmiş yol iletilir. Bu durumlarda URI’siz proxy_pass kullanıp yolu açıkça yeniden yazmak daha öngörülebilirdir. Tek karakter, bambaşka davranış.
İstemci IP’si ve protokol başlıkları
Uygulama TCP bağlantısını Nginx’ten alır. İstemcinin IP’sini, kullandığı protokolü ve istediği alan adını yalnızca başlıklardan öğrenebilir. Bu başlıkları her server bloğuna ayrı ayrı yazmak yerine tek bir snippet dosyasında toplayın:
# /etc/nginx/snippets/proxy-headers.conf
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 X-Forwarded-Host $host;
Nginx varsayılan olarak arka uca Host başlığında proxy_pass içindeki adı ($proxy_host) gönderir; çok kiracılı uygulamalar ve mutlak URL üreten çatılar bu yüzden kullanıcıyı app_backend gibi bir adrese yönlendirir. X-Forwarded-Proto eksik olduğunda uygulama isteğin HTTP olduğunu sanır; oturum çerezini Secure işaretlemez ya da sonsuz HTTPS yönlendirme döngüsüne girer.
Asıl tuzak kalıtım kuralındadır: bir location içinde tek bir proxy_set_header tanımlarsanız, üst bloktaki tüm proxy_set_header tanımları o location için geçersiz olur. nginx -t bunu yakalamaz, çünkü yapılandırma geçerlidir. Snippet’i her location’a include etmek tuzağı ortadan kaldırır. Uygulama tarafında da proxy’ye güvenmeyi açıkça tanımlayın (Express’te trust proxy, Django’da SECURE_PROXY_SSL_HEADER, Laravel’de TrustProxies ara katmanı); aksi halde başlıklar yok sayılır.
Cloudflare veya başka bir proxy arkasında gerçek IP
Nginx’in önünde Cloudflare, bir yük dengeleyici ya da WAF varsa $remote_addr o cihazın adresidir. ngx_http_realip_module ile yalnızca güvendiğiniz kaynaklardan gelen başlığa itibar edilir:
# http bloğunda; aralıkların tamamını https://www.cloudflare.com/ips/ listesinden alın
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 2400:cb00::/32;
# ... listedeki diğer IPv4 ve IPv6 aralıkları
real_ip_header CF-Connecting-IP;
Önde kendi yük dengeleyiciniz varsa real_ip_header X-Forwarded-For; ve real_ip_recursive on; kullanılır; bu durumda güvenilen tüm ara proxy’lerin adresleri set_real_ip_from ile tanımlanmalıdır. set_real_ip_from 0.0.0.0/0 yazmak, herkesin başlık göndererek IP sahteciliği yapmasına izin verir; “çalışsın diye” yazılmış bu satırla sahada sık karşılaşılır ve o andan itibaren rate limit ile IP kısıtlaması anlamını yitirir. Cloudflare’in IP listesi değişebilir. Bu dosyayı periyodik güncelleyen bir görev kurun. Cloudflare ile önbellek katmanının birlikte nasıl çalıştığı için Cloudflare, WP Rocket ve Redis rehberine bakabilirsiniz.
Zaman aşımı ve buffer ayarları
Nginx’in üç proxy zaman aşımı vardır ve hiçbiri toplam istek süresini ölçmez; her biri iki işlem arasındaki boşluğa uygulanır:
| Direktif | Varsayılan | Ne ölçer |
|---|---|---|
proxy_connect_timeout |
60s | Arka uçla TCP bağlantısı kurma süresi (genellikle 75 saniyeyi aşamaz) |
proxy_send_timeout |
60s | Arka uca iki yazma işlemi arası |
proxy_read_timeout |
60s | Arka uçtan iki okuma işlemi arası |
Kural basit: bağlantı zaman aşımını kısa tutun (aynı sunucudaki bir servis için birkaç saniye yeterlidir), okuma zaman aşımını ise yalnızca uzun süren uçlar için location bazında uzatın. Rapor üreten bir /export/ yolu için 300 saniye tanımlamak, tüm siteye 300 saniye vermekten daha güvenlidir; aksi halde takılan istekler işçi bağlantılarını uzun süre tutar. Kalıcı çözüm mimaridedir: uzun işi kuyruğa alın, istemci durumu sorgulasın. Zaman aşımını büyütmek tedavi değildir.
Buffer tarafında varsayılan proxy_buffering on çoğu senaryoda doğrudur. Elle dokunmanız gereken üç nokta var:
proxy_buffer_size: Yanıtın başlık kısmı için kullanılır (varsayılan bir bellek sayfası, 4k veya 8k). Büyük çerez veya JWT taşıyan uygulamalarda loglardaupstream sent too big headergörülür ve istemci 502 alır; 16k–32k’ya çıkarmak çoğunlukla yeterlidir.proxy_buffersveproxy_busy_buffers_size: Gövde tamponları. Bellek yetmezse yanıt geçici dosyaya yazılır; logdakian upstream response is buffered to a temporary fileuyarısı bunu gösterir.- Server-Sent Events veya akış yapan uçlarda o location için
proxy_buffering off;tanımlayın ya da uygulamanınX-Accel-Buffering: nobaşlığı göndermesini sağlayın.
WebSocket ve upstream keepalive
WebSocket, HTTP/1.1 Upgrade mekanizmasıyla başlar. Upgrade ve Connection hop-by-hop başlıklardır; Nginx bunları arka uca kendiliğinden iletmez. Siz aktarmazsanız el sıkışma hiç tamamlanmaz. nginx.org’un WebSocket proxying belgesinde önerilen yapı:
# http bloğunda
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
# server bloğunda
location /ws/ {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
include /etc/nginx/snippets/proxy-headers.conf;
proxy_read_timeout 1h;
}
Bu location’da Connection "" yerine $connection_upgrade var; ikisini karıştırmak WebSocket’i sessizce bozar. Normal HTTP istekleri için ise upstream keepalive devreye girer: keepalive 32; her işçi sürecin arka uca açık tutacağı boşta bağlantı sayısının üst sınırıdır. Çalışması için proxy_http_version 1.1 ve boş Connection başlığı gerekir; Nginx varsayılan olarak arka uçla HTTP/1.0 konuşur ve her istekte yeni bağlantı açar. Yoğun trafikte keepalive olmadan sunucuda çok sayıda TIME_WAIT soketi birikir ve geçici port tükenmesi yaşanabilir. ss -s çıktısında timewait sayısı binlerle ölçülüyorsa ilk bakılacak yer burasıdır.
Docker ile container tabanlı yayında arka uç bir servis adıyla tanımlanıyorsa, Nginx bu adı yalnızca yapılandırma yüklenirken çözer; container yeniden oluşturulup IP’si değişirse Nginx eski adrese gitmeye devam eder. “Container yeniden başladı, site 502 veriyor” şikâyetinin klasik nedeni budur. Ya arka uç değiştiğinde Nginx’i yeniden yükleyin ya da resolver 127.0.0.11 valid=10s; ile değişken tabanlı proxy_pass kullanın. Container yığınının üretim ayarları Docker Compose üretim yayını rehberinde ayrıntılı anlatılıyor.
502 ve 504 hatalarının teşhisi
İki hata kodu farklı şeyler söyler: 502 Bad Gateway, Nginx’in arka uçtan geçerli bir yanıt alamadığını; 504 Gateway Timeout ise yanıtın zaman aşımı içinde gelmediğini gösterir. Tarayıcıdaki hata sayfası size hiçbir şey söylemez. Teşhis error.log ile başlar.
| error.log mesajı | Kod | Olası neden ve kontrol |
|---|---|---|
| connect() failed (111: Connection refused) | 502 | Uygulama çalışmıyor veya farklı port/adreste dinliyor; ss -ltnp ile doğrulayın |
| connect() to unix:… failed (13: Permission denied) | 502 | Soket dosyası izinleri veya SELinux; PHP-FPM havuzunda listen.owner/listen.group |
| connect() failed (13: Permission denied), TCP | 502 | RHEL ailesinde SELinux; setsebool -P httpd_can_network_connect 1 |
| upstream prematurely closed connection | 502 | Uygulama işlem ortasında çöktü veya bellek limitine takıldı; uygulama logları |
| upstream sent too big header | 502 | proxy_buffer_size veya fastcgi_buffer_size yetersiz |
| no live upstreams | 502 | Tüm sunucular max_fails nedeniyle geçici olarak devre dışı |
| upstream timed out (110: Connection timed out) while reading response header | 504 | Yavaş sorgu veya kilitlenme; proxy_read_timeout artırmak yalnızca belirtiyi gizler |
Konsolda izlenecek sıra:
nginx -tile yapılandırmanın geçerli olduğunu doğrulayın;nginx -Tbirleşik yapılandırmayı dökerek beklenen dosyanın gerçekten yüklendiğini gösterir.- Arka ucu Nginx’i atlayarak sunucunun kendisinden deneyin:
curl -sv -H "Host: app.example.com" http://127.0.0.1:3000/. Burada da hata varsa sorun proxy’de değildir. - Erişim loguna
$upstream_addr,$upstream_status,$upstream_response_timeve$request_timeekleyin. İki süre arasındaki fark, zamanın Nginx–istemci tarafında mı yoksa uygulamada mı harcandığını gösterir. - 499 kodlarına bakın: istemci yanıtı beklemeden bağlantıyı kapatmıştır; genellikle önünüzdeki CDN veya yük dengeleyicinin zaman aşımı Nginx’inkinden kısadır.
- Zaman aşımı zincirini hizalayın: CDN > Nginx > uygulama sunucusu > veritabanı sorgu sınırı şeklinde dıştan içe azalan değerler, hatanın kaynağa en yakın katmanda ve anlamlı bir mesajla oluşmasını sağlar.
proxy_next_upstream varsayılan olarak error timeout durumlarında isteği bir sonraki sunucuya dener; Nginx 1.9.13’ten beri POST gibi idempotent olmayan istekler, arka uca gönderildikten sonra yeniden denenmez. Bu davranışı non_idempotent ile genişletmek, ödeme veya sipariş oluşturan uçlarda çift kayıt riskini getirir. Varsayılanı bırakın.
PHP-FPM kullanan sitelerde aynı teşhis mantığı fastcgi_pass ve fastcgi_read_timeout gibi karşılıklar üzerinden yürür; havuz tükenmesi kaynaklı 502/504’ler için PHP-FPM ve MariaDB performans rehberi doğrudan uygulanabilir. Tüm direktiflerin resmi tanımları ngx_http_proxy_module belgesindedir.
Yayına almadan önce kontrol listesi
Host,X-Forwarded-For,X-Forwarded-Protoher location’da iletiliyor; uygulama proxy’ye güvenecek şekilde ayarlı.- Önde CDN veya yük dengeleyici varsa realip yalnızca onların aralıklarına güveniyor.
client_max_body_sizeuygulamanın yükleme sınırıyla uyumlu.- Zaman aşımları location bazında ve dış katmandan içe doğru azalan sırada.
- WebSocket yolları
Upgrade/Connectionbaşlıklarını aktarıyor ve okuma zaman aşımı uygulamanın ping aralığından uzun. - Upstream keepalive ile
proxy_http_version 1.1birlikte tanımlı. - Erişim logunda upstream süreleri ve durum kodları kayıtlı.
TLS protokol ve şifre ayarları ayrı bir konu; onlar için web sunucusunda TLS yapılandırması rehberine geçin. Çok sayıda uygulamayı aynı Nginx arkasında toplayan yapılarda asıl iş yapılandırma standardı ve izlemedir; bu kurguyu Nginx web sunucusu ve reverse proxy hizmetimiz kapsamında kuruyoruz.
Sık sorulan sorular
proxy_pass adresinin sonundaki eğik çizgi neyi değiştirir?
proxy_pass içinde URI parçası varsa (örneğin http://app/ ile biten adres), location ile eşleşen önek bu URI ile değiştirilir; /api/users isteği arka uca /users olarak gider. URI yoksa (http://app) yol olduğu gibi iletilir. Taşıma sonrasında çıkan beklenmedik 404’lerin sık nedeni bu farktır.
WebSocket bağlantılarım tam 60 saniyede kopuyor, neden?
proxy_read_timeout varsayılan olarak 60 saniyedir ve iki okuma arasındaki boşluğa uygulanır. Uygulama bu sürede hiç veri göndermezse Nginx bağlantıyı kapatır. İlgili location için zaman aşımını uzatın ya da uygulamada periyodik ping/pong kullanın; ikincisi aradaki diğer proxy ve yük dengeleyicilerde de işe yarar.
Cloudflare arkasında loglarda neden hep Cloudflare IP’leri görünüyor?
Nginx bağlantıyı Cloudflare’in uç sunucusundan aldığı için $remote_addr o adresi gösterir. realip modülüyle set_real_ip_from satırlarına Cloudflare’in yayımladığı IP aralıklarını ekleyip real_ip_header CF-Connecting-IP tanımladığınızda loglar, rate limit ve erişim kuralları gerçek istemci IP’siyle çalışır.
PHP-FPM için de proxy_pass mi kullanılmalı?
Hayır. PHP-FPM FastCGI konuşur; Nginx’te fastcgi_pass ile bağlanılır ve zaman aşımı, buffer gibi ayarların fastcgi_ önekli karşılıkları kullanılır. Yukarıdaki zaman aşımı, buffer ve teşhis mantığı aynen geçerlidir; istemci bilgisi ise proxy_set_header yerine fastcgi_param ile aktarılır.
- Teknoloji
- Nginx, ngx_http_proxy_module, ngx_http_realip_module, WebSocket, PHP-FPM, Docker
- Konu
- Uygulamaları Nginx arkasında doğru yayınlamak
- Seviye
- Orta–ileri
- Ön koşullar
- Linux sunucuda Nginx kurulu ve temel server bloğu bilgisi
- Arka uç uygulamanın dinlediği adres ve port
- Yapılandırmayı test edip yeniden yükleme yetkisi
Web Sunucuları ve DevOps