PHP-FPM Pool Yapılandırması: Her Site İçin Ayrı Kullanıcı ve Kaynak Limiti Tanımlama

Paylaşımlı hosting ortamlarında en sık karşılaşılan güvenlik sorunlarından biri, tüm PHP süreçlerinin aynı kullanıcı altında çalışmasıdır. Birinin sitesi hacklendiğinde, saldırgan aynı sunucudaki diğer sitelerin dosyalarına da erişebilir. PHP-FPM pool yapılandırması bu sorunu kökten çözer: her site için ayrı bir süreç havuzu, ayrı kullanıcı kimliği ve ayrı kaynak limiti tanımlarsınız. Bu yazıda gerçek dünya senaryolarıyla PHP-FPM pool yapılandırmasını derinlemesine ele alacağız.

PHP-FPM Pool Nedir ve Neden Önemlidir?

PHP-FPM (FastCGI Process Manager), PHP süreçlerini yöneten gelişmiş bir daemon’dır. Her “pool”, bağımsız bir PHP işlem grubudur. Bu grup kendi kullanıcısıyla, kendi bellek limitiyle ve kendi süreç sayısıyla çalışır.

Varsayılan kurulumda çoğu dağıtım tek bir www veya www-data pool’u ile gelir. Bu yapıda sunucunuzdaki her site aynı kimlikle çalışır. /var/www/siteA/ klasörüne yazma izni olan bir PHP betiği, /var/www/siteB/ klasörüne de erişebilir. Güvenlik açısından bu kabul edilemez bir durumdur.

Ayrı pool yapılandırmasıyla:

  • Her site kendi Linux kullanıcısıyla çalışır
  • Bir sitenin güvenliği ihlal edilse bile diğer siteler etkilenmez
  • Her site için farklı kaynak limiti (RAM, CPU, süreç sayısı) tanımlayabilirsiniz
  • Log dosyaları site bazında ayrışır, sorun tespiti kolaylaşır

Sistem Hazırlığı: Kullanıcı ve Dizin Yapısı

Önce her site için ayrı bir Linux kullanıcısı oluşturmamız gerekiyor. Örnek senaryomuzda üç site var: siteniz.com, musteri1.com ve musteri2.com.

# Her site için sistem kullanıcısı oluştur (login kabuğu olmadan)
sudo useradd -r -s /sbin/nologin -M siteniz
sudo useradd -r -s /sbin/nologin -M musteri1
sudo useradd -r -s /sbin/nologin -M musteri2

# Web dizinlerini oluştur ve sahipliği ata
sudo mkdir -p /var/www/siteniz.com/{public,logs,tmp}
sudo mkdir -p /var/www/musteri1.com/{public,logs,tmp}
sudo mkdir -p /var/www/musteri2.com/{public,logs,tmp}

# Dizin sahipliğini ilgili kullanıcılara ver
sudo chown -R siteniz:siteniz /var/www/siteniz.com
sudo chown -R musteri1:musteri1 /var/www/musteri1.com
sudo chown -R musteri2:musteri2 /var/www/musteri2.com

# Nginx'in okuyabilmesi için www-data'yı gruplara ekle
sudo usermod -aG siteniz www-data
sudo usermod -aG musteri1 www-data
sudo usermod -aG musteri2 www-data

Dizin izinlerini de doğru ayarlayın. Nginx okuyabilmeli ama yazamamalı, PHP-FPM süreci ise sahip olduğu dizinlere yazabilmeli:

# Ana dizin ve public: sahip okur/yazar, grup okur, diğerleri hiçbir şey
sudo chmod 750 /var/www/siteniz.com
sudo chmod 750 /var/www/siteniz.com/public

# Geçici ve log dizinleri
sudo chmod 770 /var/www/siteniz.com/tmp
sudo chmod 750 /var/www/siteniz.com/logs

Pool Yapılandırma Dosyalarının Oluşturulması

PHP-FPM pool yapılandırmaları genellikle /etc/php/8.2/fpm/pool.d/ dizininde bulunur (PHP sürümüne göre yol değişir). Her site için ayrı bir .conf dosyası oluşturmak en iyi pratiktir.

