Web sunucularında dosya sunumu kulağa basit gelebilir: istemci bir dosya ister, sunucu dosyayı bulur ve gönderir. Ama gerçek dünya senaryolarına girince işler hızla karmaşıklaşır. PHP uygulamaları, statik dosya fallback’leri, özel 404 sayfaları, yeniden yazma kuralları… Nginx’in try_files direktifi tam bu noktada devreye girer ve bunların hepsini tek, zarif bir yapıyla çözmenizi sağlar. Bu yazıda try_files‘ı derinlemesine inceleyeceğiz, sadece teorik değil gerçekten production ortamında karşılaşacağınız senaryolarla.
try_files Direktifi Nedir ve Neden Önemlidir
try_files, Nginx’e bir istek geldiğinde dosya sisteminde hangi sırayla arama yapması gerektiğini ve hiçbirini bulamazsa ne yapması gerektiğini söyleyen bir direktiftir. location bloğu içinde kullanılır ve Nginx’in en güçlü özelliklerinden biri olmasına rağmen yanlış anlaşılan direktiflerin başında gelir.
Temel sözdizimi şöyle:
try_files dosya1 dosya2 ... son_secenek;
Nginx sırasıyla her dosyayı kontrol eder. Bulduğu ilk dosyayı ya da dizini istemciye sunar. Hiçbirini bulamazsa en son belirtilen seçeneğe geçer; bu bir URI yönlendirmesi, hata kodu veya named location olabilir.
try_files‘ı Apache’nin mod_rewrite‘ına göre çok daha verimli kılan şey şudur: Apache kural bazlı bir yaklaşım kullanırken Nginx dosya sistemine doğrudan bakarak karar verir. Bu, özellikle yoğun trafikli sunucularda belirgin bir performans farkı yaratır.
Temel Kullanım: Statik Dosya Sunumu
En basit kullanım senaryosundan başlayalım. Bir kullanıcı /images/logo.png istek yaptığında Nginx’in önce gerçek dosyayı, bulamazsa bir placeholder göndermesini istiyoruz.
server {
listen 80;
server_name example.com;
root /var/www/html;
location /images/ {
try_files $uri /images/placeholder.png;
}
}
Burada $uri değişkeni istek yapılan URL’yi temsil eder. Nginx önce /var/www/html/images/logo.png dosyasının gerçekten var olup olmadığına bakar. Varsa sunar, yoksa /images/placeholder.png‘yi sunar.
Bu yaklaşım CDN geçiş süreçlerinde çok işe yarar. Eski dosyalar yavaş yavaş silinirken kullanıcılara anlamlı bir fallback gösterebilirsiniz.
PHP Uygulamaları için Klasik try_files Kullanımı
WordPress, Laravel, Symfony gibi PHP uygulamalarını Nginx üzerinde çalıştırıyorsanız try_files‘ı muhtemelen zaten kullanıyorsunuzdur. Ama altında ne döndüğünü anlamak önemli.
server {
listen 80;
server_name example.com;
root /var/www/html;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ .php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
Bu konfigürasyonda ne oluyor adım adım açıklayalım:
$uri: İstenen dosya tam olarak var mı?/about.htmlgibi statik bir dosyaysa direkt sunar.$uri/: İstenen şey bir dizin mi?/blog/gibi bir istek varsa dizini kontrol eder,indexdirektifine göre dosyayı arar./index.php?$query_string: Yukarıdakilerin ikisi de yoksa tüm isteğiindex.php‘ye yönlendirir. Laravel’in router’ı, WordPress’in yeniden yazma kuralları burada devreye girer.
$query_string eklemezseniz ?page=2 gibi query parametreleri kaybolur, bunu sık görülen bir hata olarak not edin.
$uri/ ile Dizin İndeksleme
$uri/ kullanımı inceliği kaybolmaması gereken bir konu. Nginx bir dizin isteği aldığında önce index direktifinde belirtilen dosyaları arar.
server {
listen 80;
server_name example.com;
root /var/www/html;
index index.html index.htm index.php;
location / {
try_files $uri $uri/ =404;
}
location /docs/ {
try_files $uri $uri/ /docs/index.html;
}
}
/docs/ isteği geldiğinde Nginx şunu yapar:
/var/www/html/docsadında bir dosya var mı? Hayır./var/www/html/docs/dizini var mı ve içindeindex.htmlvar mı? Evet, sun.- Bunların ikisi de yoksa
/docs/index.html‘yi sunar.
Bu yaklaşım single-page application (SPA) deployment’larında mükemmel çalışır. React, Vue veya Angular uygulamalarında her URL index.html‘e düşmeli ki frontend router devralabilsin.
Named Location ile Gelişmiş Yönlendirme
try_files‘ın az bilinen ama son derece güçlü bir özelliği: named location’lara yönlendirme. @ ile başlayan location blokları bu iş için vardır.
server {
listen 80;
server_name example.com;
root /var/www/html;
location / {
try_files $uri $uri/ @php_backend;
}
location @php_backend {
rewrite ^/(.*)$ /index.php?route=$1 last;
}
location ~ .php$ {
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
}
Named location kullanmanın avantajı şudur: fallback davranışını tamamen özelleştirebilirsiniz. Basit bir URI yönlendirmesi değil, karmaşık rewrite kuralları, özel header’lar veya farklı upstream’ler kullanabilirsiniz.
Bir e-ticaret senaryosu düşünün: ürün görselleri önce local disk’ten sunulur, bulamazsa bir image proxy servisine yönlendirilir.
location /products/images/ {
try_files $uri @image_proxy;
}
location @image_proxy {
proxy_pass http://image-service:8080;
proxy_set_header X-Original-URI $request_uri;
}
Hata Kodlarıyla try_files Kullanımı
Son parametre olarak =404, =403 gibi HTTP durum kodları verebilirsiniz. Bu özellikle güvenlik açısından önemli.
server {
listen 80;
server_name example.com;
root /var/www/html;
# Sadece var olan dosyaları sun, yoksa 404 ver
location /uploads/ {
try_files $uri =404;
}
# API endpoint'leri için 400 bad request
location /api/files/ {
try_files $uri =400;
}
# Özel hata sayfaları
error_page 404 /errors/404.html;
error_page 403 /errors/403.html;
location /errors/ {
internal;
root /var/www/html;
}
}
internal direktifine dikkat: Bu, /errors/ location’ının sadece Nginx iç yönlendirmeleri için kullanılabileceği anlamına gelir. Dışarıdan doğrudan /errors/404.html isteği 404 döner, bu güvenlik açısından iyi bir pratik.
Gerçek Dünya Senaryosu: Çok Katmanlı Medya Sunucusu
Şimdi gerçekten karmaşık bir senaryo çözelim. Diyelim ki bir içerik platformu yönetiyorsunuz. Kullanıcılar resim yüklüyor, bunlar S3’e ya da NFS’e gidiyor, ama performance için lokal cache’de tutuluyor. Fallback sıralaması şöyle olmalı:
- Lokal cache’de var mı? Sun.
- NFS mount’ta var mı? Sun ve cache’e kopyala.
- Hiçbirinde yoksa 404 ver.
server {
listen 80;
server_name media.example.com;
# Lokal cache dizini
root /var/cache/media;
location ~* ^/media/(.+.(jpg|jpeg|png|gif|webp|mp4|pdf))$ {
try_files $uri @nfs_fallback;
# Cache control header'ları
expires 30d;
add_header Cache-Control "public, immutable";
add_header X-Served-From "local-cache";
}
location @nfs_fallback {
# NFS mount noktasından sun
root /mnt/nfs/media;
try_files $uri @not_found;
add_header X-Served-From "nfs-storage";
}
location @not_found {
return 404;
}
}
Burada nested named location kullanımı dikkat çekici. @nfs_fallback kendi içinde try_files kullanarak @not_found‘a düşüyor. Bu zincir yapısı son derece esnek bir fallback mekanizması sağlıyor.
Laravel ve Modern PHP Framework Konfigürasyonu
Laravel’in önerdiği standart Nginx konfigürasyonu yeterli, ama production için daha sağlamlaştırılmış bir versiyon yazalım.
server {
listen 443 ssl http2;
server_name app.example.com;
root /var/www/laravel/public;
index index.php;
# Güvenlik: gizli dosyalara erişimi engelle
location ~ /. {
deny all;
return 404;
}
# Güvenlik: hassas dosyalara erişimi engelle
location ~* .(env|log|htaccess|htpasswd|ini|conf)$ {
deny all;
return 404;
}
# Statik dosyalar için uzun cache, try_files ile güvenli sunum
location ~* .(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf|eot)$ {
try_files $uri =404;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
# Ana uygulama
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ .php$ {
# Path traversal saldırılarına karşı
try_files $uri =404;
fastcgi_split_path_info ^(.+.php)(/.+)$;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_read_timeout 300;
}
}
PHP location bloğundaki try_files $uri =404 satırına özellikle dikkat edin. Bu path traversal saldırılarını engeller. Bu satır olmadan upload.jpg/evil.php gibi bir istek PHP olarak işlenebilir. Bu kritik bir güvenlik önlemidir.
Statik Site ile API Proxy Birlikte
Günümüzde yaygın bir senaryo: frontend React/Vue/Angular uygulaması statik dosyalar olarak sunuluyor, /api/ altındaki istekler ise backend servise proxy’leniyor.
server {
listen 80;
server_name app.example.com;
root /var/www/frontend/dist;
index index.html;
# API isteklerini backend'e yönlendir
location /api/ {
proxy_pass http://backend:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_cache_bypass $http_upgrade;
}
# WebSocket desteği
location /ws/ {
proxy_pass http://backend:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "Upgrade";
}
# Statik asset'ler, build hash'li dosyalar
location ~* .(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
try_files $uri =404;
expires 1y;
add_header Cache-Control "public, immutable";
}
# SPA fallback: her route index.html'e düşsün
location / {
try_files $uri $uri/ /index.html;
}
}
Bu konfigürasyonda /api/profil isteği backend’e gider, /dashboard gibi bir frontend route isteği ise index.html‘e düşer ve React Router devralır. Statik dosyalar =404 ile sıkı bir şekilde kontrol altına alınmıştır.
Yaygın Hatalar ve Çözümleri
Sonsuz Döngü Problemi
# YANLIS - Sonsuz döngüye girebilir
location / {
try_files $uri $uri/ /index.php;
}
location = /index.php {
try_files $uri /index.php; # Bu sorunlu!
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
PHP location bloğu içinde tekrar try_files $uri /index.php yazarsanız teorik olarak sonsuz döngü yaratabilirsiniz. Doğrusu şöyle olmalı:
location = /index.php {
try_files $uri =404; # Sadece dosyanın varlığını kontrol et
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
Root Direktifi Konumu
try_files her zaman aktif root direktifini kullanır. Location bloğu içinde root tanımladıysanız dikkatli olun.
server {
root /var/www/html;
location /static/ {
root /var/www/static-files; # Bu location için root degisti
try_files $uri =404;
# /static/logo.png istegi /var/www/static-files/static/logo.png'yi arar
# /var/www/static-files/logo.png DEGIL!
}
}
root yerine alias kullanmak bu durumda daha doğru olur:
location /static/ {
alias /var/www/static-files/; # Trailing slash onemli!
try_files $uri =404;
# /static/logo.png istegi /var/www/static-files/logo.png'yi arar
}
Query String Kaybı
# YANLIS - Query string kaybolur
location / {
try_files $uri $uri/ /index.php;
}
# DOGRU - Query string korunur
location / {
try_files $uri $uri/ /index.php?$query_string;
}
Özellikle pagination, filtreleme gibi query parametreler kullanan uygulamalarda bu fark kritik.
Performans İpuçları
try_files dosya sistemi I/O işlemi yapar. Her parametre için disk’e gidiş söz konusu. Yoğun trafikli sunucularda bu önemli bir faktör.
open_file_cache ile bu maliyeti azaltabilirsiniz:
http {
open_file_cache max=10000 inactive=30s;
open_file_cache_valid 60s;
open_file_cache_min_uses 2;
open_file_cache_errors on;
server {
root /var/www/html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
}
}
open_file_cache dosya tanımlayıcılarını ve var olup olmadığı bilgisini bellekte tutar. try_files her seferinde disk’e gitmek yerine cache’e bakar. 10.000 dosyaya kadar bu önbelleği tutması ve her dosyayı 60 saniye geçerli sayması production için makul değerler.
Ayrıca try_files parametrelerini mantıklı sırayla dizin. En çok eşleşme beklediğiniz dosyayı en başa koyun. Çoğu isteğin PHP’ye düşeceği bir uygulamada $uri ve $uri/ kontrollerini atlayıp direkt /index.php‘ye düşebilirsiniz, ama bu statik dosyaların da PHP üzerinden işleneceği anlamına gelir. Genellikle statik kontrol yapmak daha iyidir.
Konfigürasyonu Test Etme
Değişiklikleri uygulamadan önce her zaman test edin:
# Konfigürasyon sözdizimi kontrolü
nginx -t
# Detaylı çıktı ile test
nginx -T | grep -A 5 "try_files"
# Reload (mevcut bağlantıları kesmez)
nginx -s reload
# try_files davranışını debug etmek için access log'a ek bilgi ekle
log_format debug_format '$remote_addr - $request - $uri - Status: $status';
access_log /var/log/nginx/debug.log debug_format;
Bir isteğin try_files içinde hangi adımda çözüldüğünü anlamak için $uri‘nin her aşamada nasıl değiştiğini loglamak oldukça yardımcı olur.
# Spesifik bir URL'yi curl ile test et
curl -v http://localhost/test-path 2>&1 | grep -E "(< HTTP|Location)"
# Nginx error log'u canlı izle
tail -f /var/log/nginx/error.log
Sonuç
try_files direktifi basit görünse de Nginx’in en çok iş yapan bileşenlerinden biri. Statik dosya sunumundan PHP uygulamalarına, SPA deployment’larından çok katmanlı medya sistemlerine kadar her yerde karşınıza çıkar.
En önemli çıkarımları özetlemek gerekirse:
- Sıralama kritik: Parametreleri disk I/O’yu minimize edecek şekilde sıralayın.
=404PHP bloğunda zorunlu: Path traversal saldırılarından korunmak için PHP location’larında mutlaka kullanın.$query_stringunutmayın: PHP’ye fallback yaparken query parametrelerini taşıyın.- Named location’lar güçlü: Karmaşık fallback mantığı için
@prefix’li location’ları kullanın. open_file_cacheile performansı artırın: Yoğun trafikli sunucularda ihmal etmeyin.- Her zaman
nginx -t: Reload öncesi test alışkanlık haline gelmeli.
Production’da Nginx konfigürasyonu yazmak bir anlamda balancing act: güvenlik, performans ve esnekliği dengelemek gerekiyor. try_files‘ı doğru anlamak bu dengeyi kurmanın temel taşlarından biri.