Bir web sunucusu yönetirken en çok ihmal edilen konulardan biri hata sayfaları ve fallback mekanizmalarıdır. Kullanıcı bir 404 alıyor, tarayıcısına Nginx’in varsayılan çirkin hata sayfası geliyor, sen de bunun farkında bile değilsin. Ya da daha kötüsü, bir upstream servisin çökmesi sırasında kullanıcılara düzgün bir mesaj göstermek yerine bağlantı hatası aldırıyorsun. Bu yazıda Nginx’te hata sayfalarını nasıl özelleştireceğini, fallback mekanizmalarını nasıl kuracağını ve production ortamında işine yarayacak gerçek dünya senaryolarını ele alacağız.
Nginx Hata Sayfaları Nasıl Çalışır?
Nginx, bir istek işlenirken hata oluştuğunda error_page direktifini kullanarak belirli HTTP durum kodlarını yakalar ve kullanıcıyı yönlendirir. Bu direktif son derece esnek yapıdadır; hem dahili yönlendirme hem de harici URI’lara yönlendirme yapabilirsin.
Temel sözdizimi şöyle:
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
Burada dikkat edilmesi gereken nokta, error_page direktifinin hangi context içinde tanımlandığıdır. http, server veya location bloğunda tanımlayabilirsin. Daha spesifik olan her zaman kazanır, yani location bloğundaki tanım server bloğundakini override eder.
error_page Direktifinin Parametreleri
Durum kodu listesi: Birden fazla HTTP kodunu aynı anda yakalayabilirsin, aralarına boşluk koymen yeterli.
= (eşittir işareti): Hata sayfasının döndüreceği durum kodunu değiştirebilirsin. Örneğin error_page 404 =200 /maintenance.html; dersin, 404 yerine 200 döner. Bu bazen CDN veya monitoring araçlarıyla çalışırken işine yarar.
@named_location: Named location kullanarak çok daha karmaşık mantıklar kurabilirsin.
Statik HTML Hata Sayfaları Yapılandırması
En basit senaryodan başlayalım. Elinde statik HTML dosyaları var, bunları belirli hata kodlarına eşlemek istiyorsun.
server {
listen 80;
server_name example.com;
root /var/www/html;
# Birden fazla hata kodunu tek sayfaya yönlendir
error_page 404 /errors/404.html;
error_page 500 502 503 504 /errors/50x.html;
error_page 403 /errors/403.html;
error_page 429 /errors/429.html;
# Hata sayfaları için location bloğu
location /errors/ {
internal;
root /var/www/;
}
}
Buradaki internal direktifi kritik bir güvenlik önlemi. Bu direktif olmadan kullanıcılar doğrudan /errors/404.html adresine gidebilir. internal ile bu dosyalar sadece dahili Nginx yönlendirmeleri tarafından erişilebilir hale gelir.
Hata sayfalarının fiziksel konumu için de dikkatli olman gerekiyor. root /var/www/ dediğinde ve location /errors/ içindeyken, Nginx dosyayı /var/www/errors/404.html adresinde arar. Bu yolu karıştırmak çok yaygın bir hata.
Dinamik Hata Sayfaları ve PHP/FastCGI Entegrasyonu
Production ortamında statik hata sayfaları çoğu zaman yeterli olmaz. Sitenin header ve footer’ını dahil etmek isteyebilirsin, hata loglamak isteyebilirsin ya da dil desteği eklemek isteyebilirsin. Bunun için PHP veya başka bir backend ile entegrasyon gerekir.
server {
listen 80;
server_name example.com;
root /var/www/html;
error_page 404 @not_found;
error_page 500 502 503 504 @server_error;
location @not_found {
internal;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/html/errors/404.php;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param ORIGINAL_URI $uri;
}
location @server_error {
internal;
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME /var/www/html/errors/50x.php;
fastcgi_param HTTP_STATUS $status;
}
}
Bu yapılandırmada named location kullandık. Named location’lar @ işaretiyle tanımlanır ve sadece dahili yönlendirmeler tarafından çağrılabilir, doğrudan URL olarak erişilemezler.
PHP tarafında ise $_SERVER['HTTP_STATUS'] veya $_SERVER['ORIGINAL_URI'] gibi değişkenlere erişerek hangi sayfadan geldiğini, hangi hata kodunun tetiklendiğini takip edebilirsin.
Upstream Fallback Mekanizmaları
Gerçek production senaryolarında en değerli Nginx özelliklerinden biri upstream fallback yönetimidir. Diyelim ki bir uygulama sunucusuna proxy yapıyorsun ve bu sunucu zaman zaman yanıt veremiyor. Kullanıcıya ham bir bağlantı hatası göstermek yerine bakım sayfası veya önbelleğe alınmış içerik sunabilirsin.
upstream backend_pool {
server app1.internal:8080;
server app2.internal:8080;
server app3.internal:8080 backup;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend_pool;
proxy_connect_timeout 3s;
proxy_read_timeout 10s;
proxy_intercept_errors on;
error_page 502 503 504 @maintenance;
}
location @maintenance {
internal;
root /var/www/;
try_files /maintenance.html =503;
}
}
Burada proxy_intercept_errors on direktifi çok önemli. Bu direktif olmadan upstream’den gelen hata kodları doğrudan kullanıcıya iletilir ve Nginx’in error_page tanımları devreye girmez. Bu direktifi aktif ettiğinde, upstream’den 400 veya üzeri kod geldiğinde Nginx kendi error_page kurallarını uygular.
proxy_next_upstream ile Otomatik Failover
Upstream sunuculardan biri hata döndürdüğünde Nginx’i otomatik olarak bir sonraki sunucuya yönlendirecek şekilde yapılandırabilirsin:
upstream backend_pool {
server app1.internal:8080;
server app2.internal:8080;
server app3.internal:8080;
}
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend_pool;
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
proxy_intercept_errors on;
error_page 502 503 504 /errors/50x.html;
}
}
proxy_next_upstream direktifinin parametreleri:
- error: Sunucuyla bağlantı kurulamadığında veya istek gönderilemediğinde bir sonrakine geç
- timeout: Bağlantı veya okuma zaman aşımında bir sonrakine geç
- http_502: Upstream 502 döndürdüğünde bir sonrakine geç
- http_503: Upstream 503 döndürdüğünde bir sonrakine geç
- http_504: Upstream 504 döndürdüğünde bir sonrakine geç
- non_idempotent: POST gibi idempotent olmayan istekleri de yönlendir (dikkatli kullan, çift gönderim riski var)
Önbellekten Fallback (Stale Cache)
Bu benim production’da en çok kullandığım mekanizmalardan biri. Nginx proxy cache aktifse ve backend çökerse, Nginx eski (stale) önbellek verilerini kullanıcıya sunmaya devam edebilir. Kullanıcı biraz eski içerik görür ama site ayakta kalır.
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app_cache:10m max_size=1g
inactive=60m use_temp_path=off;
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://backend_pool;
proxy_cache app_cache;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 302 10m;
proxy_cache_valid 404 1m;
# Backend hata verirse önbellekten sun
proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
proxy_cache_lock on;
# Önbellekten sunulduğunu header'a ekle
add_header X-Cache-Status $upstream_cache_status;
proxy_intercept_errors on;
error_page 502 503 504 /errors/50x.html;
}
}
proxy_cache_use_stale direktifi ile belirttiğin durumlarda Nginx süresi geçmiş önbellek girdisini kullanacak. Bu özellikle okuma ağırlıklı siteler için hayat kurtarıcı. proxy_cache_lock on ise aynı anda birden fazla istek geldiğinde sadece birinin upstream’e gitmesini sağlar, thundering herd problemini önler.
try_files ile Gelişmiş Fallback Mantığı
try_files direktifi hem dosya sisteminde arama yapmak hem de fallback mekanizması oluşturmak için kullanılır. Birbiri ardına deneyeceği seçenekleri belirtirssin, hiçbiri bulunamazsa son parametre devreye girer.
server {
listen 80;
server_name example.com;
root /var/www/html;
location / {
# Önce tam dosyayı dene, sonra dizini, sonra index.php'ye geç
try_files $uri $uri/ /index.php?$query_string;
}
location /static/ {
# Dosya yoksa CDN'den çek (302 yönlendirme)
try_files $uri @cdn_fallback;
}
location @cdn_fallback {
return 302 https://cdn.example.com$request_uri;
}
location /api/ {
# Önce önbelleğe alınmış dosyayı dene, yoksa backend'e git
try_files /cache$uri @api_backend;
}
location @api_backend {
proxy_pass http://api_servers;
}
}
try_files ile yapabileceğin birkaç pratik senaryo:
- SPA (Single Page Application):
try_files $uri $uri/ /index.html;ile tüm rotaları React/Vue uygulamasına yönlendir - Statik önbellekleme: Oluşturulmuş statik dosya varsa sun, yoksa PHP’ye gönder
- Resim fallback’i: Boyutlandırılmış resim yoksa orijinalini sun veya placeholder göster
Resim Boyutlandırma Fallback Senaryosu
E-ticaret sitelerinde sık karşılaştığım gerçek dünya senaryosu: Farklı boyutlarda ürün görselleri isteniyor ama bazı boyutlar henüz oluşturulmamış. Bu durumda kullanıcıya kırık resim göstermek yerine akıllı bir fallback kurabiliriz.
server {
listen 80;
server_name example.com;
root /var/www/html;
# /images/products/thumb/product-123.jpg gibi istekler
location ~* ^/images/products/(thumb|medium|large)/(.+.(jpg|jpeg|png|webp))$ {
set $size $1;
set $filename $2;
try_files $uri @generate_thumbnail;
}
location @generate_thumbnail {
internal;
proxy_pass http://image_processor;
proxy_set_header X-Image-Size $size;
proxy_set_header X-Image-File $filename;
# Image processor da başarısız olursa
proxy_intercept_errors on;
error_page 404 500 /images/placeholder.jpg;
}
# Placeholder için ayrı location
location = /images/placeholder.jpg {
internal;
alias /var/www/html/assets/placeholder.jpg;
expires 1h;
}
}
Maintenance Mode Yapılandırması
Deployement sırasında veya planlı bakım için maintenance mode açmak istediğinde bunu Nginx seviyesinde çok temiz şekilde yapabilirsin:
server {
listen 80;
server_name example.com;
root /var/www/html;
# Maintenance dosyası varsa bakım moduna gir
set $maintenance 0;
if (-f /var/www/html/maintenance.flag) {
set $maintenance 1;
}
# Kendi IP'ni bakım modundan muaf tut
if ($remote_addr = "85.12.34.56") {
set $maintenance 0;
}
if ($maintenance = 1) {
return 503;
}
error_page 503 @maintenance_page;
location @maintenance_page {
internal;
root /var/www/;
try_files /maintenance.html =503;
add_header Retry-After 3600;
add_header Cache-Control "no-cache, no-store";
}
location / {
proxy_pass http://backend;
}
}
Maintenance modunu açmak için sadece touch /var/www/html/maintenance.flag, kapatmak için rm /var/www/html/maintenance.flag komutunu çalıştırman yeterli. Nginx reload gerekmez.
Rate Limiting ve 429 Hata Sayfaları
DDoS veya brute force durumlarında rate limiting devreye girdiğinde 429 Too Many Requests döner. Bu durumu da düzgün handle etmek gerekir:
http {
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
limit_req_zone $binary_remote_addr zone=login_limit:10m rate=5r/m;
server {
listen 80;
server_name example.com;
# 429 için özel hata sayfası
error_page 429 /errors/rate_limit.html;
location /api/ {
limit_req zone=api_limit burst=20 nodelay;
limit_req_status 429;
proxy_pass http://api_backend;
}
location /auth/login {
limit_req zone=login_limit burst=3;
limit_req_status 429;
proxy_pass http://auth_backend;
}
location /errors/rate_limit.html {
internal;
root /var/www/;
add_header Retry-After 60;
add_header Content-Type "text/html; charset=utf-8";
}
}
}
Hata Loglarını Zenginleştirme
Hata sayfası göstermek yetmez, aynı zamanda bu hataları düzgün loglamak da önemli. Nginx’te log formatını özelleştirerek hata ayıklamayı kolaylaştırabilirsin:
http {
log_format detailed_errors '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'$request_time $upstream_response_time '
'$upstream_addr $upstream_status';
server {
listen 80;
server_name example.com;
# Normal loglar
access_log /var/log/nginx/access.log;
# Sadece hataları ayrı dosyaya yaz
access_log /var/log/nginx/errors.log detailed_errors if=$loggable;
error_log /var/log/nginx/error.log warn;
# 2xx ve 3xx'i loglama, sadece hataları logla
map $status $loggable {
~^[23] 0;
default 1;
}
}
}
Yapılandırmayı Test Etme ve Doğrulama
Hata sayfası yapılandırmalarını production’a almadan önce mutlaka test et. Nginx’in kendi test komutu var:
# Syntax kontrolü
nginx -t
# Daha ayrıntılı çıktı için
nginx -T | grep -A 5 "error_page"
# Canlı sistemde curl ile hata sayfasını test et
curl -o /dev/null -s -w "%{http_code}n" http://example.com/olmayan-sayfa
curl -v http://example.com/olmayan-sayfa
# Upstream'i devre dışı bırakarak fallback test et
# (test ortamında)
curl -H "Host: example.com" http://localhost/api/test
# 503 senaryosunu test etmek için maintenance flag oluştur
touch /var/www/html/maintenance.flag
curl -I http://example.com
rm /var/www/html/maintenance.flag
Bir de production’da sık yaptığım kontrol: Hata sayfalarının kendisi de 404 dönmesin diye ayrıca test etmek gerekiyor. Hata sayfasının hata vermesi en kötü senaryodur ve Nginx bu durumda varsayılan sayfasına döner.
# Hata sayfalarının erişilebilir olduğunu kontrol et
for page in 404.html 50x.html 403.html maintenance.html; do
status=$(curl -o /dev/null -s -w "%{http_code}"
http://localhost/errors/$page)
echo "$page: $status"
done
Sık Yapılan Hatalar
Production’da gördüğüm en yaygın hatalar şunlar:
- internal direktifini unutmak: Hata sayfası dosyalarını
internalolmayan bir location altında tanımlamak, kullanıcıların doğrudan bu URL’lere erişmesine izin verir. SEO açısından da problem çıkarır.
- proxy_intercept_errors’u açmayı unutmak: Upstream’den gelen 502/503 hatalarını kendi error_page’lerinle handle etmek istiyorsan bu direktif şart. Yoksa upstream’in döndürdüğü ham hata sayfası kullanıcıya gider.
- Döngüsel yönlendirme: Hata sayfası kendisi 404 döndürürse ve bu da aynı error_page kuralına takılırsa sonsuz döngü oluşur. Nginx bunu bir noktada keser ama loglar dolup taşar.
- Root path karışıklığı:
location /errors/bloğundarootdirektifini yanlış ayarlamak./errors/404.htmliçinroot /var/www/dersen Nginx/var/www/errors/404.html‘e bakar.aliaskullanıyorsan path sonundaki slash’a dikkat et.
- Cache header’larını unutmak: Hata sayfaları CDN veya tarayıcı tarafından agresif biçimde cache’lenebilir. Hata sayfalarına
Cache-Control: no-cacheeklemezsen, bir süre geçtikten sonra düzelmiş hatayı görmek isteyen kullanıcılar hata sayfasının önbelleklenmiş haline takılır.
Sonuç
Nginx’te hata sayfaları ve fallback mekanizmaları, küçük bir detay gibi görünse de production kalitesini doğrudan etkileyen kritik unsurlardır. Statik HTML sayfalarından başlayıp proxy cache fallback’lerine, maintenance mode’dan rate limiting hata yönetimine kadar anlattığımız her senaryo gerçek dünya problemlerine çözüm üretiyor.
Benim önerim, her yeni Nginx yapılandırmasında şu kontrol listesini geçmek: error_page direktifleri tanımlı mı, internal kullanıldı mı, proxy_intercept_errors ayarlandı mı, hata sayfaları kendi kendine erişilebilir mi, önbellekten stale içerik sunulabilir mi? Bu beş soruyu geçen bir yapılandırma çoğu durumda sağlam bir fallback stratejisine sahip demektir.
Nginx’in bu konudaki esnekliği gerçekten etkileyici. Sadece statik dosya sunmaktan çok daha fazlası, neredeyse uygulama mantığı yazabilirsin. Ama bu güç beraberinde karmaşıklık getiriyor. Yapılandırmaları basit tut, her değişikliği test et ve her zaman bir geri dönüş planın olsun.