İlk pool’umuzu oluşturalım:

sudo nano /etc/php/8.2/fpm/pool.d/siteniz.com.conf

Aşağıdaki yapılandırma orta ölçekli bir web sitesi için uygundur:

[siteniz.com]
; Kullanıcı ve grup tanımları
user = siteniz
group = siteniz

; Socket dosyası (TCP yerine Unix socket daha hızlıdır)
listen = /run/php/php8.2-fpm-siteniz.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Süreç yönetimi: dynamic modda min/max arasında otomatik ölçeklenir
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
pm.max_requests = 500

; PHP ayarlarını override et
php_admin_value[memory_limit] = 256M
php_admin_value[upload_max_filesize] = 32M
php_admin_value[post_max_size] = 34M
php_admin_value[max_execution_time] = 60
php_admin_value[error_log] = /var/www/siteniz.com/logs/php-error.log
php_admin_flag[log_errors] = on

; Güvenlik ayarları
php_admin_value[open_basedir] = /var/www/siteniz.com:/tmp:/usr/share/php
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen

; Geçici dosya dizini
php_value[session.save_path] = /var/www/siteniz.com/tmp
php_value[upload_tmp_dir] = /var/www/siteniz.com/tmp

; Çevre değişkenleri
env[HOSTNAME] = $HOSTNAME
env[PATH] = /usr/local/bin:/usr/bin:/bin
env[APP_ENV] = production

Şimdi düşük trafikli bir müşteri sitesi için daha kısıtlı bir pool:

[musteri1.com]
user = musteri1
group = musteri1

listen = /run/php/php8.2-fpm-musteri1.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

; Düşük trafik için ondemand modu - sadece istek gelince süreç başlatır
pm = ondemand
pm.max_children = 10
pm.process_idle_timeout = 10s
pm.max_requests = 200

; Kısıtlı kaynaklar
php_admin_value[memory_limit] = 128M
php_admin_value[upload_max_filesize] = 10M
php_admin_value[post_max_size] = 12M
php_admin_value[max_execution_time] = 30
php_admin_value[error_log] = /var/www/musteri1.com/logs/php-error.log
php_admin_flag[log_errors] = on

; Daha sıkı güvenlik
php_admin_value[open_basedir] = /var/www/musteri1.com:/tmp
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source

php_value[session.save_path] = /var/www/musteri1.com/tmp
php_value[upload_tmp_dir] = /var/www/musteri1.com/tmp

Nginx Tarafında Pool Bağlantısı

Her Nginx sanal host bloğunu ilgili PHP-FPM socket’ine yönlendirmeniz gerekiyor:

# /etc/nginx/sites-available/siteniz.com
server {
    listen 80;
    server_name siteniz.com www.siteniz.com;
    root /var/www/siteniz.com/public;
    index index.php index.html;

    access_log /var/www/siteniz.com/logs/nginx-access.log;
    error_log /var/www/siteniz.com/logs/nginx-error.log;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ .php$ {
        fastcgi_pass unix:/run/php/php8.2-fpm-siteniz.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;

        # Zaman aşımı ayarları
        fastcgi_connect_timeout 60s;
        fastcgi_send_timeout 60s;
        fastcgi_read_timeout 60s;
    }

    # PHP dosyalarını uploads dizininden çalıştırmayı engelle
    location ~* /uploads/.*.php$ {
        deny all;
    }
}

Süreç Yönetimi Modları: Hangisini Ne Zaman Kullanmalısınız?

PHP-FPM’in üç farklı süreç yönetimi modu vardır ve hangisini seçeceğiniz sitenin trafik profiline göre değişir.

static mod: Sabit sayıda süreç her zaman ayakta durur.

  • pm: static
  • pm.max_children: 10
  • Anlık trafik artışlarını karşılar ama boşta da RAM tüketir
  • Yüksek ve stabil trafik için uygundur

