Sunucu Yönetimi & Linux

    Too Many Open Files Hatası: Nginx ve PHP-FPM Dosya Limiti Çözümü

    Too many open files hatasının üç katmanlı kökeni ve descriptor sızıntısını limitten ayırt etme yöntemi.

    11 dk okuma Güncellendi: 18 Ağustos 2026

    Trafik yükseldiğinde site birden 502 vermeye başlar, error.log dosyasını açarsınız ve aynı satır saniyede onlarca kez tekrar eder:

    2026/08/18 21:14:03 [alert] 1188#1188: accept4() failed (24: Too many open files)
    2026/08/18 21:14:03 [crit] 1191#1191: *84213 socket() failed (24: Too many open files) while connecting to upstream
    

    Refleks bellidir: ulimit -n yazarsınız, 1024 görürsünüz, ulimit -n 65535 ile büyütürsünüz, servisi yeniden başlatırsınız. On dakika sonra hata geri gelir. Bu noktada çoğu kişi limitin uygulanmadığını düşünüp /etc/security/limits.conf dosyasına satır ekler, sunucuyu tamamen yeniden başlatır ve hâlâ aynı çıktıyla karşılaşır.

    Sebep basit ama sezgiye aykırıdır: systemd tarafından başlatılan bir servis, sizin kabuk oturumunuzun limitini de limits.conf dosyasını da okumaz. O dosya PAM üzerinden yalnızca interaktif giriş yapan oturumlara uygulanır; nginx.service ise PAM'e hiç uğramadan, systemd'nin kendi tanımladığı çerçeveyle doğar. Yani doğru komutu çalıştırıp yanlış katmanı düzeltmiş olursunuz.

    Bu yazıda hatanın üç ayrı katmanını sırayla düzelteceğiz, sonra asıl kritik soruya geçeceğiz: limit gerçekten mi yetersiz, yoksa uygulamanız descriptor sızdırdığı için mi tavana çarpıyorsunuz? Limitin ne olduğunu ve ulimit bayraklarını hiç bilmiyorsanız önce ulimit ile kaynak limitleri yazısını okuyun; burada kavramı tekrar anlatmayıp doğrudan teşhis ve kalıcı çözüme odaklanacağım.

    Too Many Open Files Hatası Tam Olarak Neyi Söylüyor#

    Linux'ta yalnızca disk üzerindeki dosyalar değil, her TCP soketi, her Unix soketi, her pipe, her eventfd ve her inotify örneği de bir file descriptor (dosya tanımlayıcı) tüketir. Nginx bir istemci bağlantısı için bir descriptor, upstream'e (PHP-FPM'e) açtığı bağlantı için bir descriptor daha kullanır. Yani 5.000 eş zamanlı istek, tek başına 10.000 descriptor demektir.

    Hata mesajındaki parantez içi sayı, hangi tavana çarptığınızı söyler ve bu ayrım tüm çözümü belirler:

    errnoSabitAnlamıDoğru katman
    24EMFILESüreç kendi tavanına çarptıLimitNOFILE / worker_rlimit_nofile
    23ENFILESistem genelindeki tavan doldufs.file-max (sysctl)

    Pratikte gördüğünüz neredeyse her zaman 24'tür; sistem geneli tavan modern çekirdeklerde RAM'e göre otomatik hesaplandığı için 23 çok nadirdir. Bu yüzden fs.file-max değerini büyütmek çoğu senaryoda hiçbir şeyi düzeltmez — ama üçüncü katmanı hazırlayan bir ön koşuldur, birazdan göreceğiz.

    Aynı hata metnini üreten ama tamamen farklı bir kaynak daha vardır: inotify. Bir dosya izleme aracı (inotifywait, bazı deploy scriptleri, Node tabanlı watcher'lar) fs.inotify.max_user_instances tavanına çarptığında da çekirdek EMFILE döndürür ve ekranda yine "too many open files" yazar. Descriptor sayınız düşükken bu hatayı alıyorsanız önce oraya bakın:

    # Kullanıcı başına inotify örneği ve izleme tavanı
    sysctl fs.inotify.max_user_instances fs.inotify.max_user_watches
    

    ulimit -n Değiştirdim Ama Hata Devam Ediyor#

    ulimit -n 65535 komutunun etkisi, o komutu yazdığınız kabuk oturumu ve o oturumdan başlattığınız süreçlerle sınırlıdır. Oturumu kapattığınızda kaybolur, başka bir SSH oturumuna geçmez ve en önemlisi systemd'nin yönettiği servislere hiç ulaşmaz.

    /etc/security/limits.conf de aynı boşluğa düşer. Bu dosyayı okuyan şey PAM'in pam_limits.so modülüdür ve PAM yalnızca bir kullanıcı giriş yaptığında devreye girer: SSH oturumu, su, login. systemctl start nginx böyle bir giriş değildir. Dolayısıyla limits.conf içine yazdığınız nginx soft nofile 65535 satırı, nginx.service için ölü bir satırdır.

    Gerçek durumu tek komutla görebilirsiniz. Kabuğunuzun değeri değil, çalışan sürecin değeri bağlayıcıdır:

    # Çalışan nginx master sürecinin gerçek limitleri
    cat /proc/$(pgrep -o nginx)/limits | grep -i "open files"
    
    # PHP-FPM master için aynı bakış
    cat /proc/$(pgrep -o php-fpm)/limits | grep -i "open files"
    

    Çıktıdaki Soft Limit sütunu hâlâ 1024 gösteriyorsa, ne yaptığınızın önemi yok — o süreç 1024 descriptor ile yaşıyor demektir. systemd 240 ve sonrasında servisler için varsayılan DefaultLimitNOFILE=1024:524288'dir: soft limit 1024, hard limit 524288. Yani tavanı yükseltme yetkiniz zaten var, sadece soft limiti kimse yükseltmiyor.

    Aşağıdaki üç katmanı sırayla uygulayın. Sıra önemlidir, çünkü her katman bir üstündekinin tanımladığı tavanı aşamaz.

    Katman 1: Sistem Geneli fs.file-max ve fs.nr_open#

    Bu katmana çoğu zaman dokunmanız gerekmez, ama iki değeri bilmeniz gerekir. fs.file-max sistemin toplam descriptor tavanıdır; fs.nr_open ise tek bir sürecin RLIMIT_NOFILE değerinin alabileceği en büyük sayıdır ve varsayılanı 1048576'dır. LimitNOFILE değerini fs.nr_open üzerine çıkarmaya çalışırsanız servis başlamaz.

    Önce mevcut kullanımı ölçün:

    # allocated  unused  max  → üçüncü sütun fs.file-max
    cat /proc/sys/fs/file-nr
    
    sysctl fs.file-max fs.nr_open
    

    İlk sütun (allocated) üçüncü sütuna yaklaşmıyorsa sistem tavanı sorun değildir; ikinci katmana geçin. Yaklaşıyorsa kalıcı olarak büyütün:

    sudo tee /etc/sysctl.d/99-file-max.conf >/dev/null <<'EOF'
    fs.file-max = 2097152
    EOF
    
    sudo sysctl --system
    

    sysctl -w ile yapılan değişiklik yeniden başlatmada kaybolur; bu yüzden dosyaya yazıp --system ile uygulayın.

    Katman 2: systemd Servisi İçin LimitNOFILE Ayarı#

    Asıl düzeltme burasıdır. Paket yöneticisinin kurduğu .service dosyasını doğrudan düzenlemeyin — bir sonraki güncelleme üzerine yazar. Bunun yerine drop-in override kullanın:

    sudo systemctl edit nginx
    

    Açılan editöre yalnızca şunu yazın:

    [Service]
    LimitNOFILE=65535
    

    Bu, /etc/systemd/system/nginx.service.d/override.conf dosyasını oluşturur ve paket güncellemelerinden etkilenmez. Aynısını PHP-FPM için de yapın (systemctl edit php8.3-fpm gibi, sürümünüze göre). Tek bir değer yazdığınızda systemd hem soft hem hard limiti o değere sabitler; ayrı ayrı vermek isterseniz LimitNOFILE=65535:524288 biçimini kullanın.

    Ardından — ve bu adım en sık atlanan yerdir:

    sudo systemctl daemon-reload
    sudo systemctl restart nginx
    

    reload değil, restart. RLIMIT_NOFILE bir sürece doğduğu anda verilir; nginx -s reload ya da systemctl reload nginx yalnızca yapılandırmayı tazeler, master süreci yerinde kalır ve eski limitiyle yaşamaya devam eder. Sayfaların düzeldiğini sanıp yükte tekrar hataya düşmenin bir numaralı sebebi budur.

    Tüm servisler için varsayılanı topluca yükseltmek isterseniz /etc/systemd/system.conf içindeki DefaultLimitNOFILE= satırını kullanabilirsiniz, ama bunun için sunucuyu yeniden başlatmanız gerekir ve etkisi geniştir. Servis bazlı override çoğu durumda daha doğru tercihtir. systemd unit dosyalarının yapısına aşina değilseniz systemd servis yönetimi yazısı bu dosyaların anatomisini açıklar.

    Yeniden başlatmadan, çalışan bir süreç üzerinde acil müdahale gerekiyorsa prlimit kullanabilirsiniz. Kalıcı değildir, sadece yangını söndürür:

    sudo prlimit --pid $(pgrep -o nginx) --nofile=65535:65535
    

    Katman 3: Nginx worker_rlimit_nofile ve PHP-FPM rlimit_files#

    systemd limiti düzeltti, ama Nginx'in kendi ayarı var. worker_rlimit_nofile direktifi, master sürecin worker'lara devrettiği descriptor limitini belirler ve systemd'nin verdiği hard limite kadar yükselebilir. Ayarlanmadığında worker'lar sistemden miras aldıkları soft limitle çalışır.

    Kritik ilişki şudur: bir worker, her istemci bağlantısı için bir, upstream bağlantısı için bir descriptor kullanır. Bu yüzden worker_rlimit_nofile değeri worker_connections değerinin en az iki katı olmalıdır.

    # /etc/nginx/nginx.conf — ana (main) bağlam
    user  www-data;
    worker_processes  auto;
    worker_rlimit_nofile  65535;
    
    events {
        worker_connections  8192;
        multi_accept on;
    }
    

    Değişiklikten sonra sözdizimini doğrulayıp servisi yeniden başlatın:

    sudo nginx -t && sudo systemctl restart nginx
    
    # Worker süreçlerinin gerçekten aldığını doğrula
    for p in $(pgrep -f "nginx: worker"); do
      grep "Max open files" /proc/$p/limits
    done
    

    PHP-FPM tarafında ayarın adı farklıdır ve havuz (pool) dosyasına yazılır, php.ini içine değil:

    ; /etc/php/8.3/fpm/pool.d/www.conf
    rlimit_files = 65535
    rlimit_core = 0
    

    PHP-FPM'de descriptor tüketiminin ikinci kaynağı, aynı anda ayakta duran worker sayısıdır. pm.max_children değerini yükseltmek, dolaylı olarak descriptor talebini de yükseltir; ikisini birlikte planlayın. Havuz boyutlandırmasının mantığı için PHP-FPM pool ayarları ve iki servisin nasıl konuştuğu için Nginx ve PHP-FPM yapılandırması yazılarına bakabilirsiniz.

    Veritabanı da aynı zincirde yer alır: MySQL/MariaDB için open_files_limit değeri, systemd LimitNOFILE değerinden büyük olamaz. SHOW VARIABLES LIKE 'open_files_limit'; çıktısı yazdığınız değerden düşükse, sınırlayan katman systemd'dir.

    Limit mi Yetmiyor Yoksa Descriptor Sızıntısı mı Var#

    Buraya kadar yaptığınız her şey tavanı yükseltmekti. Ama tavanı yükselttikçe hata birkaç saat sonra geri geliyorsa, sorun limit değil sızıntıdır: uygulama açtığı descriptor'ları kapatmıyordur ve siz sadece çökme anını erteliyorsunuzdur.

    Ayrım için tek bir soru yeterlidir: descriptor sayısı yük ile mi artıyor, yoksa zamanla mı? Yük düştüğünde geri iniyorsa limit sorunudur; yük düşse bile inmiyorsa sızıntıdır.

    Ölçmek için sayacı bir süre izleyin:

    # PHP-FPM master sürecinin açık descriptor sayısını 5 saniyede bir yazdır
    PID=$(pgrep -o php-fpm)
    watch -n5 "ls /proc/$PID/fd | wc -l"
    
    # Tüm süreçler için en çok descriptor tutan ilk 10 süreç
    sudo lsof -n 2>/dev/null | awk '{print $2}' | sort | uniq -c | sort -rn | head -10
    

    Sayı gece boyunca trafik olmadan da düzenli artıyorsa sızıntı kesindir. Kaynağı bulmak için o sürecin descriptor'larını türüne göre gruplayın:

    # Descriptor tiplerini say: REG (dosya), IPv4/IPv6 (soket), sock, FIFO, a_inode
    sudo lsof -n -p $PID | awk '{print $5}' | sort | uniq -c | sort -rn
    
    # Aynı dosyanın yüzlerce kez açık olması klasik sızıntı imzasıdır
    sudo lsof -n -p $PID | awk '{print $NF}' | sort | uniq -c | sort -rn | head -20
    

    En sık gördüğüm üç imza şudur:

    1. Aynı log dosyası yüzlerce kez açık. Uygulama her istekte dosyayı açıp kapatmayı unutuyordur. Burada asıl bakılacak yer uygulama kodudur; servisin ürettiği kayıtları incelemek için journalctl ile log yönetimi yazısındaki filtreleme yöntemleri işinizi kolaylaştırır.
    2. Yığınla CLOSE_WAIT durumunda soket. Karşı taraf bağlantıyı kapatmış, uygulama close() çağırmamıştır. Soket durumlarını saymak için açık portları listeleme yazısındaki ss kullanımından yararlanabilirsiniz:
    ss -tan state close-wait | wc -l
    
    1. (deleted) etiketli dosyalar. Diskten silinmiş ama süreç hâlâ tutuyordur; hem descriptor hem disk alanı sızdırır:
    sudo lsof -n +L1 | head -20
    

    Sızıntı tespit ettiğinizde limiti büyütmek doğru hamle değildir; ilgili servisi zamanlanmış bir yeniden başlatmayla ayakta tutup asıl düzeltmeyi uygulama tarafında yapmak gerekir. PHP-FPM'de pm.max_requests değerini sonlu bir sayıya (örneğin 500) çekmek, worker'ları periyodik olarak tazeleyerek sızıntının etkisini sınırlar — kalıcı çözüm değil, ama nefes aldırır.

    Kaç Descriptor'a İhtiyacınız Var: Kaba Hesap#

    Rastgele büyük bir sayı yazmak yerine ihtiyacınızı kabaca hesaplayabilirsiniz. Nginx için formül şudur:

    gereken_descriptor ≈ (eş_zamanlı_bağlantı × 2) + açık_log_dosyası + upstream_soket_havuzu + emniyet_payı
    

    Örnek bir senaryo: saniyede 400 istek alan, ortalama yanıt süresi 200 ms olan bir sitede aynı anda ayakta duran istek sayısı yaklaşık 80'dir. Ancak keepalive bağlantıları çok daha uzun yaşar; 60 saniyelik keepalive_timeout ile aynı anda açık kalan bağlantı birkaç bine çıkabilir. Bu bağlantıların her biri istemci tarafında bir, PHP-FPM tarafında bir descriptor tutar. 4.000 açık bağlantı, tek başına 8.000 descriptor demektir.

    PHP-FPM'de hesap daha basittir: her worker aynı anda bir isteği işler, ama o istek sırasında veritabanı bağlantısı, oturum dosyası, açtığı şablon dosyaları ve varsa Redis/Memcached soketi ayrı ayrı descriptor tutar. Worker başına 20–50 descriptor gerçekçi bir tahmindir; 50 worker'lık bir havuz 2.500 civarına ulaşabilir.

    Bu sayıları toplayıp iki katını almak, güvenli bir LimitNOFILE değeri verir. Pratikte 65535 orta ölçekli sunucuların neredeyse tamamını rahatlıkla karşılar; bu değerin üzerine ancak on binlerce eş zamanlı bağlantı taşıyan reverse proxy'lerde çıkmak gerekir. Ne kadar bellek harcadığını merak ediyorsanız cevap "çok az": descriptor tablosu ihtiyaç oldukça büyüyen bir yapıdır, tavanı yükseltmek tek başına RAM tüketmez.

    Doğrulama Kontrol Listesi ve Sık Yapılan Hatalar#

    Değişiklikten sonra "düzeldi" demeden önce şu dört çıktının hepsini görmüş olmalısınız:

    # 1) Sistem tavanı ve mevcut kullanım
    cat /proc/sys/fs/file-nr
    
    # 2) Master süreçlerin gerçek limiti (kabuğunkine değil, buna bakın)
    cat /proc/$(pgrep -o nginx)/limits | grep -i "open files"
    cat /proc/$(pgrep -o php-fpm)/limits | grep -i "open files"
    
    # 3) Worker süreçlerinin limiti (master'dan miras almış olmalı)
    for p in $(pgrep -f "nginx: worker"); do grep "Max open files" /proc/$p/limits; done
    
    # 4) Hatanın gerçekten kesildiği
    sudo grep -c "Too many open files" /var/log/nginx/error.log
    

    Sık yapılan hatalar:

    HataNeden işe yaramazDoğrusu
    limits.conf düzenlemeksystemd servisleri PAM'e uğramazsystemctl edit SERVISADI ile LimitNOFILE
    systemctl reload nginxLimit süreç doğarken verilirsystemctl restart nginx
    Yalnızca fs.file-max büyütmekHata errno 24, yani süreç tavanıKatman 2 ve 3
    Unit dosyasını doğrudan düzenlemekPaket güncellemesi üzerine yazarDrop-in override.conf
    worker_rlimit_nofile yazıp daemon-reload unutmaksystemd override okunmazÖnce daemon-reload, sonra restart
    Limiti sürekli büyütmekSızıntıyı gizlerÖnce sızıntı testi

    Son bir not: paylaşımlı hosting kullanıyorsanız bu katmanların hiçbirine erişiminiz yoktur. Orada aynı belirti farklı bir kılıkta, hesap bazlı kaynak sınırı olarak karşınıza çıkar; o senaryoyu 508 Resource Limit Is Reached hatası yazısında ayrıca ele aldım.

    Sıkça Sorulan Sorular#

    ulimit -n 65535 yazdım, neden hâlâ hata alıyorum?#

    Çünkü o komut yalnızca yazdığınız kabuk oturumunu ve o oturumdan başlattığınız süreçleri etkiler. Nginx, PHP-FPM veya MySQL systemd tarafından başlatıldığı için kabuğunuzun limitinden tamamen bağımsızdır. Gerçek değeri cat /proc/PID/limits ile okuyun; hâlâ 1024 görüyorsanız çözüm systemctl edit SERVISADI ile LimitNOFILE tanımlamaktır.

    LimitNOFILE değerini kaç yapmalıyım?#

    Eş zamanlı bağlantı hedefinizin en az iki katı, üstüne emniyet payı ekleyerek. Orta ölçekli bir web sunucusu için 65535 fazlasıyla yeterlidir ve ölçülebilir bir maliyeti yoktur; descriptor tablosu ihtiyaç oldukça büyür. Gereksiz yere 1048576 gibi değerler yazmak fayda getirmez, üstelik fs.nr_open tavanını aşarsanız servis hiç başlamaz.

    limits.conf tamamen gereksiz mi?#

    Hayır, ama kapsamı dardır. SSH ile giriş yapan kullanıcılar, cron dışındaki interaktif oturumlar ve su ile geçilen hesaplar için hâlâ geçerlidir. Elle başlattığınız uzun süreli işlemler, yedekleme scriptleri veya kabuktan çalıştırdığınız derleme işleri bu dosyadan etkilenir. Servisler için ise systemd LimitNOFILE tek geçerli yoldur.

    Descriptor sızıntısı olduğunu nasıl kesin anlarım?#

    Trafiği düşürün ya da gecenin en sakin saatinde ölçün. Açık descriptor sayısı yük düştüğünde geri iniyorsa limit yetersizliği, inmiyor ve düzenli artmaya devam ediyorsa sızıntı vardır. lsof -n -p PID çıktısında aynı dosya adının onlarca kez tekrarlanması veya ss -tan state close-wait sayısının sürekli büyümesi bunu doğrular.

    Nginx'te worker_rlimit_nofile ile worker_connections arasındaki ilişki nedir?#

    Her bağlantı istemci tarafında bir, upstream tarafında bir descriptor tüketir. Bu yüzden bir worker'ın kaldırabileceği gerçek bağlantı sayısı worker_rlimit_nofile / 2 civarındadır. worker_connections değerini bu sayının üzerine çıkarırsanız Nginx bağlantıyı kabul eder ama descriptor bulamayıp hata verir. Pratik kural: worker_rlimit_nofile en az worker_connections × 2 olsun.

    Ayarı yaptım, reload ettim, neden değişmedi?#

    RLIMIT_NOFILE bir sürece doğduğu anda verilir ve çalışırken kendiliğinden güncellenmez. reload, master süreci yerinde bırakıp yalnızca yapılandırmayı tazeler; eski limit aynen devam eder. systemctl daemon-reload ile systemd'nin override'ı okumasını sağlayın, ardından systemctl restart ile süreci gerçekten yeniden doğurun. Kesinti kabul edilemiyorsa geçici olarak prlimit kullanabilirsiniz.

    NginxLinuxSorun Giderme

    Uygulamaya geçmeye hazır mısınız?

    NVMe SSD, ücretsiz SSL ve %99.9 uptime garantisiyle Clou.TR hosting ve sunucu çözümleriyle projenizi hayata geçirin.