Sunucunun hata günlüğünde ya da ekranda "Allowed memory size of 134217728 bytes exhausted" satırını gördüyseniz, bir PHP betiği kendisine tanınan bellek sınırını (memory_limit) doldurmuş demektir. Bu hata WordPress'te eklenti güncellerken, XenForo'da içe aktarma sırasında, Laravel'de bir kuyruk işinde ya da composer update çalışırken çıkabilir. Sınırı yükseltmek çoğu zaman siteyi birkaç dakikada ayağa kaldırır. Ancak belleğin nereye gittiğini bulmazsanız hata birkaç hafta sonra daha büyük bir sayıyla geri gelir. Bu rehberde önce mesajı okumayı ve sınırı doğru katmanda, gerektiği kadar artırmayı, ardından belleği tüketen kodu bulup düzeltmeyi anlatıyoruz. Aşağıdaki hata çıktılarını ve ölçümleri 27 Ekim 2026'da PHP 8.4 ile kendimiz ürettik. 21 Ekim 2026 güvenlik sürümleriyle değişen davranışı da ayrı bir bölümde ele alıyoruz.
Hata mesajı ne söylüyor?
Test betiğimizin ürettiği mesaj şöyle (yalnızca dosya yolunu değiştirdik):
Kod:
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes) in /var/www/ornek/import.php on line 4
Mesajın üç parçası var:
- 134217728 bytes: Sınırın kendisi. 134.217.728 = 128 × 1024 × 1024, yani memory_limit = 128M. Bu, PHP'nin php.ini olmadan kullandığı varsayılan değerdir; php.ini-production dosyasında da aynı değer yazar.
- tried to allocate 20480 bytes: Sınırı aşan son bellek ayırma (allocation) isteği. Genellikle küçüktür ve sorunun kaynağını göstermez; bardağı taşıran son damladır.
- in … on line 4: Belleğin bittiği yer. Belleği dolduran kod başka bir yerde olabilir. Döngüde büyüyen bir dizi, mesajda yalnızca son elemanın eklendiği satırla görünür.
Mesajdaki bayt değerini ayara çevirmek için:
| Mesajdaki sayı | Ayar | Nerede karşınıza çıkar? |
|---|---|---|
| 2097152 | 2M | Neredeyse her zaman yazım hatası. PHP 7.x'te "256MB" gibi geçersiz bir değer sınırı sessizce 2 MiB'a düşürüyordu. |
| 41943040 | 40M | WordPress'in tek sitede varsayılan WP_MEMORY_LIMIT değeri (yalnızca sunucu sınırı daha düşükse uygulanır) |
| 67108864 | 64M | WordPress Multisite'ta varsayılan WP_MEMORY_LIMIT |
| 134217728 | 128M | PHP'nin varsayılanı |
| 268435456 | 256M | WordPress'in yönetim paneli, WP-Cron ve görsel işleme için varsayılan WP_MAX_MEMORY_LIMIT değeri |
| 536870912 | 512M | Elle yükseltilmiş sınır |
| 1073741824 | 1G | Elle yükseltilmiş sınır, çoğunlukla komut satırı işleri için |
| 1610612736 | 1536M | Composer'ın kendi yükselttiği sınır (1,5 GiB) |
"tried to allocate" kısmı da ipucu verebilir:
- Sürekli 262144 bytes (256 KiB) görüyorsanız sonsuz özyineleme (recursion) olasılığı yüksektir. PHP, fonksiyon çağrılarının çerçevelerini 256 KiB'lık sayfalar hâlinde ayırır; kendini durmadan çağıran test fonksiyonumuz tam olarak bu mesajı verdi.
- Yüzlerce megabaytlık bir değer, tek seferde dev bir dize ya da dizi oluşturulduğunu gösterir. 200 MiB'lık tek bir str_repeat() çağrısı testimizde "tried to allocate 209715232 bytes" üretti. Bir dosyayı tümüyle belleğe okumak ya da büyük bir JSON üretmek de aynı izi bırakır.
- 4096, 8192 veya 20480 gibi küçük değerler belleğin yavaş yavaş dolduğunu gösterir. Suçlu satırın kendisi değil, o ana kadar biriken veridir.
Hızlı ve güvenli çözüm: sınırı doğru katmanda artırın
1. Kodu hangi PHP çalıştırıyor?
Aynı sunucuda birden fazla PHP sürümü ve her sürümün ayrı yapılandırması bulunabilir. Komut satırı (CLI) bir php.ini okur, web tarafındaki PHP-FPM başka birini. Önce komut satırındaki durumu görün:
Bash:
php -v
php --ini
php -i | grep -E '^memory_limit'
php -r 'echo ini_get("memory_limit"), PHP_EOL;'
php --ini, yüklenen php.ini dosyasını (Loaded Configuration File) ve ek .ini dosyalarının okunduğu dizini gösterir. php -i çıktısındaki "memory_limit => 128M => 128M" satırında ilk değer o anda geçerli olan (local), ikincisi ana (master) değerdir. Debian ve Ubuntu paketlerinde komut satırı için ayrı bir php.ini gelir ve bu dosyada memory_limit = -1 yazılıdır; FPM'in php.ini dosyasında ise değer 128M'dir. Bu yüzden terminalde sorunsuz çalışan bir betik web'de çökebilir, sistem cron'unda çalışan bir iş de web isteklerinden tamamen farklı ayarlarla çalışır.
Web tarafını görmek için sitenin belge kök dizinine tahmin edilmesi zor adlı geçici bir dosya koyun, tarayıcıda açın ve sonucu okuduktan sonra hemen silin:
PHP:
<?php
// gecici-kontrol-7f3a.php : sonucu okuduktan sonra silin
echo php_sapi_name(), ' | ', PHP_VERSION, ' | ', ini_get('memory_limit'), ' | ', php_ini_loaded_file() ?: 'php.ini yok';
Çıktıdaki ilk değer sunucu API'sidir (SAPI): fpm-fcgi (PHP-FPM), cgi-fcgi (CGI/FastCGI), apache2handler (Apache mod_php) ya da litespeed (LiteSpeed LSAPI). Aynı bilgileri phpinfo() da verir. Orada memory_limit satırının Local Value ve Master Value sütunları farklıysa .user.ini, .htaccess ya da kod içindeki bir ayar ana değeri eziyor demektir. phpinfo() sunucu hakkında çok fazla bilgi gösterdiği için o sayfayı da işiniz bitince silin.
2. Ne kadar artırmalı?
Sınırı bir basamak yükseltin (128M → 256M), siteyi deneyin ve aşağıdaki "Gerçek nedeni bulmak" bölümünde anlattığımız gibi tepe belleği ölçün. Sınırı bir anda 2G'ye çıkarmak hatayı susturur ama tek bir kötü isteğin sunucunun bütün RAM'ini istemesine izin verir. Ağır içe aktarma ve yeniden oluşturma işlerini web isteğinde değil, kendi sınırıyla komut satırında çalıştırmak çoğu durumda daha doğrudur.3. Ayarı doğru yere yazın
| Katman | Örnek | Ne zaman etkili olur? | Dikkat |
|---|---|---|---|
| php.ini | memory_limit = 256M | PHP-FPM veya LSAPI yeniden yüklenince | O PHP sürümünü kullanan bütün siteleri etkiler |
| FPM havuzu (pool) | php_admin_value[memory_limit] = 256M | PHP-FPM yeniden yüklenince | php_admin_value ile verilen değeri ne ini_set() ne .user.ini değiştirebilir; php_value ile verilen değer değiştirilebilir |
| .user.ini | memory_limit = 256M | user_ini.cache_ttl süresi dolunca (varsayılan 300 saniye) | Yalnızca CGI/FastCGI ve PHP-FPM okur; LiteSpeed varsayılan olarak okumaz |
| .htaccess | php_value memory_limit 256M | Bir sonraki istekte | Yalnızca Apache mod_php, LiteSpeed ve CloudLinux mod_lsapi; Apache ile PHP-FPM kullanan sunucuda 500 hatası verir |
| Kod | ini_set('memory_limit', '512M'); | O istek boyunca | Değer kilitliyse ya da mevcut kullanımın altındaysa reddedilir |
| Komut satırı | php -d memory_limit=1G betik.php | Yalnızca o komutta | Cron ve tek seferlik işler için en temiz yol |
Debian veya Ubuntu üzerinde PHP 8.4 için örnek dosyalar ve yeniden yükleme komutları:
INI:
; /etc/php/8.4/fpm/php.ini (sunucu geneli)
memory_limit = 256M
; /etc/php/8.4/fpm/pool.d/www.conf (yalnızca bu havuz)
php_admin_value[memory_limit] = 256M
Bash:
sudo php-fpm8.4 -t && sudo systemctl reload php8.4-fpm # Debian/Ubuntu
sudo php-fpm -t && sudo systemctl reload php-fpm # AlmaLinux, Rocky Linux, RHEL
php-fpm -t yapılandırmayı sınar; yazım hatası varsa servisi yeniden yüklemeden önce görürsünüz. Reload, işçi süreçlerini yeniden başlatır ve php.ini dahil yapılandırmayı yeniden okur. php-fpm.conf'taki process_control_timeout varsayılan olarak 0 olduğundan o anda süren istekler yarıda kesilebilir; uzun isteklerin tamamlanması gerekiyorsa bu değeri birkaç saniyeye (örneğin 10s) çıkarın.
4. Kontrol paneli kullanıyorsanız
- cPanel: Software → MultiPHP INI Editor ekranında Basic Mode'dan alan adını seçip memory_limit değerini değiştirin. cPanel değişikliği direktifi destekleyen dosyalara yazar: sitenin belge kök dizinindeki php.ini, .user.ini ve .htaccess. CloudLinux mod_lsapi kullanılan sunucularda .htaccess dosyasına ayrıca bir lsapi_module bölümü eklenir.
- CloudLinux PHP Selector: "Select PHP Version" ekranının seçenekler (Options) bölümünde memory_limit bulunur. Selector alt-php sürümlerini yönettiği için sitenin gerçekten o sürümle çalıştığını yukarıdaki kontrol dosyasıyla doğrulayın.
- DirectAdmin: CustomBuild, PHP 8.4 için /usr/local/php84/lib/php.ini dosyasını kullanır. Kendi ayarlarınızı /usr/local/php84/lib/php.conf.d/11-custom.ini gibi ayrı bir dosyaya yazmak, ana dosyayı kurcalamaktan daha temizdir. Tek kullanıcıya özel FPM ayarı için Admin Level → Custom HTTPD Configuration → alan adı → php-fpm şablonundaki CUSTOM2 alanına php_value[memory_limit] = 256M yazın (uygulamaların değiştirmesini istemiyorsanız php_admin_value kullanın), ardından systemctl restart php-fpm84 çalıştırın (php.ini ya da php.conf.d dosyalarını düzenledikten sonra da aynı yeniden başlatma gerekir). Dosya yolları ve doğrulama için DirectAdmin'de php.ini düzenleme rehberimize bakabilirsiniz.
- Plesk: memory_limit alanı Domains → alan adı → PHP Settings sayfasında, performans ve güvenlik ayarları (Performance and security settings) bölümündedir.
Değişiklik neden etkili olmuyor?
- Birim yanlış: memory_limit yalnızca K, M ve G harflerini tanır. "256MB" ya da "1GB" yazarsanız PHP 8.2 ve sonrası "Invalid quantity "256MB": unknown multiplier "B"" uyarısını verir. PHP 8.4 testimizde bu uyarının ardından "Failed to set memory limit to 256 bytes" geldi ve sınır 128M'de kaldı. Doğru yazım 256M ve 1G'dir.
- Yanlış php.ini dosyası: Komut satırının ve FPM'in dosyaları ya da iki farklı PHP sürümünün dosyaları karışmıştır. Web'deki kontrol dosyasının gösterdiği yolu düzenleyin.
- Değer kilitli: Barındırma firması sınırı php_admin_value ile koyduysa ini_set(), .user.ini ve WordPress sabitleri işe yaramaz. Değeri yalnızca sunucu yöneticisi değiştirebilir.
- .user.ini önbelleği: PHP, .user.ini dosyalarını user_ini.cache_ttl süresi (varsayılan 300 saniye) boyunca önbellekte tutar. Beş dakika bekleyin ya da PHP-FPM'i yeniden yükleyin.
- LiteSpeed: LiteSpeed'in PHP SAPI'si .user.ini dosyasını yalnızca LSPHP_ENABLE_USER_INI=on ortam değişkeni tanımlıysa okur ve LiteSpeed belgeleri .user.ini kullanılmasını önermiyor. LSPHP ise .htaccess içindeki php_value satırlarını destekler; ayarı oraya ya da panele yazın. LiteSpeed'in ayrık (detached) PHP modunda php.ini değişikliği ancak lsphp yeniden başlayınca geçerli olur; LiteSpeed'in cPanel belgeleri kullanıcının bunu touch ~/.lsphp_restart.txt komutuyla tetikleyebileceğini belirtiyor.
- Apache ile PHP-FPM'de .htaccess: php_value satırı 500 Internal Server Error üretir ve hata günlüğüne "Invalid command 'php_value', perhaps misspelled or defined by a module not included in the server configuration" yazılır. Satırı silip ayarı .user.ini ya da havuz dosyasına taşıyın. cPanel, PHP 8.0 ve sonrasında DSO (mod_php) işleyicisini zaten desteklemiyor.
- PHP 8.5'te max_memory_limit: Yalnızca başlangıçta okunan bu ayar, memory_limit için bir tavan belirler. Daha yüksek bir değer istendiğinde PHP "Failed to set memory_limit to … bytes. Setting to max_memory_limit instead" uyarısıyla tavana iner.
- ini_set() çok geç çağrıldı: Mevcut kullanımın altındaki bir değer reddedilir. Testimizde yaklaşık 54 MiB kullanan bir betikte ini_set('memory_limit', '32M') false döndürdü ve "Failed to set memory limit to 33554432 bytes (Current memory usage is 56639488 bytes)" uyarısını verdi.
- Servis yeniden yüklenmedi: php.ini ve havuz dosyasındaki değişiklikler PHP-FPM yeniden yüklenmeden geçerli olmaz.
memory_limit = -1 neden sunucu çözümü değildir?
-1 değeri istek başına sınırı tamamen kaldırır. Kontrolden çıkan tek bir istek, sunucu takas alanına (swap) düşene ya da Linux çekirdeğinin OOM killer mekanizması devreye girene kadar bellek ister. OOM killer en çok bellek tutan süreçlerden birini öldürür ve bu bazen PHP değil, MySQL olur. Ekim 2026'daki yamalı sürümlerde -1, aşağıda anlatılan sert sınırı da kaldırır. -1'i yalnızca başında durduğunuz tek seferlik komut satırı işleri için ve yalnızca o komuta özel kullanın:
Bash:
php -d memory_limit=-1 tek-seferlik-tasima.php
Ekim 2026 güvenlik sürümleri: memory_limit artık yumuşak sınır
21 Ekim 2026'da yayımlanan PHP 8.5.12, 8.4.27, 8.3.36 ve 8.2.35 sürümleri, php.net'in kimlik doğrulaması gerektirmeden uzaktan kod çalıştırmaya yol açabileceğini belirttiği iki kritik açığı (CVE-2026-109618 ve CVE-2026-109619) kapatıyor. Açıklardan biri bellek yetersizliğinin (out-of-memory) işlenme biçimiyle ilgili olduğu için düzeltme memory_limit'in davranışını da değiştirdi:- memory_limit artık yumuşak bir sınır (soft limit). Sınırı aşan ayırma çoğu durumda tamamlanır ve bildiğimiz "Allowed memory size" hatası, motor güvenli bir noktaya geldiğinde verilir. Mesajın metni değişmedi; sınırın aşıldığı dosya ve satır yine mesajda yer alır. Süreç kısa bir an için memory_limit'in üzerine çıkabilir. register_shutdown_function() ile kaydedilen kapanış fonksiyonları yine çalışır.
- Yeni memory_hard_limit_percent ayarı sert sınırı (hard limit) belirler. Varsayılan, 1G'ye kadarki sınırlarda memory_limit'in iki katı, daha büyük sınırlarda memory_limit + 1G'dir: 128M için 256M, 4G için 5G. memory_limit = -1 ise sert sınır da yoktur.
- Sert sınır aşılırsa süreç hemen 137 çıkış koduyla sonlanır. "Hard memory limit of … bytes exhausted" mesajı yalnızca standart hata akışına (stderr) yazılır; error_log'a düşmez, kapanış fonksiyonları ve yıkıcılar (destructor) çalışmaz. PHP yanıt üretmediği için ziyaretçi web sunucusunun hata sayfasını görür (nginx'te genellikle 502 Bad Gateway). PHP-FPM ana günlüğünde bu durum "child … exited with code 137" olarak görünür. İşçilerin stderr çıktısını günlükte görmek için havuzda catch_workers_output = yes gerekir.
- php.net, 100 değerinin sert sınırı memory_limit'e eşitleyeceği ve bellek hatalarının çoğunu düzgün bir hata yerine sürecin anında sonlandırılmasına çevireceği konusunda uyarıyor. Varsayılanı bırakmak çoğu sunucu için doğru seçimdir.
Sürümünüzün yamalı olup olmadığını en kolay bu ayarın tanınıp tanınmadığından anlarsınız. php.net'e göre ayar tanınmıyorsa kullandığınız sürüm açıklardan etkilenir ve hemen güncellenmelidir:
Bash:
php -r 'var_dump(ini_get("memory_hard_limit_percent"));'
# string(0) "" -> ayar tanınıyor: yamalı sürüm (boş dize varsayılan değerdir)
# bool(false) -> ayar yok: PHP'yi hemen güncelleyin
Bu komut yalnızca komut satırındaki PHP'yi denetler; web tarafı için aynı ini_get() çağrısını geçici kontrol dosyasına ekleyin. Linux dağıtımları düzeltmeyi kendi paket sürümleriyle de yayımlayabildiği için sürüm numarasından çok bu kontrole güvenin. Kapasite planı açısından sonuç şu: varsayılan ayarla tek bir işçi kısa süreliğine memory_limit'in iki katına çıkabilir. Hemen güncelleyemeyenler için php.net'in güvenlik duyurusunda geçici önlemler (örneğin memory_limit = -1 ile birlikte konteyner ya da cgroup bellek sınırı) listeleniyor; php.net bunların tam çözüm olmadığını açıkça belirtiyor. php.net 23 Ekim'de ek bir duyuru da yayımladı: bir sonraki olağan sürüme kadar, olasılığı düşük bir uzaktan kod çalıştırma yolunu (CVE-2026-115560) kapatmak için session.upload_progress.enabled=0 ayarını öneriyor. Güncel öneriler için php.net haber sayfasını takip edin.
Uygulamaya göre notlar
WordPress
WordPress'in iki sabiti var. WP_MEMORY_LIMIT ön yüz istekleri için geçerlidir; varsayılanı tek sitede 40M, Multisite'ta 64M'dir. WP_MAX_MEMORY_LIMIT ise yönetim paneli (wp-admin), WP-Cron ve görsel işleme (GD ve Imagick düzenleyicileri) için kullanılır; varsayılanı 256M, php.ini'deki değer daha yüksekse o değerdir. WordPress sınırı yalnızca yükseltir, hiçbir zaman düşürmez: 128M'lik bir sunucuda 40M'lik varsayılan hiçbir şeyi değiştirmez. Sabitleri wp-config.php dosyasında "That's all, stop editing!" yorum satırından (Türkçe paketlerde bu yorum çevrilmiş olabilir), yani wp-settings.php dosyasının yüklendiği satırdan önce tanımlayın:
PHP:
define( 'WP_MEMORY_LIMIT', '256M' );
define( 'WP_MAX_MEMORY_LIMIT', '512M' );
Sunucu sınırı php_admin_value ile kilitliyse WordPress'in sınırı yükseltme denemesi sessizce başarısız olur; sabiti tanımlamış olsanız da geçerli sınır değişmez. Geçerli değerleri Araçlar → Site sağlığı → Bilgi (Tools → Site Health → Info) sayfasında görürsünüz: Server bölümünde PHP memory limit satırı (yönetim paneli sınırı yükselttiyse yanında ayrıca "PHP memory limit (only for admin screens)" satırı), WordPress Constants bölümünde de WP_MEMORY_LIMIT ve WP_MAX_MEMORY_LIMIT yer alır.
Ziyaretçi yalnızca "There has been a critical error on this website." mesajını (Türkçe kurulumlarda bunun çevirisini) görür; asıl hata yönetici e-postasında ve hata günlüğündedir. Günlüğü geçici olarak açmak için:
PHP:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true ); // wp-content/debug.log
define( 'WP_DEBUG_DISPLAY', false );
İşiniz bitince kapatın; wp-content/debug.log dışarıdan okunabilir olabilir. WordPress 5.1'den beri WP_DEBUG_LOG değerine web kökü dışındaki bir dosya yolu da verebilirsiniz. Sık görülen suçlular büyük CSV içe aktarmaları (görselleriyle birlikte WooCommerce ürünleri), sayfa oluşturucular, WP-Cron'da çalışan yedekleme ve güvenlik taramaları ile telefondan ya da fotoğraf makinesinden yüklenen büyük görsellerdir. Sorumlu eklentiyi canlı siteyi bozmadan bulmak için Health Check & Troubleshooting eklentisinin sorun giderme modu işe yarar; eklentileri yalnızca sizin oturumunuzda kapatır.
XenForo
Büyük içe aktarmaları ve yeniden oluşturma (rebuild) işlerini tarayıcı yerine komut satırından çalıştırın. XenForo belgeleri CLI içe aktarmanın tarayıcıdakinden belirgin biçimde hızlı olduğunu söylüyor. Üstelik CLI işi, web isteklerinin sınırından bağımsız olarak kendi sınırıyla çalışır:
Bash:
cd /var/www/forum
php cmd.php list # sürümünüzdeki komutlar
php -d memory_limit=1G cmd.php xf:import # yönetim panelinde hazırlanan içe aktarmayı çalıştırır
php -d memory_limit=512M cmd.php xf:run-jobs # bekleyen arka plan işlerini çalıştırır
Arama dizini gibi rebuild işlerinin komutları xf-rebuild: ile başlar; tam adlarını list çıktısından kontrol edin. XenForo'nun yakalayabildiği hatalar yönetim panelinde Logs → Server error log altında listelenir; bellek hatası bazen yalnızca PHP veya web sunucusu günlüğünde kalır.
Laravel
Eloquent'te all() ya da get() ile on binlerce kaydı tek seferde yüklemek klasik nedendir. Laravel belgeleri bunun için chunk(), chunkById(), lazy(), lazyById() ve cursor() seçeneklerini sunar. cursor() bellekte tek bir model tutar, ama PDO sürücüsü ham sonuçları kendi tamponunda biriktirdiği için çok büyük tablolarda yine tükenebilir; belgeler bu durumda lazy() yöntemini öneriyor. Kayıtları dolaşırken güncelliyorsanız kimlik sütununa göre ilerleyen lazyById() veya chunkById() kullanın:
PHP:
use App\Models\Order;
Order::where('status', 'paid')
->lazyById(1000)
->each(function (Order $order) {
// siparişleri tek tek işleyin
});
Kuyruk işçileri (queue worker) her işten sonra çatıyı (framework) baştan yüklemez; bellek işten işe birikebilir. İşçiyi belirli bir iş sayısı, süre ya da bellek eşiğinden sonra kapatıp Supervisor'ın yeniden başlatmasına bırakın. --memory değeri megabayt cinsindendir, varsayılanı 128'dir ve aşıldığında işçi düzgünce çıkar:
Bash:
php artisan queue:work --max-jobs=1000 --max-time=3600 --memory=256
Composer
PHP sınırı 1,5 GiB'ın altındaysa (ve -1 değilse) Composer sınırı kendiliğinden 1536M'ye çıkarır. Bu yüzden Composer'daki hata çoğunlukla "Allowed memory size of 1610612736 bytes" diye görünür. Önce sürümü kontrol edin: Composer belgeleri, Composer 1'in çok daha fazla bellek kullandığını belirtiyor ve Composer 2'yi, mümkünse 2.2 veya üstünü öneriyor.
Bash:
composer --version
composer self-update --2 # hâlâ 1.x kullanıyorsanız
COMPOSER_MEMORY_LIMIT=-1 composer update vendor/paket --with-dependencies
php -d memory_limit=-1 "$(command -v composer)" install
COMPOSER_MEMORY_LIMIT ortam değişkeni sınırı yalnızca o çalıştırma için belirler. Canlı sunucuda composer update değil, kilit dosyasından (composer.lock) composer install çalıştırın; bağımlılık çözümlemesini yerel makinede ya da CI hattında yapın. Composer belgeleri, cPanel sunucularında kabuğun "fork bomb" korumasının da bu hataya yol açabildiğini ayrıca not ediyor.
Gerçek nedeni bulmak
Ölçün
memory_get_usage(true) ve memory_get_peak_usage(true), PHP'nin bellek yöneticisinin sistemden ayırdığı belleği verir; php.net'e göre memory_limit tam olarak bu değere karşı uygulanır. Bu değer çoğunlukla 2 MiB'lık bloklar (chunk) hâlinde büyür; tek seferlik çok büyük bir ayırma ise yaklaşık kendi boyutu kadar eklenir. PHP 8.2'den beri memory_reset_peak_usage() ile tepe değeri sıfırlayıp kodun bölümlerini ayrı ayrı ölçebilirsiniz. Hangi isteklerin çok bellek kullandığını görmek için uygulamanın en başında şu kapanış fonksiyonunu kaydedin (WordPress'te bir mu-plugin, Laravel'de bir servis sağlayıcı ya da auto_prepend_file):
PHP:
register_shutdown_function(static function (): void {
$peakMiB = memory_get_peak_usage(true) / 1048576;
if ($peakMiB >= 96) {
error_log(sprintf(
'peak_memory=%.1fMiB target=%s',
$peakMiB,
$_SERVER['REQUEST_URI'] ?? implode(' ', $_SERVER['argv'] ?? [])
));
}
});
Kapanış fonksiyonları "Allowed memory size" hatasından sonra da çalışır; böylece hatayı veren isteğin adresi de günlüğe düşer. Sert sınırın aşıldığı durumda ise çalışmazlar.
PHP-FPM'in iki yerleşik aracı daha var. Erişim günlüğü (access log) her isteğin tepe belleğini %M alanıyla yazabilir. Durum sayfası (status page) ise her işçinin son isteğinde kullandığı belleği (last request memory) ve havuzun işçi sınırına kaç kez dayandığını (max children reached) gösterir:
INI:
; havuz dosyası, örneğin www.conf
access.log = /var/log/php-fpm-$pool.access.log
access.format = "%t \"%m %r%Q%q\" %s %{milli}d ms %{mega}M MB %C%%"
pm.status_path = /fpm-status
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm-$pool.slow.log
Durum sayfasını web sunucusunda yalnızca yerel adrese açın. Yavaş istek günlüğü (slowlog), belirlenen süreyi aşan isteklerin PHP yığın izini yazar ve ağır uç noktaları (endpoint) bulmanın kolay bir yoludur.
Profil çıkarın
- Xdebug profiler: xdebug.mode=profile ve xdebug.start_with_request=trigger ayarlarıyla yalnızca XDEBUG_TRIGGER gönderdiğiniz isteklerin profilini alırsınız. Xdebug belgelerine göre profiler, zamanın yanında hangi fonksiyonların bellek kullanımını artırdığını da kaydeder. Çıktı cachegrind.out.* dosyalarıdır ve KCachegrind, QCachegrind ya da PhpStorm ile açılır. Xdebug'ın yükü yüksektir; canlı sunucuda değil, verinin bir kopyasıyla test ortamında kullanın.
- memprof: Bu eklentinin dump_on_limit modu, tam bu hata oluştuğu anda bellek profilini /tmp altında memprof.callgrind.* dosyasına yazar. Komut satırında MEMPROF_PROFILE=dump_on_limit php betik.php biçiminde çalıştırılır.
- Günlükleri eşleştirin: Hata günlüğündeki zamanı erişim günlüğündeki adresle, yavaş istek günlüğüyle ve cron zamanlamasıyla karşılaştırın. Hata her gece aynı saatte geliyorsa suçlu büyük olasılıkla bir yedekleme ya da senkronizasyon işidir.
Klasik nedenler ve çözümleri
Aynı veriyi farklı yöntemlerle işlemenin farkını görmek için PHP 8.4'te ölçüm yaptık (memory_get_peak_usage(true)). İlk dört satır 52,7 MB'lık, bir milyon satırlık bir CSV dosyasına; son iki satır bir milyon küçük kayda (kimlik ve e-posta) aittir:| Yöntem | Tepe bellek |
|---|---|
| file() ile bütün satırları diziye almak | 146,3 MiB |
| file() sonucunu array_map ve str_getcsv() ile ayrıştırmak | 506,0 MiB |
| fgetcsv() döngüsü | 4,0 MiB |
| SplFileObject (READ_CSV) | 4,0 MiB |
| Kayıtları önce bir dizide toplayıp sonra dolaşmak | 424,0 MiB |
| Aynı kayıtları generator (yield) ile tek tek üretmek | 4,0 MiB |
128M'lik sınırla ilk iki yöntem tam da bu hatayla çöktü. 52,7 MB'lık dosya PHP dizilerine dönüşünce yaklaşık on kat yer kapladı; akış (stream) kullanan yöntemlerin belleği ise dosya boyutuna bağlı değildir. Büyük bir diziyi bir kerede kurmak yerine üreteç (generator) kullanın:
PHP:
function satirlar(string $yol): Generator
{
$h = fopen($yol, 'r');
while (($row = fgetcsv($h, null, ',', '"', '')) !== false) {
yield $row;
}
fclose($h);
}
foreach (satirlar('/veri/siparisler.csv') as $row) {
// bellekte yalnızca o anki satır var
}
PHP 8.4'te fgetcsv() ve str_getcsv() çağrılarında escape parametresini vermezseniz her çağrıda kullanımdan kaldırma (deprecation) uyarısı alırsınız; örnekteki son '' bu yüzden orada.
Veritabanı: mysqlnd ile varsayılan tamponlu (buffered) sorguda sonuç kümesinin tamamı PHP belleğine alınır ve php.net'e göre memory_limit'e sayılır; satırları fetch() ile tek tek dolaşsanız bile. Milyonlarca satır için iki yol var: tamponsuz (unbuffered) sorgu ya da anahtar tabanlı sayfalama (keyset pagination).
PHP:
// Tamponsuz sorgu: sonuç bitene kadar aynı bağlantıda başka sorgu çalıştıramazsınız.
// PHP 8.3 ve öncesinde sabit PDO::MYSQL_ATTR_USE_BUFFERED_QUERY (PHP 8.5'te kullanımdan kaldırıldı).
$pdo->setAttribute(Pdo\Mysql::ATTR_USE_BUFFERED_QUERY, false);
foreach ($pdo->query('SELECT id, email FROM users') as $row) {
// ...
}
// Anahtar tabanlı sayfalama: her turda 1000 satır, OFFSET yok
$stmt = $pdo->prepare('SELECT id, email FROM users WHERE id > ? ORDER BY id LIMIT 1000');
$lastId = 0;
do {
$stmt->execute([$lastId]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
// ...
$lastId = $row['id'];
}
} while (count($rows) === 1000);
Görseller: GD'de gerçek renkli (truecolor) bir görsel piksel başına 4 bayt ister. 6000 × 4000 piksellik bir fotoğraf 96.000.000 bayt, yani yaklaşık 92 MiB eder. Testimizde imagecreatetruecolor(6000, 4000) PHP'nin sayacını 98.345.008 bayt artırdı, çünkü her satır 4 KiB'lık sayfalara yuvarlanıyor. JPEG dosyası yalnızca 375 KB'tı: gereken bellek dosya boyutuna değil, piksel sayısına bağlıdır. Görselden 1600 piksellik bir kopya üretince tepe 111 MB'ı geçti; uygulamanın kendi kullanımı da eklenince 128M yetmeyebilir. PHP ile gelen (bundled) GD bu belleği memory_limit'e sayar. Debian/Ubuntu ve Fedora gibi dağıtımların paketleri ise GD'yi sistemdeki libgd ile derler; bu durumda piksel belleği memory_limit'e görünmez ama RAM'i yine tüketir ve LVE, konteyner ya da OOM sınırlarına takılabilir. Hangisini kullandığınızı phpinfo() çıktısının GD bölümünden anlarsınız: "GD Version" satırında "bundled (2.1.0 compatible)" yazıyorsa PHP ile gelen sürümdür; "GD headers Version" ve "GD library Version" satırları ise sistem kütüphanesini gösterir.
Imagick ise belleği ImageMagick üzerinden ayırır ve memory_limit bunu hiç görmez. memory_limit=32M ile yaptığımız testte aynı görsel yüklenirken PHP'nin sayacı yerinden oynamadı, süreç ise yaklaşık 338 MB büyüdü (ImageMagick 7, Q16-HDRI derlemesi). ImageMagick'i policy.xml ya da Imagick::setResourceLimit() ile sınırlayın. Bu sınırlara takıldığınızda "Allowed memory size" değil, "cache resources exhausted" veya "width or height exceeds limit" gibi ImageMagick hataları alırsınız. Yükleme tarafında yalnızca dosya boyutunu değil, piksel boyutunu da sınırlayın ve küçük resim (thumbnail) üretimini kuyruğa alın. Yükleme sınırlarının bütün katmanlarını "Uploaded File Is Too Large" yazımızda anlattık.
Özyineleme: 262144 imzasını görüyorsanız kendini çağıran fonksiyonu arayın. Veriden kaynaklanan tipik örnek, kategori ya da menü ağacında bir kaydın kendi alt kaydını üst kayıt (parent) olarak göstermesidir. Ziyaret ettiğiniz kimlikleri tutup aynı kimliğe ikinci kez girmeyin ve bir derinlik sınırı koyun. Özyineleme array_map() gibi dahili bir fonksiyon üzerinden geçiyorsa PHP 8.3 ve sonrası bellek bitmeden önce "Maximum call stack size of … reached. Infinite recursion?" hatasını verebilir; testimizde PHP 8.4 tam olarak bunu yaptı.
Uzun süre çalışan süreçler: Kuyruk işçileri, daemon'lar ve Octane, Swoole ya da RoadRunner gibi kalıcı çalışma ortamları belleği istekler arasında korur. Statik önbellekler, Doctrine'in kimlik haritası (identity map) ve günlük tamponları her işte biraz daha büyür. Süreci düzenli aralıklarla yeniden başlatın: Laravel'de --max-jobs, --max-time ve --memory; Symfony Messenger'da messenger:consume --limit, --memory-limit ve --time-limit; PHP-FPM'de pm.max_requests. www.conf'taki açıklamaya göre pm.max_requests, üçüncü taraf kütüphanelerdeki bellek sızıntılarına karşı kullanılabilir. Doctrine ile toplu işlemde her partiden sonra EntityManager üzerinde clear() çağırın. PHP 8.0'dan beri GD görselleri nesnedir ve imagedestroy() hiçbir şey yapmaz (PHP 8.5'te kullanımdan kaldırıldı); belleği bırakmak için değişkeni unset() edin. Kalıcı çalışan PHP süreçlerinde bellek disiplinine 2026'da PHP backend yazımızda da değinmiştik.
Aynı hata değil: benzer belirtileri ayırt etmek
| Belirti veya günlük satırı | Ne oldu? | Ne yapmalı? |
|---|---|---|
| Allowed memory size of … exhausted | PHP'nin istek başına sınırı (memory_limit) doldu | Nedeni bulun, sınırı ölçerek artırın |
| Hard memory limit of … exhausted (yalnızca stderr), çıkış kodu 137 | Yamalı sürümlerde istek sert sınırı aştı; çoğunlukla tek seferlik dev bir ayırma | Aynı yaklaşım; büyük dizeyi veya diziyi bulun |
| Out of memory (allocated …) (tried to allocate …) ya da yalnızca "Out of memory" | İşletim sistemi bellek vermedi; memory_limit'e ulaşılmadı. Yamalı sürümler yalnızca stderr'e "Out of memory" yazıp çıkar. | RAM, swap, ulimit ve konteyner sınırlarını inceleyin |
| FPM: exited on signal 9 (SIGKILL); çekirdek: Out of memory: Killed process … ya da Memory cgroup out of memory | Çekirdeğin veya konteynerin OOM killer'ı süreci öldürdü | pm.max_children ve memory_limit'i RAM'e göre küçültün ya da RAM ekleyin |
| FPM: server reached pm.max_children setting (N), consider raising it; önce yavaşlama, sonra 502, 503 veya 504 | Eşzamanlı istekler işçi sayısını aştı; bu bir bellek hatası değil | max_children'ı RAM ve ortalama işçi belleğine göre ayarlayın, yavaş istekleri hızlandırın |
| CloudLinux: 508 Resource Limit Is Reached ya da 500/503 ile birlikte PMEM hataları | Hesap düzeyindeki LVE sınırı: giriş süreçleri (Entry Processes, EP) veya fiziksel bellek (PMEM) | Paneldeki kaynak kullanımı sayfasına bakın, paketi veya sınırı gözden geçirin |
| ImagickException: cache resources exhausted | ImageMagick kendi kaynak sınırına takıldı | policy.xml, setResourceLimit(), görsel boyutu |
| Maximum call stack size of … reached. Infinite recursion? | PHP 8.3 ve sonrasındaki C yığını koruması | Özyinelemeyi düzeltin |
Hangisiyle karşı karşıya olduğunuzu günlüklerden ayırabilirsiniz (Debian/Ubuntu, nginx ve PHP 8.4 örneği):
Bash:
# PHP hata mesajları; nginx bunları "FastCGI sent in stderr" satırlarına yazar
sudo grep -h "Allowed memory size" /var/log/nginx/error.log | tail -5
# PHP-FPM ana günlüğü: işçi çıkışları ve max_children uyarıları
sudo grep -E "exited (with code|on signal)|max_children" /var/log/php8.4-fpm.log | tail
# Çekirdeğin OOM killer kayıtları
sudo journalctl -k --since "1 hour ago" | grep -i -E "out of memory|killed process"
# PHP-FPM işçilerinin ortalama belleği (RSS)
ps -eo rss=,comm= | awk '/php-fpm/ {s+=$1; n++} END {if (n) printf "%d süreç, ortalama %.0f MB\n", n, s/n/1024}'
Apache ile PHP-FPM kullanan sunucularda aynı PHP mesajı "AH01071: Got error 'PHP message: …'" biçiminde Apache hata günlüğüne düşer. AlmaLinux ve RHEL ailesinde varsayılan havuzun PHP hataları /var/log/php-fpm/www-error.log, FPM'in kendi kayıtları /var/log/php-fpm/error.log dosyasındadır.
FPM günlüğündeki iki çıkış satırı farklı şeyler anlatır. "exited with code 137" PHP'nin kendi sert sınırı (yamalı sürümler), "exited on signal 9 (SIGKILL)" ise çekirdeğin ya da konteynerin müdahalesidir:
Kod:
[27-Oct-2026 03:00:12] WARNING: [pool www] child 21731 exited with code 137 after 4.120335 seconds from start
[27-Oct-2026 03:00:41] WARNING: [pool www] child 21802 exited on signal 9 (SIGKILL) after 61.904211 seconds from start
pm.max_children değerini kabaca "PHP'ye ayırabileceğiniz RAM ÷ ortalama işçi belleği" ile hesaplayın: 3 GB ve işçi başına 75 MB için yaklaşık 40. RSS ortak OPcache belleğini de içerdiği için bu tahmin temkinlidir. Paylaşımlı barındırmada LVE sınırlarına sürekli takılıyorsanız php.ini, FPM havuzu ve RAM üzerinde tam denetim veren bir sanal sunucu daha uygun olabilir; AIOR'un sanal sunucu paketleri bu seçeneklerden biri.
Önleme kontrol listesi
- Her site ya da uygulama için ayrı FPM havuzu kullanın ve sınırı havuza özel verin.
- Web istekleri için makul bir sınır seçin (örneğin 256M); daha fazlasını isteyen işleri komut satırına ya da kuyruğa taşıyın.
- Tepe belleği izleyin: FPM erişim günlüğünde %M, durum sayfası ya da kapanış fonksiyonu; eşik aşıldığında uyarı alın.
- Yüklemelerde dosya boyutunun yanında piksel boyutunu da sınırlayın; küçük resimleri kuyrukta üretin.
- Uzun işleri parçalara bölün (batch) ve işçileri düzenli olarak yeniden başlatın.
- PHP'yi, eklentileri ve bağımlılıkları güncel tutun; Ekim 2026 güvenlik sürümlerini (8.5.12, 8.4.27, 8.3.36, 8.2.35) atlamayın.
- Composer 2 kullanın; update yerelde, install sunucuda.
- Her yayına almadan (deploy) sonra yönetim panelini, cron işlerini ve içe aktarmaları kısa bir duman testinden (smoke test) geçirin.
Sık sorulan sorular
memory_limit kaç olmalı?Tek doğru sayı yok. Tepe kullanımı ölçün ve üzerine pay bırakın; pek çok site için 256M makul bir başlangıçtır, ölçümünüz daha azını gösteriyorsa düşük tutun. En kötü durumu görmek için pm.max_children × memory_limit hesabını da yapın ve yamalı sürümlerde tek bir işçinin kısa süreliğine bu değerin iki katına çıkabileceğini unutmayın.
Değişikliğim neden uygulanmıyor?
En sık nedenler yanlış php.ini dosyası, "256MB" gibi geçersiz bir birim, php_admin_value ile kilitlenmiş değer, .user.ini önbelleği, .user.ini'yi okumayan LiteSpeed ve yeniden yüklenmeyen PHP-FPM'dir. Geçici kontrol dosyası ya da phpinfo() size gerçekte geçerli olan değeri ve dosyayı gösterir.
memory_limit = -1 güvenli mi?
Yalnızca başında durduğunuz tek seferlik komut satırı işlerinde. Web tarafında sınırsız bellek, tek bir hatalı isteğin bütün sunucuyu etkileyebilmesi demektir. Ekim 2026 güvenlik duyurusundaki -1 önerisi, hemen güncelleyemeyen sunucular için cgroup sınırıyla birlikte önerilen geçici bir önlemdir; kalıcı çözüm güncellemektir.
Hata neden yalnızca yönetim panelinde ya da cron'da çıkıyor?
Ağır işler (güncellemeler, içe aktarmalar, yedeklemeler) oralarda çalışır. WordPress yönetim panelinde, WP-Cron'da ve görsel işlemede sınırı WP_MAX_MEMORY_LIMIT değerine yükseltir; hata yine de çıkıyorsa ya bu değer yetmiyordur ya da sunucu sınırı kilitlidir. Sistem cron'u PHP'yi komut satırından çalıştırdığı için web'den farklı bir php.ini kullanır.
Sunucuya RAM eklemek sorunu çözer mi?
"Allowed memory size" hatasını tek başına çözmez, çünkü sınır süreç başınadır ve RAM'den bağımsızdır. Ek RAM; OOM killer, LVE ya da konteyner sınırı gibi sistem düzeyindeki sorunlarda ve daha fazla işçi çalıştırmak istediğinizde işe yarar. Bellek sızdıran bir işte ise yalnızca çöküşü erteler.
Sonuç ve sonraki adımlar
Bu hata, PHP'nin tek bir isteği bütün sunucuyu etkilemeden önce durdurmasıdır. Mesajı okuyun, kodu hangi PHP'nin çalıştırdığını bulun, sınırı doğru katmanda bir basamak artırın; sonra ölçün ve belleği tüketen dizi, sorgu, görsel ya da süreci düzeltin. Sıradaki adımlar:- Bugün: memory_hard_limit_percent kontrolünü hem komut satırında hem web'de yapın; ayar yoksa PHP'yi güncelleyin.
- Bu hafta: FPM erişim günlüğüne %M ekleyin ya da kapanış fonksiyonuyla tepe belleği kaydedin ve en çok bellek kullanan beş adresi çıkarın.
- Sonra: o adreslerdeki işleri akış, sayfalama ve kuyrukla yeniden yazın; ağır işleri komut satırına taşıyın.
"Allowed memory size of 134217728 bytes exhausted" means one PHP script used up the memory it was allowed to have (memory_limit). It shows up while WordPress updates a plugin, during a XenForo import, inside a Laravel queue job or halfway through composer update. Raising the limit usually brings the site back within minutes. If you never find out where the memory went, though, the error returns a few weeks later with a bigger number in it. This guide first shows how to read the message and raise the limit at the right layer, by the right amount, then how to find and fix the code that eats the memory. We reproduced the error output and the measurements below with PHP 8.4 on 27 October 2026. A separate section covers what changed with the PHP security releases of 21 October 2026.
What the error message tells you
This is the message our test script produced (only the path was changed):
Kod:
PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 20480 bytes) in /var/www/example/import.php on line 4
It has three parts:
- 134217728 bytes: the limit itself. 134,217,728 = 128 × 1024 × 1024, so memory_limit = 128M. That is PHP's built-in default when no php.ini is loaded, and php.ini-production uses the same value.
- tried to allocate 20480 bytes: the size of the last allocation, the one that no longer fit. It is usually small and says little about the cause; it is the last straw, not the load that came before it.
- in … on line 4: where memory ran out. The code that filled memory can be somewhere else entirely. An array that grows inside a loop only shows up on the line that adds the final element.
To translate the byte count back into a setting:
| Number in the message | Setting | Where you typically see it |
|---|---|---|
| 2097152 | 2M | Almost always a typo. On PHP 7.x an invalid value such as "256MB" silently became a 2 MiB limit. |
| 41943040 | 40M | WordPress's default WP_MEMORY_LIMIT on a single site (only applied when the server limit is lower) |
| 67108864 | 64M | WordPress Multisite's default WP_MEMORY_LIMIT |
| 134217728 | 128M | PHP's default |
| 268435456 | 256M | WordPress's default WP_MAX_MEMORY_LIMIT for wp-admin, WP-Cron and image processing |
| 536870912 | 512M | A limit someone raised by hand |
| 1073741824 | 1G | A limit raised by hand, usually for CLI jobs |
| 1610612736 | 1536M | Composer's own raised limit (1.5 GiB) |
The "tried to allocate" value can still be a useful hint:
- A steady 262144 bytes (256 KiB) points to runaway recursion. PHP allocates the frames of function calls in 256 KiB pages, and our test function that kept calling itself produced exactly this message.
- Hundreds of megabytes in one go mean a single huge string or array was being built. One 200 MiB str_repeat() call in our test reported "tried to allocate 209715232 bytes". Reading a whole file into memory or encoding a huge JSON document leaves the same trace.
- Small values such as 4096, 8192 or 20480 mean memory filled up gradually. The culprit is whatever piled up before, not the line in the message.
The quick, safe fix: raise the limit at the right layer
1. Find out which PHP runs the code
One server can run several PHP versions, each with its own configuration, and the command line (CLI) reads a different php.ini from the PHP-FPM that serves your website. Start with the CLI:
Bash:
php -v
php --ini
php -i | grep -E '^memory_limit'
php -r 'echo ini_get("memory_limit"), PHP_EOL;'
php --ini prints the loaded php.ini (Loaded Configuration File) and the directory that extra .ini files are scanned from. In the php -i line "memory_limit => 128M => 128M", the first value is the local (effective) one and the second the master value. Debian and Ubuntu packages ship a separate php.ini for the CLI with memory_limit = -1, while the FPM php.ini keeps 128M. That is why a script can run fine in a terminal and die on the web, and why a job started from the system crontab runs with completely different settings from your web requests.
For the web side, put a temporary file with a hard-to-guess name in the site's document root, open it in a browser and delete it as soon as you have read the result:
PHP:
<?php
// tmp-check-7f3a.php : delete after reading
echo php_sapi_name(), ' | ', PHP_VERSION, ' | ', ini_get('memory_limit'), ' | ', php_ini_loaded_file() ?: 'no php.ini';
The first value is the server API (SAPI): fpm-fcgi (PHP-FPM), cgi-fcgi (CGI/FastCGI), apache2handler (Apache mod_php) or litespeed (LiteSpeed LSAPI). phpinfo() shows the same details. If its Local Value and Master Value columns differ for memory_limit, a .user.ini, an .htaccess line or code is overriding the master setting. phpinfo() reveals a lot about the server, so remove that page too when you are done.
2. Decide how much to raise it
Go up one step (128M to 256M), test the site and measure peak usage as shown in the "Finding the real cause" section below. Jumping straight to 2G silences the error but lets one bad request ask for all of the server's RAM. Heavy imports and rebuilds are usually better off on the command line with their own limit than inside a web request.3. Put the setting in the right place
| Layer | Example | When it takes effect | Watch out for |
|---|---|---|---|
| php.ini | memory_limit = 256M | After PHP-FPM or LSAPI reloads | Affects every site on that PHP version |
| FPM pool | php_admin_value[memory_limit] = 256M | After PHP-FPM reloads | Neither ini_set() nor .user.ini can override php_admin_value; a php_value setting can be overridden |
| .user.ini | memory_limit = 256M | Once user_ini.cache_ttl expires (300 seconds by default) | Read only by CGI/FastCGI and PHP-FPM; LiteSpeed ignores it by default |
| .htaccess | php_value memory_limit 256M | On the next request | Apache mod_php, LiteSpeed and CloudLinux mod_lsapi only; on Apache with PHP-FPM it causes a 500 error |
| Code | ini_set('memory_limit', '512M'); | For that request | Refused when the value is locked or below current usage |
| Command line | php -d memory_limit=1G script.php | For that command only | The cleanest option for cron and one-off jobs |
Example files and reload commands for PHP 8.4 on Debian or Ubuntu:
INI:
; /etc/php/8.4/fpm/php.ini (server-wide)
memory_limit = 256M
; /etc/php/8.4/fpm/pool.d/www.conf (this pool only)
php_admin_value[memory_limit] = 256M
Bash:
sudo php-fpm8.4 -t && sudo systemctl reload php8.4-fpm # Debian/Ubuntu
sudo php-fpm -t && sudo systemctl reload php-fpm # AlmaLinux, Rocky Linux, RHEL
php-fpm -t tests the configuration, so a typo shows up before the reload rather than as an outage. A reload restarts the workers and rereads the configuration, including php.ini. With the default process_control_timeout = 0 in php-fpm.conf, requests that are still running at that moment can be cut off; if long requests must be allowed to finish, set it to a few seconds (for example 10s).
4. If you use a control panel
- cPanel: in Software → MultiPHP INI Editor, pick the domain in Basic Mode and change memory_limit. cPanel writes the change to whichever files support the directive: php.ini, .user.ini and .htaccess in the site's document root. On servers with CloudLinux mod_lsapi it also adds an lsapi_module section to .htaccess.
- CloudLinux PHP Selector: memory_limit is on the Options tab of the "Select PHP Version" screen. The Selector manages alt-php versions, so confirm with the check file above that the site really runs on the version you edited.
- DirectAdmin: CustomBuild uses /usr/local/php84/lib/php.ini for PHP 8.4. Keeping your own settings in a separate file such as /usr/local/php84/lib/php.conf.d/11-custom.ini is cleaner than editing the main file. For a per-user FPM setting, go to Admin Level → Custom HTTPD Configuration → the domain → the php-fpm template, add php_value[memory_limit] = 256M to the CUSTOM2 box (use php_admin_value if applications must not change it), then run systemctl restart php-fpm84 (the same restart applies after editing php.ini or a php.conf.d file). Our guide to editing php.ini on DirectAdmin has the file paths and checks.
- Plesk: memory_limit is under Domains → the domain → PHP Settings, in the performance and security settings section.
Why your change does not apply
- Wrong unit: memory_limit only understands K, M and G. Write "256MB" or "1GB" and PHP 8.2 and later warn "Invalid quantity "256MB": unknown multiplier "B"". In our PHP 8.4 test the next line was "Failed to set memory limit to 256 bytes" and the limit stayed at 128M. Use 256M or 1G.
- Wrong php.ini: the CLI and FPM files, or two PHP versions, got mixed up. Edit the path the web check file reported.
- Locked value: if the host set the limit with php_admin_value, ini_set(), .user.ini and the WordPress constants do nothing. Only the server administrator can change it.
- .user.ini cache: PHP caches .user.ini files for user_ini.cache_ttl seconds (300 by default). Wait five minutes or reload PHP-FPM.
- LiteSpeed: LiteSpeed's PHP SAPI reads .user.ini only when the LSPHP_ENABLE_USER_INI=on environment variable is set, and LiteSpeed's documentation advises against using .user.ini. LSPHP does support php_value lines in .htaccess, so put the setting there or in the panel. In LiteSpeed's detached PHP mode a php.ini change applies only after lsphp restarts; LiteSpeed's cPanel documentation gives touch ~/.lsphp_restart.txt as the way for a user to trigger that.
- .htaccess on Apache with PHP-FPM: a php_value line produces a 500 Internal Server Error and the error log says "Invalid command 'php_value', perhaps misspelled or defined by a module not included in the server configuration". Delete the line and move the setting to .user.ini or the pool file. cPanel no longer supports the DSO (mod_php) handler for PHP 8.0 and later anyway.
- max_memory_limit on PHP 8.5: this startup-only setting caps memory_limit. Ask for more and PHP warns "Failed to set memory_limit to … bytes. Setting to max_memory_limit instead" and uses the cap.
- ini_set() called too late: a value below current usage is refused. In our test, a script using about 54 MiB got false back from ini_set('memory_limit', '32M') plus the warning "Failed to set memory limit to 33554432 bytes (Current memory usage is 56639488 bytes)".
- No reload: changes to php.ini and pool files take effect only after PHP-FPM reloads.
Why memory_limit = -1 is not a server fix
-1 removes the per-request cap completely. One runaway request can then keep allocating until the server starts swapping or the Linux kernel's OOM killer steps in. The OOM killer picks one of the processes holding the most memory, and sometimes that is MySQL rather than PHP. On the patched October 2026 releases, -1 also removes the hard limit described below. Keep -1 for one-off CLI jobs you are watching, and set it for that command only:
Bash:
php -d memory_limit=-1 one-off-migration.php
October 2026 security releases: memory_limit is now a soft limit
PHP 8.5.12, 8.4.27, 8.3.36 and 8.2.35, released on 21 October 2026, fix two critical vulnerabilities (CVE-2026-109618 and CVE-2026-109619) that php.net says can allow unauthenticated remote code execution. One of them sits in out-of-memory handling, so the fix also changed how memory_limit behaves:- memory_limit is now a soft limit. An allocation that crosses it usually completes, and the familiar "Allowed memory size" error is raised once the engine reaches a safe point. The message text is unchanged and still names the file and line where the limit was crossed. A process can briefly sit above memory_limit. Shutdown functions registered with register_shutdown_function() still run.
- The new memory_hard_limit_percent setting defines the hard limit. By default it is twice memory_limit for limits up to 1G, and memory_limit plus 1G above that: 256M for 128M, 5G for 4G. With memory_limit = -1 there is no hard limit either.
- Crossing the hard limit ends the process immediately with exit status 137. The message "Hard memory limit of … bytes exhausted" goes to standard error only; it never reaches error_log, and shutdown functions and destructors do not run. PHP sends no response, so the visitor gets an error page from the web server (with nginx usually 502 Bad Gateway). In the PHP-FPM master log this appears as "child … exited with code 137". To see the workers' stderr in the log, the pool needs catch_workers_output = yes.
- php.net warns that a value of 100 makes the hard limit equal to memory_limit and turns most out-of-memory conditions into an immediate kill instead of a proper error. Leaving the default is the right choice for most servers.
The quickest way to tell whether your build is patched is to check whether PHP knows the new setting. According to php.net, if it does not, your version is affected and must be updated immediately:
Bash:
php -r 'var_dump(ini_get("memory_hard_limit_percent"));'
# string(0) "" -> the setting exists: patched build (empty means default)
# bool(false) -> unknown setting: update PHP now
That only checks the CLI binary; add the same ini_get() call to the temporary web check file for FPM. Distributions can ship the fix under their own package versions, so trust this check over the version number. For capacity planning, the consequence is that with default settings a single worker can briefly reach twice its memory_limit. For servers that cannot update yet, the php.net advisory lists temporary measures, such as memory_limit = -1 combined with a container or cgroup memory limit, and is clear that they are not a complete fix. php.net added a follow-up on 23 October: until the next regular release it suggests session.upload_progress.enabled=0 to close an unlikely remaining remote code execution path (CVE-2026-115560). Check the php.net news page for the current guidance.
Notes per application
WordPress
WordPress has two constants. WP_MEMORY_LIMIT applies to front-end requests; it defaults to 40M on a single site and 64M on Multisite. WP_MAX_MEMORY_LIMIT applies to wp-admin, WP-Cron and image processing (the GD and Imagick editors); it defaults to 256M, or to the php.ini value if that is higher. WordPress only ever raises the limit and never lowers it, so on a server with 128M the 40M default changes nothing. Define the constants in wp-config.php above the "That's all, stop editing!" line, which means before wp-settings.php is loaded:
PHP:
define( 'WP_MEMORY_LIMIT', '256M' );
define( 'WP_MAX_MEMORY_LIMIT', '512M' );
If the host locked the limit with php_admin_value, WordPress's attempt to raise it fails silently and the effective limit stays put even though your constant is defined. Tools → Site Health → Info shows what is really in effect: "PHP memory limit" in the Server section (when wp-admin has raised the limit, a second line, "PHP memory limit (only for admin screens)", appears next to it), plus WP_MEMORY_LIMIT and WP_MAX_MEMORY_LIMIT in the WordPress Constants section.
Visitors only see "There has been a critical error on this website."; the real message is in the admin recovery email and in the error log. To switch logging on temporarily:
PHP:
define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true ); // wp-content/debug.log
define( 'WP_DEBUG_DISPLAY', false );
Turn it off again afterwards, because wp-content/debug.log may be readable from the web. Since WordPress 5.1, WP_DEBUG_LOG also accepts a file path outside the web root. Usual suspects are big CSV imports (WooCommerce products with images), page builders, backup and security scans running in WP-Cron, and large photos uploaded straight from phones or cameras. To find the responsible plugin without breaking the live site, use the troubleshooting mode of the Health Check & Troubleshooting plugin; it disables plugins for your session only.
XenForo
Run big imports and rebuilds from the command line rather than the browser. XenForo's documentation says the CLI import is significantly faster than the browser method, and a CLI job runs with its own limit, independent of your web requests:
Bash:
cd /var/www/forum
php cmd.php list # commands available in your version
php -d memory_limit=1G cmd.php xf:import # runs the import configured in the admin panel
php -d memory_limit=512M cmd.php xf:run-jobs # runs pending background jobs
Rebuild commands, such as the one for the search index, start with xf-rebuild:; check the exact names in the list output. Errors XenForo manages to catch are listed in the admin control panel under Logs → Server error log, but a memory error sometimes only lands in the PHP or web server log.
Laravel
Loading tens of thousands of Eloquent models with all() or get() is the classic cause. The Laravel docs offer chunk(), chunkById(), lazy(), lazyById() and cursor(). cursor() keeps only one model in memory, but PHP's PDO driver still buffers the raw results, so it can run out on very large tables; the docs recommend lazy() for that case. If you update records while iterating, use lazyById() or chunkById(), which page by the id column:
PHP:
use App\Models\Order;
Order::where('status', 'paid')
->lazyById(1000)
->each(function (Order $order) {
// process one order at a time
});
Queue workers do not boot the framework again for every job, so memory can accumulate from job to job. Let a worker exit after a number of jobs, an amount of time or a memory threshold, and let Supervisor restart it. --memory is in megabytes, defaults to 128, and makes the worker exit cleanly once exceeded:
Bash:
php artisan queue:work --max-jobs=1000 --max-time=3600 --memory=256
Composer
If PHP's limit is below 1.5 GiB (and not -1), Composer raises it to 1536M by itself. That is why Composer's failures usually read "Allowed memory size of 1610612736 bytes". Check the version first: the Composer docs say Composer 1 used much more memory and recommend Composer 2, ideally 2.2 or later.
Bash:
composer --version
composer self-update --2 # if you are still on 1.x
COMPOSER_MEMORY_LIMIT=-1 composer update vendor/package --with-dependencies
php -d memory_limit=-1 "$(command -v composer)" install
The COMPOSER_MEMORY_LIMIT environment variable sets the limit for that run only. On production servers run composer install from composer.lock rather than composer update, and do the dependency resolution on your workstation or in CI. The Composer docs also note that cPanel's shell fork bomb protection can trigger this error.
Finding the real cause
Measure
memory_get_usage(true) and memory_get_peak_usage(true) report the memory that PHP's memory manager has reserved from the system, and php.net states that memory_limit is enforced against exactly that number. It mostly grows in 2 MiB chunks; a single very large allocation is added at roughly its own size. Since PHP 8.2, memory_reset_peak_usage() lets you reset the peak and measure parts of your code separately. To learn which requests are heavy, register this shutdown function as early as possible (a WordPress mu-plugin, a Laravel service provider or auto_prepend_file):
PHP:
register_shutdown_function(static function (): void {
$peakMiB = memory_get_peak_usage(true) / 1048576;
if ($peakMiB >= 96) {
error_log(sprintf(
'peak_memory=%.1fMiB target=%s',
$peakMiB,
$_SERVER['REQUEST_URI'] ?? implode(' ', $_SERVER['argv'] ?? [])
));
}
});
Shutdown functions still run after an "Allowed memory size" error, so the failing request's URL is logged too. They do not run when the hard limit is hit.
PHP-FPM has two built-in tools as well. The access log can record each request's peak memory with the %M field, and the status page shows the memory each worker's last request used (last request memory) and how many times the pool hit its worker limit (max children reached):
INI:
; pool file, for example www.conf
access.log = /var/log/php-fpm-$pool.access.log
access.format = "%t \"%m %r%Q%q\" %s %{milli}d ms %{mega}M MB %C%%"
pm.status_path = /fpm-status
request_slowlog_timeout = 5s
slowlog = /var/log/php-fpm-$pool.slow.log
Expose the status page to localhost only. The slow log writes a PHP stack trace for every request that runs longer than the timeout, which is an easy way to find heavy endpoints.
Profile
- Xdebug profiler: with xdebug.mode=profile and xdebug.start_with_request=trigger, only requests carrying XDEBUG_TRIGGER are profiled. According to the Xdebug docs the profiler also records which functions increase memory usage, not just time. The output is cachegrind.out.* files that open in KCachegrind, QCachegrind or PhpStorm. Xdebug adds a lot of overhead, so use it on a staging copy of your data, not in production.
- memprof: this extension's dump_on_limit mode writes a memory profile to /tmp/memprof.callgrind.* at the exact moment this error fires. On the CLI, run MEMPROF_PROFILE=dump_on_limit php script.php.
- Correlate logs: match the time in the error log against URLs in the access log, the slow log and your cron schedule. If the error arrives at the same time every night, a backup or sync job is the likely culprit.
Classic causes and their fixes
To show how much the processing method matters, we measured peak usage with memory_get_peak_usage(true) on PHP 8.4. The first four rows use a 52.7 MB CSV file with one million lines; the last two use one million small records (an id and an email address):| Method | Peak memory |
|---|---|
| file() to load every line into an array | 146.3 MiB |
| Parsing the file() result with array_map and str_getcsv() | 506.0 MiB |
| fgetcsv() loop | 4.0 MiB |
| SplFileObject (READ_CSV) | 4.0 MiB |
| Build an array of all records, then loop over it | 424.0 MiB |
| Yield the same records one by one from a generator | 4.0 MiB |
With a 128M limit the first two methods crashed with exactly this error. Turned into PHP arrays, the 52.7 MB file took almost ten times its size, while the streaming methods use the same memory whatever the file size. Instead of building a big array, yield from a generator:
PHP:
function rows(string $path): Generator
{
$h = fopen($path, 'r');
while (($row = fgetcsv($h, null, ',', '"', '')) !== false) {
yield $row;
}
fclose($h);
}
foreach (rows('/data/orders.csv') as $row) {
// only the current row is in memory
}
On PHP 8.4, fgetcsv() and str_getcsv() emit a deprecation notice on every call if you leave out the escape argument, which is why the example passes '' explicitly.
Databases: with mysqlnd and the default buffered mode, the whole result set is copied into PHP's memory and, per php.net, counted against memory_limit, even if you walk it row by row with fetch(). For millions of rows you have two options: an unbuffered query or keyset pagination.
PHP:
// Unbuffered: no other query can run on this connection until the result is consumed.
// On PHP 8.3 and older the constant is PDO::MYSQL_ATTR_USE_BUFFERED_QUERY (deprecated in PHP 8.5).
$pdo->setAttribute(Pdo\Mysql::ATTR_USE_BUFFERED_QUERY, false);
foreach ($pdo->query('SELECT id, email FROM users') as $row) {
// ...
}
// Keyset pagination: 1000 rows per round trip, no OFFSET
$stmt = $pdo->prepare('SELECT id, email FROM users WHERE id > ? ORDER BY id LIMIT 1000');
$lastId = 0;
do {
$stmt->execute([$lastId]);
$rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
foreach ($rows as $row) {
// ...
$lastId = $row['id'];
}
} while (count($rows) === 1000);
Images: a truecolor image in GD needs 4 bytes per pixel. A 6000 × 4000 photo is 96,000,000 bytes, about 92 MiB, before any copy is made. In our test imagecreatetruecolor(6000, 4000) raised PHP's counter by 98,345,008 bytes because every row is rounded up to 4 KiB pages. The JPEG file itself was only 375 KB: memory depends on pixel dimensions, not file size. Making a single 1600-pixel copy pushed the peak past 111 MB, so once the application's own usage is added, 128M may not be enough. PHP's bundled GD counts this memory against memory_limit. Distribution packages such as Debian/Ubuntu and Fedora build GD against the system libgd instead; then the pixel buffers are invisible to memory_limit but still use RAM and can hit LVE, container or OOM limits. The GD section of phpinfo() tells you which one you have: a "GD Version" row reading "bundled (2.1.0 compatible)" means the copy shipped with PHP, while "GD headers Version" and "GD library Version" rows mean the system libgd.
Imagick allocates through ImageMagick, and memory_limit never sees that memory. In our test with memory_limit=32M, loading the same image did not move PHP's counter at all while the process grew by about 338 MB (ImageMagick 7, Q16-HDRI build). Limit ImageMagick with policy.xml or Imagick::setResourceLimit(). When it hits those limits you get ImageMagick errors such as "cache resources exhausted" or "width or height exceeds limit", not "Allowed memory size". On the upload side, limit pixel dimensions as well as file size and generate thumbnails in a queue. We covered every upload limit layer in our "Uploaded File Is Too Large" article.
Recursion: if you see the 262144 signature, look for a function that calls itself. A typical data-driven case is a category or menu tree in which a record points to one of its own descendants as its parent. Track the ids you have visited, refuse to enter the same id twice and set a depth limit. When the recursion runs through an internal function such as array_map(), PHP 8.3 and later can stop it before memory runs out with "Maximum call stack size of … reached. Infinite recursion?", which is exactly what PHP 8.4 did in our test.
Long-running processes: queue workers, daemons and persistent runtimes such as Octane, Swoole or RoadRunner keep memory between requests. Static caches, Doctrine's identity map and log buffers grow a little with every job. Restart them on a schedule: --max-jobs, --max-time and --memory in Laravel; messenger:consume --limit, --memory-limit and --time-limit in Symfony Messenger; pm.max_requests in PHP-FPM, which the comment in www.conf describes as a way to work around memory leaks in third-party libraries. In Doctrine batch jobs, call clear() on the EntityManager after each batch. Since PHP 8.0, GD images are objects and imagedestroy() does nothing (it is deprecated in PHP 8.5); unset() the variable to release the memory. We touched on memory discipline for long-lived PHP processes in our PHP on the backend in 2026 article as well.
Not the same problem: telling similar symptoms apart
| Symptom or log line | What happened | What to do |
|---|---|---|
| Allowed memory size of … exhausted | PHP's per-request limit (memory_limit) was reached | Find the cause, raise the limit based on measurements |
| Hard memory limit of … exhausted (stderr only), exit status 137 | On patched releases, a request crossed the hard limit, usually with one huge allocation | Same approach; find the giant string or array |
| Out of memory (allocated …) (tried to allocate …), or just "Out of memory" | The operating system refused memory before memory_limit was reached. Patched releases print only "Out of memory" to stderr and exit. | Check RAM, swap, ulimit and container limits |
| FPM: exited on signal 9 (SIGKILL); kernel: Out of memory: Killed process … or Memory cgroup out of memory | The kernel's or the container's OOM killer killed the process | Size pm.max_children and memory_limit to the RAM, or add RAM |
| FPM: server reached pm.max_children setting (N), consider raising it; slow pages, then 502, 503 or 504 | Concurrent requests exceeded the number of workers; not a memory error | Size max_children from RAM and average worker memory, speed up slow requests |
| CloudLinux: 508 Resource Limit Is Reached, or 500/503 together with PMEM faults | An account-level LVE limit: entry processes (EP) or physical memory (PMEM) | Check the panel's resource usage page, review the plan or limits |
| ImagickException: cache resources exhausted | ImageMagick hit its own resource limit | policy.xml, setResourceLimit(), image dimensions |
| Maximum call stack size of … reached. Infinite recursion? | The C stack guard in PHP 8.3 and later | Fix the recursion |
The logs tell you which one you are dealing with (Debian/Ubuntu, nginx and PHP 8.4 shown):
Bash:
# PHP error messages; nginx logs them as "FastCGI sent in stderr" lines
sudo grep -h "Allowed memory size" /var/log/nginx/error.log | tail -5
# PHP-FPM master log: worker exits and max_children warnings
sudo grep -E "exited (with code|on signal)|max_children" /var/log/php8.4-fpm.log | tail
# Kernel OOM killer entries
sudo journalctl -k --since "1 hour ago" | grep -i -E "out of memory|killed process"
# Average memory (RSS) of PHP-FPM workers
ps -eo rss=,comm= | awk '/php-fpm/ {s+=$1; n++} END {if (n) printf "%d processes, average %.0f MB\n", n, s/n/1024}'
With Apache in front of PHP-FPM, the same PHP message lands in Apache's error log as "AH01071: Got error 'PHP message: …'". On AlmaLinux and the rest of the RHEL family, PHP errors from the default pool go to /var/log/php-fpm/www-error.log and FPM's own messages to /var/log/php-fpm/error.log.
The two exit lines in the FPM log mean different things: "exited with code 137" is PHP's own hard limit on patched releases, while "exited on signal 9 (SIGKILL)" means the kernel or the container stepped in:
Kod:
[27-Oct-2026 03:00:12] WARNING: [pool www] child 21731 exited with code 137 after 4.120335 seconds from start
[27-Oct-2026 03:00:41] WARNING: [pool www] child 21802 exited on signal 9 (SIGKILL) after 61.904211 seconds from start
As a rough rule, set pm.max_children to the RAM you can give PHP divided by the average worker size: about 40 for 3 GB and 75 MB per worker. RSS includes shared OPcache memory, so the estimate errs on the safe side. If you keep hitting LVE limits on shared hosting, a virtual server with full control over php.ini, FPM pools and RAM may suit you better; AIOR's VPS plans are one option.
Prevention checklist
- Give each site or application its own FPM pool and set the limit per pool.
- Pick a sensible limit for web requests (256M, for example) and move anything that needs more to the CLI or a queue.
- Monitor peak memory with %M in the FPM access log, the status page or the shutdown function, and alert when a threshold is crossed.
- Limit uploads by pixel dimensions as well as file size, and build thumbnails in a queue.
- Split long jobs into batches and restart workers regularly.
- Keep PHP, plugins and dependencies up to date, and do not skip the October 2026 security releases (8.5.12, 8.4.27, 8.3.36, 8.2.35).
- Use Composer 2: update on your workstation, install on the server.
- After every deployment, smoke-test the admin area, cron jobs and imports.
Frequently asked questions
How much memory_limit do I need?There is no single right number. Measure peak usage and leave headroom; 256M is a reasonable starting point for many sites, and if your measurements show less, keep it lower. Also work out the worst case, pm.max_children × memory_limit, and remember that on patched releases a single worker can briefly reach twice that limit.
Why does my change not apply?
The usual reasons are the wrong php.ini, an invalid unit such as "256MB", a value locked with php_admin_value, the .user.ini cache, LiteSpeed ignoring .user.ini, and a PHP-FPM that was never reloaded. The temporary check file or phpinfo() shows the value and the file that are really in effect.
Is memory_limit = -1 safe?
Only for one-off CLI jobs you are watching. On the web, unlimited memory means one faulty request can take down the whole server. The -1 in the October 2026 advisory is a temporary measure for servers that cannot update yet, paired with a cgroup limit; the real fix is updating.
Why does it only happen in wp-admin or in cron?
That is where the heavy work runs: updates, imports, backups. WordPress raises the limit to WP_MAX_MEMORY_LIMIT in wp-admin, in WP-Cron and for image processing, so an error there means that value was not enough or the server limit is locked. A system cron job runs PHP from the command line and therefore reads a different php.ini from your web requests.
Will adding RAM fix it?
Not on its own. The "Allowed memory size" error comes from a per-process limit, not from the amount of RAM in the server. More RAM helps with system-level problems such as the OOM killer, LVE or container limits, and when you want to run more workers. For a job that leaks memory, it only postpones the crash.
Conclusion and next steps
This error is PHP stopping one request before it can hurt the whole server. Read the message, find out which PHP runs the code, raise the limit one step at the right layer, then measure and fix the array, query, image or process that eats the memory. Next steps:- Today: run the memory_hard_limit_percent check on both the CLI and the web side, and update PHP if the setting is missing.
- This week: add %M to the FPM access log or log peak memory with the shutdown function, and list the five URLs that use the most memory.
- Then: rewrite the work behind those URLs with streams, pagination and queues, and move heavy jobs to the command line.