dynamic mod: Trafike göre süreç sayısı değişir, bir minimum her zaman aktiftir.

  • pm: dynamic
  • pm.max_children: Aynı anda çalışabilecek maksimum süreç sayısı
  • pm.start_servers: Başlangıçta açılan süreç sayısı
  • pm.min_spare_servers: En az bu kadar boşta süreç bulunur
  • pm.max_spare_servers: En fazla bu kadar boşta süreç bulunur
  • pm.max_requests: Bir süreç bu kadar istek sonra yeniden başlar (memory leak önlemi)
  • Çoğu web sitesi için en dengeli seçenektir

ondemand mod: İstek geldikçe süreç açar, boştayken kapatır.

  • pm: ondemand
  • pm.max_children: Maksimum eşzamanlı süreç
  • pm.process_idle_timeout: Boşta kalan süreç bu süre sonra kapanır
  • Düşük trafikli siteler için RAM tasarrufu sağlar

Kaynak Limitlerini Hesaplamak

Pool başına kaç süreç tanımlayacağınızı hesaplamak için şu formülü kullanın:

# Mevcut PHP süreçlerinin ortalama RAM kullanımını göster
ps --no-headers -o "rss,cmd" -C php-fpm8.2 | awk '{sum+=$1} END {print int(sum/NR/1024)" MB ortalama"}'

# Toplam kullanılabilir RAM'i hesapla (örnek çıktı üzerinden)
# Kullanılabilir RAM: 4096 MB
# Ortalama PHP süreci: 64 MB
# Diğer servisler (Nginx, MySQL, vb.): 512 MB
# Kullanılabilir: (4096 - 512) / 64 = 55 süreç

# Bu 55 süreci siteler arasında paylaştırın:
# siteniz.com: 20 süreç (yüksek trafik)
# musteri1.com: 10 süreç
# musteri2.com: 25 süreç

Gerçek Dünya Senaryosu: WooCommerce Sitesi

E-ticaret siteleri özel dikkat gerektirir. Yüksek bellek, uzun zaman aşımı ve güvenli geçici dizinler şart:

[eticaret.com]
user = eticaret
group = eticaret

listen = /run/php/php8.2-fpm-eticaret.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660

pm = dynamic
pm.max_children = 30
pm.start_servers = 6
pm.min_spare_servers = 4
pm.max_spare_servers = 10
pm.max_requests = 1000

; WooCommerce için yüksek bellek limiti
php_admin_value[memory_limit] = 512M
php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 66M

; Toplu ürün içe aktarma işlemleri için uzun zaman aşımı
php_admin_value[max_execution_time] = 300
php_admin_value[max_input_time] = 300
php_admin_value[max_input_vars] = 5000

php_admin_value[error_log] = /var/www/eticaret.com/logs/php-error.log
php_admin_flag[log_errors] = on
php_admin_flag[display_errors] = off

php_admin_value[open_basedir] = /var/www/eticaret.com:/tmp:/usr/share/php:/usr/share/GeoIP

; Oturum güvenliği
php_value[session.save_path] = /var/www/eticaret.com/tmp/sessions
php_value[session.cookie_httponly] = 1
php_value[session.cookie_secure] = 1
php_value[session.use_strict_mode] = 1

; OPcache ayarları (site özelinde)
php_admin_value[opcache.memory_consumption] = 128
php_admin_value[opcache.max_accelerated_files] = 10000

Pool Durumunu ve Performansını İzleme

PHP-FPM’in dahili status sayfası pool sağlığını izlemek için çok değerlidir:

; siteniz.com.conf dosyasına ekle
pm.status_path = /fpm-status
ping.path = /fpm-ping
ping.response = pong

Nginx’te bu endpoint’i sadece localhost’a açın:

location /fpm-status {
    fastcgi_pass unix:/run/php/php8.2-fpm-siteniz.sock;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    include fastcgi_params;
    allow 127.0.0.1;
    deny all;
}

Status sayfasını komut satırından sorgulayabilirsiniz:

# Pool durumunu göster
curl -s "http://127.0.0.1/fpm-status?full" | head -50

