Nginx ile Büyük Dosya Yükleme Sorunları: client_max_body_size Ayarı
Bir kullanıcı sana gelir, “Nginx’e dosya yükleyemiyorum, sayfa hata veriyor” der. Sen de logları açarsın, 413 Request Entity Too Large hatası sırıtır sana. İşte bu an, client_max_body_size direktifini tanımanın tam zamanıdır. Görünüşte basit bir konfigürasyon satırı, ama arkasında onlarca farklı senaryo, katmanlı yapılar ve birden fazla servisi kapsayan sorunlar saklıdır.
Bu yazıda Nginx’in dosya yükleme sorunlarını her açıdan ele alacağız. Sadece “şu değeri artır” demekle kalmayıp, neden bu hatanın oluştuğunu, loglardan nasıl tespit edileceğini, farklı konfigürasyon katmanlarını ve production ortamında nelere dikkat etmen gerektiğini konuşacağız.
413 Hatası Nedir ve Neden Oluşur?
HTTP 413 Request Entity Too Large hatası, istemcinin sunucuya gönderdiği isteğin gövdesi (request body), sunucunun kabul etmek istediği maksimum boyutu aştığında döner. Nginx bu limiti client_max_body_size direktifiyle kontrol eder ve varsayılan değer 1MB‘dır.
Yani bir kullanıcı 5MB’lık bir PDF yüklemeye çalıştığında, Nginx isteği işleme almadan reddeder ve 413 döner. Dosya sunucuya hiç ulaşmaz, PHP/Node/Python uygulamanız bunu görmez bile.
Peki bu hata sadece dosya yüklemelerinde mi çıkar? Hayır. Büyük JSON body gönderen API istekleri, multipart form verileri, hatta bazı durumlarda büyük cookie başlıkları bile benzer sorunlara yol açabilir. Ama en yaygın ve can sıkıcı senaryo tabii ki dosya yüklemeleridir.
Loglardan Sorunu Tespit Etmek
Herhangi bir ayar yapmadan önce önce logları doğru okumayı bilmek gerekir. Nginx’in access log’u ve error log’u bu konuda birlikte çalışır.
Access log’da 413 görmek:
tail -f /var/log/nginx/access.log | grep 413
Tipik bir 413 access log satırı şöyle görünür:
192.168.1.45 - - [15/Jan/2024:14:23:11 +0300] "POST /upload HTTP/1.1" 413 548 "https://example.com/upload" "Mozilla/5.0..."
Error log’da detayları görmek:
tail -f /var/log/nginx/error.log
Error log’da şuna benzer bir satır görürsün:
2024/01/15 14:23:11 [error] 12345#12345: *89 client intended to send too large body: 5242880 bytes, client: 192.168.1.45, server: example.com, request: "POST /upload HTTP/1.1", host: "example.com"
Bu satırda 5242880 bytes kısmı bize istemcinin göndermek istediği dosyanın boyutunu söylüyor. Bu değeri MB’a çevirmek için 1048576’ya böl: 5242880 / 1048576 = 5MB. Yani kullanıcı 5MB’lık bir dosya yüklemeye çalışmış.
Belirli bir zaman aralığında 413 hatalarını saymak:
awk '$9 == 413 {print $1, $4}' /var/log/nginx/access.log |
sed 's/[//' |
awk '{print $2}' |
cut -d: -f1-2 |
sort | uniq -c | sort -rn
Bu komut sana hangi saatlerde en çok 413 hatası alındığını gösterir. Eğer belirli saatlerde yoğunlaşıyorsa, bu bir toplu yükleme işlemi veya otomatize bir sürecin işaretidir.
client_max_body_size Direktifinin Çalışma Mantığı
Bu direktif Nginx’te üç farklı kontext’te kullanılabilir:
- http: Tüm virtual host’ları etkiler, global ayardır
- server: Sadece o server bloğunu etkiler
- location: Sadece o URL path’ini etkiler
Öncelik sırası şöyledir: location > server > http. Yani bir location bloğunda tanımladığın değer, server veya http bloğundaki değeri ezer.
Temel kullanım:
http {
# Global olarak 0 = limitsiz (dikkatli kullan!)
client_max_body_size 0;
server {
listen 80;
server_name example.com;
# Bu server için 10MB
client_max_body_size 10m;
location /upload {
# Sadece upload endpoint'i için 100MB
client_max_body_size 100m;
}
location /api {
# API için 5MB yeterli
client_max_body_size 5m;
}
}
}
Değer birimlerini de doğru kullanmak önemli:
- k veya K: Kilobyte (1024 byte)
- m veya M: Megabyte (1024 kilobyte)
- g veya G: Gigabyte (1024 megabyte)
- 0: Limit yok (production’da dikkatli kullan)
Gerçek Dünya Senaryosu 1: WordPress Medya Yüklemeleri
Bir WordPress sitesi kuruyorsun, kullanıcılar medya kütüphanesine fotoğraf yükleyemiyor. Şikayetler geliyor, “Resim yükleyince hata alıyorum” diyor herkes.
İlk olarak wp-config.php‘deki PHP limitlerini düşünürsün. Ama hayır, sorun daha önce, Nginx katmanında yaşanıyor. PHP bu isteği hiç görmüyor bile.
WordPress için tipik bir Nginx konfigürasyonu:
server {
listen 443 ssl;
server_name myblog.com;
root /var/www/wordpress;
index index.php;
# WordPress için makul bir limit
client_max_body_size 64m;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ .php$ {
fastcgi_pass unix:/run/php/php8.1-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# PHP-FPM timeout'larını da ayarla
fastcgi_read_timeout 300;
fastcgi_send_timeout 300;
}
}
Ama sadece Nginx’i ayarlamak yetmez. PHP tarafında da eşleşen değerler olmalı:
# PHP konfigürasyonunu kontrol et
php -i | grep -E "upload_max_filesize|post_max_size|max_execution_time"
# php.ini'yi düzenle
sudo nano /etc/php/8.1/fpm/php.ini
php.ini içinde şunları güncelle:
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 300
max_input_time = 300
memory_limit = 256M
Ve her iki servisi de yeniden başlat:
sudo nginx -t && sudo systemctl reload nginx
sudo systemctl restart php8.1-fpm
Gerçek Dünya Senaryosu 2: Reverse Proxy Arkasındaki Uygulama
Nginx’i bir Node.js veya Python uygulamasının önüne reverse proxy olarak koyduğunda, hem Nginx’i hem de arkadaki uygulamayı ayarlaman gerekir. Çünkü Nginx isteği geçirse bile, arkadaki uygulama kendi limitini uygulayabilir.
Node.js Express ile bir API yazıyorsun ve büyük JSON payload’lar kabul ediyorsun:
server {
listen 80;
server_name api.example.com;
location /api/v1/bulk-import {
# Toplu veri import endpoint'i için özel limit
client_max_body_size 50m;
# Büyük yüklemeler için timeout'ları artır
proxy_read_timeout 600;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /api/ {
client_max_body_size 10m;
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Node.js tarafında Express için de ayar gerekir:
// Express uygulamasında body-parser limiti
const express = require('express');
const app = express();
app.use(express.json({ limit: '50mb' }));
app.use(express.urlencoded({ extended: true, limit: '50mb' }));
Gerçek Dünya Senaryosu 3: Büyük Video Yüklemeleri
Video platformu gibi bir şey geliştiriyorsun ve kullanıcılar 500MB, 1GB’lık videolar yükleyecek. Bu senaryoda client_max_body_size 0 diyip geçemezsin, çünkü bu sunucunu DoS saldırısına açar. Akıllıca bir yaklaşım gerekir.
server {
listen 443 ssl;
server_name video.example.com;
# Genel limit
client_max_body_size 10m;
location /upload/video {
# Video yükleme endpoint'i
client_max_body_size 2048m; # 2GB
# Büyük dosyalar için buffer ayarları
client_body_buffer_size 128k;
client_body_temp_path /var/nginx/tmp/uploads;
# Timeout'ları agresif şekilde artır
proxy_read_timeout 3600;
proxy_send_timeout 3600;
# Chunked transfer için
proxy_request_buffering off;
proxy_pass http://upload-service:8080;
}
}
Burada proxy_request_buffering off önemli bir detay. Varsayılan olarak Nginx, isteği tamamen alıp diske yazar, sonra backend’e iletir. Büyük dosyalarda bu disk I/O’ya ve belleğe ciddi yük bindirir. off yapınca streaming olarak iletir.
Ayrıca geçici dosyalar için kullanılan dizinin mevcut olduğundan ve Nginx’in yazma yetkisi olduğundan emin ol:
sudo mkdir -p /var/nginx/tmp/uploads
sudo chown -R www-data:www-data /var/nginx/tmp/uploads
sudo chmod 750 /var/nginx/tmp/uploads
Buffer ve Timeout Ayarları: client_max_body_size’ın Kardeşleri
Sadece client_max_body_size ayarlamak çoğu zaman yetmez. Bu direktifle birlikte çalışan diğer parametreler de kritik:
client_body_buffer_size
İstek gövdesi önce belleğe alınır. Bu buffer dolunca diske yazılır. Küçük dosyalar için bellek yeterli olur, büyükler disk geçicisine düşer.
# Küçük buffer = daha çok disk I/O ama az bellek kullanımı
# Büyük buffer = az disk I/O ama çok bellek kullanımı
client_body_buffer_size 256k;
client_body_timeout
İstemcinin iki ardışık veri paketi göndermesi arasında geçen süre. Yavaş bağlantılardaki kullanıcılar bu timeout’a takılabilir.
# Yavaş bağlantılar için artır (saniye cinsinden)
client_body_timeout 120;
client_header_timeout
İstek başlığının gönderilmesi için ayrılan süre.
client_header_timeout 30;
Hepsini bir arada kullanan örnek konfigürasyon:
http {
# Global varsayılanlar
client_max_body_size 10m;
client_body_buffer_size 128k;
client_body_timeout 60;
client_header_timeout 30;
# Geçici dosya dizini
client_body_temp_path /var/nginx/tmp 1 2;
server {
listen 443 ssl;
server_name example.com;
location /upload {
client_max_body_size 500m;
client_body_buffer_size 1m;
client_body_timeout 300;
proxy_pass http://backend;
}
}
}
Nginx’in Önünde Load Balancer Varsa Ne Olur?
Production ortamlarında genellikle Nginx’in önünde bir load balancer veya CDN olur. AWS ALB, HAProxy, Cloudflare… Bu durumda 413 hatası Nginx’ten önce de gelebilir.
AWS ALB için: ALB’nin varsayılan istek boyutu limiti yoktur ama hedef gruplara iletme kurallarında bazı limitler olabilir.
HAProxy’nin önünde Nginx varsa: HAProxy’de de maxconn ve timeout değerlerini uyumlu şekilde ayarla.
Cloudflare kullanıyorsan: Free plan’da Cloudflare 100MB’ı sınırlar. Pro ve üstünde bu limit farklıdır. Eğer Cloudflare arkasında büyük dosya yüklemeleri yapılacaksa, o endpoint için Cloudflare bypass’ını düşünmek gerekebilir.
Hangi katmanın hata verdiğini anlamak için:
# Curl ile test et ve response header'larına bak
curl -v -X POST https://example.com/upload
-H "Content-Type: multipart/form-data"
-F "file=@/tmp/testfile.bin"
2>&1 | grep -E "< HTTP|< Server|< X-"
Response’taki Server başlığı sana hangi katmanın yanıt verdiğini söyler.
Konfigürasyonu Test Etmek
Değişiklik yaptıktan sonra mutlaka test et. Önce syntax kontrolü:
sudo nginx -t
Başarılıysa:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Sonra yeniden yükle:
sudo systemctl reload nginx
reload yerine restart kullanmaktan kaçın. reload graceful bir şekilde yeni konfigürasyonu yükler, aktif bağlantıları kesmez.
curl ile farklı boyutlarda test dosyası oluşturup test et:
# 5MB test dosyası oluştur
dd if=/dev/urandom of=/tmp/test5mb.bin bs=1M count=5
# 50MB test dosyası oluştur
dd if=/dev/urandom of=/tmp/test50mb.bin bs=1M count=50
# Yükleme testi
curl -w "n%{http_code}n" -X POST https://example.com/upload
-F "file=@/tmp/test5mb.bin"
# Daha detaylı çıktı için
curl -v --progress-bar -X POST https://example.com/upload
-F "file=@/tmp/test50mb.bin"
-o /dev/null 2>&1
Güvenlik Açısından Değerlendirmek
client_max_body_size 0 deyip geçmek cazip görünebilir, ama bu ciddi bir güvenlik açığıdır. Disk dolma saldırıları, memory exhaustion, bandwidth tükenmesi… Hepsinin kapısını açarsın.
Makul sınırlar koy ve gerektiğinde rate limiting ile destekle:
# Upload endpoint'i için rate limiting
limit_req_zone $binary_remote_addr zone=upload_limit:10m rate=10r/m;
server {
location /upload {
# Dakikada maksimum 10 yükleme isteği
limit_req zone=upload_limit burst=5 nodelay;
# Boyut limiti makul seviyede
client_max_body_size 100m;
proxy_pass http://backend;
}
}
Bu konfigürasyonla bir IP’nin dakikada 10’dan fazla yükleme isteği göndermesini engellersin. DDoS ve kötüye kullanım senaryolarını bir miktar dizginler.
Hata Mesajını Özelleştirmek
Varsayılan Nginx 413 hata sayfası çirkin ve bilgilendirici değil. Bunu özelleştirebilirsin:
server {
# Özel hata sayfaları
error_page 413 /errors/413.html;
location = /errors/413.html {
internal;
root /var/www/html;
}
}
/var/www/html/errors/413.html dosyası:
<!DOCTYPE html>
<html>
<head><title>Dosya Çok Büyük</title></head>
<body>
<h1>Dosya Boyutu Sınırı Aşıldı</h1>
<p>Yüklemek istediğiniz dosya izin verilen maksimum boyutu aşıyor.</p>
<p>Maksimum dosya boyutu: 100MB</p>
</body>
</html>
Monitoring ve Alerting
Üretim ortamında 413 hatalarını takip etmek, kullanıcıların yaşadığı sorunları proaktif olarak tespit etmeni sağlar.
# 413 hatalarını canlı izle ve say
watch -n 5 'grep "$(date +%d/%b/%Y)" /var/log/nginx/access.log |
awk '"'"'$9 == 413'"'"' | wc -l'
Prometheus ve Nginx Exporter kullanıyorsan, status sayfasını aktif et:
server {
listen 127.0.0.1:8080;
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
deny all;
}
}
Grafana Dashboard’unda 413 spike’larını görmek, hangi deployment’tan sonra sorun çıktığını anlamana yardımcı olur.
Yaygın Hatalar ve Çözümleri
Nginx’i yeniden yüklemeyi unutmak: Konfigürasyonu değiştirdin ama systemctl reload nginx yapmadın. Değişiklikler devreye girmez.
Yanlış context’te tanımlamak: location /upload bloğuna koyduğun ayar, /upload/files path’ini etkilemeyebilir. Wildcard kullanmayı değerlendir.
PHP limitlerini eşitlemeyi unutmak: Nginx’i açtın ama PHP hala eski limitle çalışıyor. Her iki tarafı da güncelle.
Proxy_buffering ile çelişki: proxy_request_buffering off iken bazı upstream ayarları beklenmedik davranışlara yol açabilir. Logları dikkatli izle.
Disk dolması: Büyük dosya yüklemelerine izin verdikten sonra disk takibini ihmal etme. df -h ve du -sh /var/nginx/tmp/* komutlarını düzenli çalıştır.
Sonuç
client_max_body_size direktifi, ilk bakışta tek satırlık basit bir ayar gibi görünür. Ama production ortamında bu direktifi doğru yapılandırmak; güvenlik, performans, uygulama katmanı uyumluluğu ve kullanıcı deneyimi açısından kritik bir hal alır.
Logları okumayı bil, hatanın hangi katmandan geldiğini tespit et, sadece Nginx değil PHP/Node/Python tarafını da güncelle, güvenliği ihmal etme ve mutlaka test et. Bu adımları takip ettiğinde, “dosya yükleyemiyorum” şikayetleri geçmişte kalır.
Bir sonraki sefer birisi sana 413 hatası getirdiğinde, önce error log’u aç, kaç byte’lık bir dosya gönderilmeye çalışıldığını gör, uygulama mimarisine uygun bir limit belirle ve konfigürasyonu katmanlı olarak uygula. Bu kadar.
