Yıllardır Apache kullanan birçok sistem yöneticisi, mod_php’nin getirdiği kısıtlamalarla boğuşmuştur. Her sanal sunucu için farklı PHP versiyonu çalıştıramamak, kaynak yönetimini fine-tune edememek, process izolasyonu sağlayamamak… PHP-FPM (FastCGI Process Manager) tam da bu problemleri çözmek için geliştirilmiştir. Apache ile PHP-FPM entegrasyonu, modern web hosting altyapılarının neredeyse standart bir bileşeni haline geldi. Bu yazıda, bu entegrasyonu sıfırdan kurmanın yanı sıra pool yapılandırmasını gerçek dünya senaryolarıyla birlikte ele alacağız.
PHP-FPM Nedir ve Neden Kullanmalısınız?
mod_php kullandığınızda, PHP her Apache worker process’ine gömülü olarak çalışır. Bu yaklaşımın en büyük sorunu, tüm sanal hostların aynı PHP versiyonunu ve aynı ayarları kullanmak zorunda kalmasıdır. Ayrıca Apache’nin her request için bir process veya thread spawn etmesi, bellek tüketimini ciddi ölçüde artırır.
PHP-FPM ise PHP’yi Apache’den tamamen bağımsız bir süreç olarak çalıştırır. Apache, PHP dosyalarına gelen istekleri FastCGI protokolü üzerinden PHP-FPM’e iletir, PHP-FPM işlemi tamamlayıp yanıtı geri döndürür. Bu mimarinin avantajları:
- Process izolasyonu: Her web sitesi kendi pool’unda, kendi kullanıcısı altında çalışabilir
- Çoklu PHP versiyonu: Aynı sunucuda PHP 7.4, 8.1, 8.2 aynı anda çalışabilir
- Gelişmiş kaynak yönetimi: Her pool için ayrı process limiti, memory limit belirlenebilir
- Daha iyi güvenlik: Bir sitenin PHP kodu diğer siteleri etkileyemez
- Performans: Özellikle yüksek trafikli sitelerde mod_php’ye göre belirgin fark yaratır
Kurulum
Debian/Ubuntu tabanlı sistemler için:
# Apache ve gerekli modüllerin kurulumu
sudo apt update
sudo apt install apache2 php8.1-fpm libapache2-mod-fcgid
# Gerekli Apache modüllerini etkinleştir
sudo a2enmod proxy_fcgi setenvif
sudo a2enmod actions
# PHP-FPM servisini başlat
sudo systemctl start php8.1-fpm
sudo systemctl enable php8.1-fpm
# Servislerin durumunu kontrol et
sudo systemctl status php8.1-fpm
sudo systemctl status apache2
RHEL/CentOS/AlmaLinux tabanlı sistemler için:
# EPEL ve Remi reposunu ekle
sudo dnf install epel-release
sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-9.rpm
# PHP-FPM kurulumu
sudo dnf install php81-php-fpm php81-php-cli php81-php-mysqlnd
# Apache modülü
sudo dnf install mod_fcgid
# Servisleri başlat
sudo systemctl start php81-php-fpm
sudo systemctl enable php81-php-fpm
sudo systemctl start httpd
sudo systemctl enable httpd
Apache’nin PHP-FPM ile Konuşması: Temel Yapılandırma
Apache’nin PHP-FPM ile haberleşmesi için iki yöntem mevcuttur: Unix socket ve TCP socket. Unix socket, aynı sunucuda çalışan servisler için TCP’ye göre daha hızlıdır çünkü network stack’i bypass eder. TCP ise PHP-FPM’in farklı bir sunucuda çalıştığı dağıtık mimarilerde kullanılır.
Debian/Ubuntu’da PHP-FPM varsayılan olarak /run/php/php8.1-fpm.sock socket dosyasını oluşturur. Temel bir sanal host yapılandırması:
<VirtualHost *:80>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public
# PHP-FPM ile Unix socket üzerinden proxy
<FilesMatch .php$>
SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost"
</FilesMatch>
# Dizin ayarları
<Directory /var/www/example.com/public>
AllowOverride All
Require all granted
Options -Indexes +FollowSymLinks
</Directory>
# Güvenlik: PHP dosyalarının upload dizininden çalışmasını engelle
<Directory /var/www/example.com/public/uploads>
<FilesMatch .php$>
Require all denied
</FilesMatch>
</Directory>
ErrorLog ${APACHE_LOG_DIR}/example.com-error.log
CustomLog ${APACHE_LOG_DIR}/example.com-access.log combined
</VirtualHost>
TCP socket kullanmak isterseniz:
<VirtualHost *:80>
ServerName example.com
DocumentRoot /var/www/example.com/public
<FilesMatch .php$>
SetHandler "proxy:fcgi://127.0.0.1:9000"
</FilesMatch>
<Directory /var/www/example.com/public>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Yapılandırmayı aktifleştirdikten sonra:
# Sanal host'u etkinleştir (Debian/Ubuntu)
sudo a2ensite example.com.conf
# Apache yapılandırmasını test et
sudo apachectl configtest
# Apache'yi yeniden yükle
sudo systemctl reload apache2
PHP-FPM Pool Yapılandırması
İşte asıl güç burada başlıyor. PHP-FPM’in pool yapılandırması /etc/php/8.1/fpm/pool.d/ dizininde bulunur. Her .conf dosyası bir pool tanımlar. Varsayılan www.conf dosyası iyi bir başlangıç noktasıdır ama production ortamında mutlaka özelleştirilmesi gerekir.
Process Manager Modları
PHP-FPM üç farklı process yönetim modu sunar:
- static: Sabit sayıda worker process çalışır, trafik az da olsa çok da olsa process sayısı değişmez
- dynamic: Process sayısı talebe göre artar veya azalır, minimum ve maksimum limitler belirlenebilir
- ondemand: Process’ler sadece request geldiğinde başlatılır, boşta kalan process’ler kapatılır, düşük trafikli siteler için idealdir
Gerçek Dünya Pool Örnekleri
Yüksek trafikli e-ticaret sitesi için pool:
; /etc/php/8.1/fpm/pool.d/eticaret.conf
[eticaret]
; Siteye özel kullanıcı ve grup ile izolasyon
user = eticaret_user
group = eticaret_user
; Unix socket - daha hızlı
listen = /run/php/php8.1-eticaret.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
; Dynamic mod - yük değişimlerine adaptif
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
; PHP ayarları - pool bazında override
php_admin_value[memory_limit] = 256M
php_admin_value[upload_max_filesize] = 64M
php_admin_value[post_max_size] = 64M
php_admin_value[max_execution_time] = 60
; Log ayarları
php_admin_flag[log_errors] = on
php_admin_value[error_log] = /var/log/php-fpm/eticaret-error.log
; Güvenlik
php_admin_value[open_basedir] = /var/www/eticaret.com:/tmp:/usr/share/php
; Yavaş request loglaması - 5 saniyeden uzun süren requestleri logla
slowlog = /var/log/php-fpm/eticaret-slow.log
request_slowlog_timeout = 5s
; Durum sayfası - monitoring için
pm.status_path = /fpm-status
Düşük trafikli blog veya kişisel site için pool:
; /etc/php/8.1/fpm/pool.d/blog.conf
[blog]
user = blog_user
group = blog_user
listen = /run/php/php8.1-blog.sock
listen.owner = www-data
listen.group = www-data
; Ondemand - kaynakları verimli kullan
pm = ondemand
pm.max_children = 10
pm.process_idle_timeout = 30s
pm.max_requests = 200
php_admin_value[memory_limit] = 128M
php_admin_value[upload_max_filesize] = 16M
php_admin_value[open_basedir] = /var/www/blog.com:/tmp
Pool değişikliklerini uygulamak için PHP-FPM’i yeniden yükleyin:
sudo systemctl reload php8.1-fpm
Aynı Sunucuda Birden Fazla PHP Versiyonu
Bu senaryo, özellikle shared hosting veya ajans ortamlarında çok sık karşılaşılan bir durumdur. Bir müşteri WordPress için PHP 8.1 isterken, diğeri legacy bir uygulama nedeniyle PHP 7.4 üzerinde kalmak zorunda olabilir.
# Birden fazla PHP versiyonunu kur (Ubuntu/Debian için ondrej/php repo gerekli)
sudo add-apt-repository ppa:ondrej/php
sudo apt update
sudo apt install php7.4-fpm php8.1-fpm php8.2-fpm
# Her versiyonun çalıştığını doğrula
sudo systemctl status php7.4-fpm
sudo systemctl status php8.1-fpm
sudo systemctl status php8.2-fpm
Farklı PHP versiyonlarına yönlendiren sanal host yapılandırmaları:
# Eski uygulama - PHP 7.4 kullanıyor
<VirtualHost *:80>
ServerName legacy-app.example.com
DocumentRoot /var/www/legacy-app/public
<FilesMatch .php$>
SetHandler "proxy:unix:/run/php/php7.4-fpm.sock|fcgi://localhost"
</FilesMatch>
<Directory /var/www/legacy-app/public>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
# Modern uygulama - PHP 8.2 kullanıyor
<VirtualHost *:80>
ServerName modern-app.example.com
DocumentRoot /var/www/modern-app/public
<FilesMatch .php$>
SetHandler "proxy:unix:/run/php/php8.2-fpm.sock|fcgi://localhost"
</FilesMatch>
<Directory /var/www/modern-app/public>
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
Güvenlik Sertleştirmesi
PHP-FPM kurulumunda güvenlik, en kritik konulardan biridir. Özellikle çok kiracılı (multi-tenant) ortamlarda dikkat edilmesi gereken noktalar:
open_basedir ile dizin izolasyonu: Her pool’un yalnızca kendi web root dizinine ve gerekli sistem dizinlerine erişmesini sağlar.
Tehlikeli PHP fonksiyonlarını devre dışı bırakma: Pool yapılandırmasına aşağıdakini ekleyebilirsiniz:
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,curl_multi_exec,parse_ini_file,show_source
Path traversal saldırısına karşı: Apache’de cgi.fix_pathinfo problemine karşı önlem alın. PHP-FPM pool’unda:
; Sadece gerçek PHP dosyalarını işle, path traversal'ı önle
security.limit_extensions = .php .php3 .php4 .php5 .php7
Apache sanal host yapılandırmasında da ek güvenlik:
<VirtualHost *:80>
ServerName secure-app.example.com
DocumentRoot /var/www/secure-app/public
# Sadece .php uzantılı dosyaları FPM'e gönder
<FilesMatch "^[^.]+.php$">
SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost"
</FilesMatch>
# Gizli dosyalara erişimi engelle
<FilesMatch "(^.ht|.git|composer.json|composer.lock)">
Require all denied
</FilesMatch>
# XML-RPC saldırılarına karşı (WordPress için)
<Files xmlrpc.php>
Require all denied
</Files>
</VirtualHost>
PHP-FPM Durum Sayfası ve İzleme
Pool yapılandırmasında tanımladığımız pm.status_path değerini kullanarak FPM’in anlık durumunu izleyebiliriz. Bu, özellikle bir performans sorunu yaşandığında ne kadar worker’ın meşgul olduğunu anlamak için çok değerlidir.
Status sayfasını sadece localhost’tan erişilebilir yapın:
<VirtualHost *:80>
ServerName monitor.example.local
<Location "/fpm-status">
SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost"
Require ip 127.0.0.1
Require ip 10.0.0.0/8
</Location>
<Location "/fpm-ping">
SetHandler "proxy:unix:/run/php/php8.1-fpm.sock|fcgi://localhost"
Require ip 127.0.0.1
</Location>
</VirtualHost>
Komut satırından hızlı durum kontrolü:
# FPM durumunu kontrol et
curl -s http://127.0.0.1/fpm-status
# JSON formatında (monitoring araçları için)
curl -s "http://127.0.0.1/fpm-status?json&full"
# Ping ile sağlık kontrolü
curl -s http://127.0.0.1/fpm-ping
# Çıktı: pong
# Aktif process sayısını öğren
curl -s "http://127.0.0.1/fpm-status" | grep "active processes"
# Yavaş logları takip et
sudo tail -f /var/log/php-fpm/eticaret-slow.log
pm.max_children Değerini Doğru Hesaplamak
Bu, PHP-FPM yapılandırmasındaki en kritik parametredir. Yanlış hesaplama ya kaynak israfına ya da site çöküşlerine yol açar.
Hesaplama mantığı basittir: Mevcut RAM’den sistem ve diğer servisler için gereken miktarı çıkarın, kalanı ortalama PHP process başına düşen RAM kullanımına bölün.
# Çalışan PHP-FPM process'lerinin ortalama RAM kullanımını öğren
ps -ylC php-fpm8.1 --sort:rss | awk 'NR>1 {sum+=$8; count++} END {printf "Ortalama: %d KB (%d MB)nToplam process: %dn", sum/count, sum/count/1024, count}'
# Daha detaylı bilgi için
ps aux | grep php-fpm | grep -v grep | awk '{sum += $6; n++} END {print "Ortalama RSS: " sum/n/1024 " MB"}'
Pratikte PHP process başına düşen RAM genellikle 20MB ile 80MB arasında değişir. Bu, çalışan uygulamaya, yüklü PHP extension’larına ve bellek limitine bağlıdır. Örneğin 4GB RAM’li bir sunucuda:
- Sistem ve diğer servisler: 1GB
- PHP-FPM için kalan: 3GB
- Ortalama process RAM: 50MB
- pm.max_children = 3072 / 50 = yaklaşık 60 (biraz marj bırakarak 50 yapın)
Sorun Giderme
Entegrasyon sürecinde en sık karşılaşılan sorunlar ve çözümleri:
“502 Bad Gateway” hatası: Genellikle FPM socket’i yoksa veya FPM çalışmıyorsa alırsınız.
# Socket dosyasının var olduğunu kontrol et
ls -la /run/php/php8.1-fpm.sock
# FPM loglarına bak
sudo journalctl -u php8.1-fpm -n 50 --no-pager
sudo tail -100 /var/log/php8.1-fpm.log
# Apache hata logunu kontrol et
sudo tail -100 /var/log/apache2/error.log
“Permission denied” hatası: Socket dosyasının izinleri yanlış olabilir.
# Socket sahibini ve izinlerini kontrol et
ls -la /run/php/
# FPM pool.d dosyasında listen.owner ve listen.group
# değerlerinin Apache kullanıcısıyla eşleştiğinden emin ol
grep "listen." /etc/php/8.1/fpm/pool.d/www.conf
PHP bilgisi doğrulama: PHP-FPM’in gerçekten çalışıp çalışmadığını test etmek için:
# Test dosyası oluştur
echo "<?php phpinfo(); ?>" | sudo tee /var/www/example.com/public/phpinfo.php
# Test ettikten sonra MUTLAKA sil
curl http://example.com/phpinfo.php | grep -i "Server API"
# Çıktıda "FPM/FastCGI" görmeli
sudo rm /var/www/example.com/public/phpinfo.php
Performans Optimizasyonu
PHP-FPM’in mod_php’ye kıyasla avantajı sadece izolasyon değil, aynı zamanda performanstır. Bazı ek ayarlarla bu performansı daha da artırabilirsiniz.
OPcache ile PHP-FPM’in birlikte çalışması için pool yapılandırmasına eklenebilecek ayarlar:
; OPcache ayarları - pool bazında
php_admin_value[opcache.memory_consumption] = 128
php_admin_value[opcache.interned_strings_buffer] = 8
php_admin_value[opcache.max_accelerated_files] = 10000
php_admin_flag[opcache.enable] = on
php_admin_flag[opcache.validate_timestamps] = off
opcache.validate_timestamps = off production’da PHP dosyalarını her request’te kontrol etmemesini sağlar, ciddi bir CPU tasarrufu yaratır. Ama bunu yaptığınızda deploy sonrası OPcache’i manuel olarak temizlemeniz gerekir.
# OPcache'i temizleme scripti (deploy hook'una eklenebilir)
sudo systemctl reload php8.1-fpm
Sonuç
Apache ile PHP-FPM entegrasyonu, ilk kurulumda biraz karmaşık görünebilir ama bir kez doğru yapılandırdığınızda size sunduğu esneklik ve kontrol inanılmaz değerlidir. Özellikle birden fazla web sitesi veya uygulama barındıran sunucularda, her site için izole pool’lar oluşturmak hem güvenliği hem de istikrarı ciddi ölçüde artırır. Bir sitenin ani trafik artışı diğer siteleri boğmaz, bir uygulamanın hatası başka bir uygulamanın process’lerini etkilemez.
Pool yapılandırmasında en çok zaman harcamanız gereken parametre pm.max_childrendir. Bunu gerçekçi RAM ölçümlerine dayandırın, teorik hesaplamalar yetmez. Slow log’u etkinleştirin ve düzenli olarak takip edin, performans sorunları genellikle orada kendini belli eder. FPM durum sayfasını Nagios, Zabbix veya Prometheus gibi monitoring araçlarınıza entegre edin.
Sonuç olarak, mod_php’den PHP-FPM’e geçiş sadece teknik bir tercih değil, production grade bir altyapı için neredeyse zorunlu bir adımdır. Bu yazıda anlattığımız yapılandırmaları kendi ortamınıza uyarlayın, önce test/staging sunucusunda deneyin, ardından production’a alın.