# Belirli bir pool'un süreçlerini listele
curl -s "http://127.0.0.1/fpm-status?json" | python3 -m json.tool

# PHP-FPM global durumu
sudo systemctl status php8.2-fpm

# Pool yapılandırmasını test et (yeniden başlatmadan önce mutlaka yapın)
sudo php-fpm8.2 -t

# Yapılandırmayı yeniden yükle (sıfır kesinti ile)
sudo systemctl reload php8.2-fpm

# Belirli bir pool için log dosyasını takip et
tail -f /var/www/siteniz.com/logs/php-error.log

Sorun Giderme ve Yaygın Hatalar

Socket izin hatası: Nginx “Permission denied while connecting to upstream” hatası veriyorsa:

# Socket dosyasının izinlerini kontrol et
ls -la /run/php/php8.2-fpm-siteniz.sock

# www-data kullanıcısının gruba dahil olup olmadığını kontrol et
groups www-data

# PHP-FPM log dosyasına bak
sudo tail -f /var/log/php8.2-fpm.log

open_basedir kısıtlaması: WordPress veya benzeri uygulamalar çeşitli sistem yollarına erişmek isteyebilir. Hata alırsanız:

# Hangi yola erişmeye çalıştığını log'dan tespit et
grep "open_basedir" /var/www/siteniz.com/logs/php-error.log

# Gerekli yolu open_basedir'a ekle
php_admin_value[open_basedir] = /var/www/siteniz.com:/tmp:/usr/share/php:/usr/lib/php

Yüksek süreç sayısı uyarısı: pm.max_children değerine ulaşıldığında PHP-FPM log’a uyarı yazar. Bu durumu izlemek için:

# Son 1000 satırda kaç kez max_children uyarısı var?
grep "max_children" /var/log/php8.2-fpm.log | wc -l

# Uyarı fazlaysa pm.max_children değerini artır veya
# min_spare_servers ile start_servers değerlerini optimize et

Güvenlik Sertleştirmesi

Pool yapılandırmasına ekleyebileceğiniz ek güvenlik ayarları:

; Tehlikeli PHP fonksiyonlarını devre dışı bırak
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec,pcntl_fork,pcntl_signal

; Bilgi sızdıran başlıkları gizle
php_admin_flag[expose_php] = off

; Hata mesajlarını ekranda gösterme, sadece logla
php_admin_flag[display_errors] = off
php_admin_flag[display_startup_errors] = off
php_admin_flag[log_errors] = on

; Dosya yükleme saldırılarını sınırla
php_admin_flag[file_uploads] = on
php_admin_value[upload_max_filesize] = 10M

; URL dosya açmayı kapat (uzak dosya dahil etme açığını kapatır)
php_admin_flag[allow_url_fopen] = off
php_admin_flag[allow_url_include] = off

Sonuç

PHP-FPM pool yapılandırması, hem güvenlik hem de performans açısından production ortamınızda olmazsa olmazdır. Her site için ayrı kullanıcı tanımlamak, bir ihlal durumunda hasarı izole eder. Kaynak limitleri sayesinde tek bir sitenin tüm sunucu kaynaklarını tüketmesini engellersiniz.

Yapılandırma yaparken şu adımları takip edin: önce site sayısına ve trafik profiline göre kaynak hesabı yapın, ardından her site için uygun pm modunu seçin, güvenlik ayarlarını sıkılaştırın ve mutlaka php-fpm -t ile yapılandırmayı test edin. Değişiklikleri systemctl reload ile uyguladığınızda aktif bağlantılar kesilmez, yeni istekler güncel yapılandırmayla işlenir.

Monitoring tarafını da ihmal etmeyin. FPM status sayfasını düzenli sorgulayan basit bir betik bile sizi çok ciddi sorunlardan korur. Hangi pool’da darboğaz yaşandığını, kaç sürecin aktif olduğunu ve ortalama istek süresini takip etmek, kapasiteyi önceden planlamanızı sağlar. Sistem yöneticiliğinde reaktif değil, proaktif olmak her zaman daha az acı demektir.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir