Modern web uygulamalarının büyük çoğunluğu PHP tabanlı çalışıyor ve bu uygulamaları production ortamında sağlıklı bir şekilde ayağa kaldırmak için Nginx ile PHP-FPM kombinasyonu neredeyse endüstri standardı haline geldi. Apache + mod_php döneminin yerini alan bu ikili, doğru yapılandırıldığında hem performans hem de kaynak kullanımı açısından ciddi avantajlar sağlıyor. Ama “kurdum, çalışıyor” noktasında kalmak yerine gerçekten optimize edilmiş bir setup oluşturmak için biraz daha derine inmek gerekiyor.
PHP-FPM Nedir ve Neden Nginx ile Bu Kadar İyi Çalışır?
PHP-FPM (FastCGI Process Manager), PHP’nin FastCGI implementasyonudur. Nginx, statik dosyaları son derece hızlı servis eder ama PHP kodunu çalıştırmaz. İşte burada PHP-FPM devreye girer. Nginx gelen PHP isteğini FastCGI protokolü üzerinden PHP-FPM’e iletir, PHP-FPM bu isteği işler ve sonucu Nginx’e geri döndürür.
Apache + mod_php kurulumunda her Apache işlemi içinde bir PHP yorumlayıcısı gömülü gelir. Bu, statik dosyalar için bile PHP yorumlayıcısının bellekte yer kaplaması demektir. Nginx + PHP-FPM mimarisinde ise işler ayrı tutulur. Nginx çok hafif bir event-driven mimaride çalışırken PHP-FPM kendi process havuzunu yönetir. Bu ayrışma sayesinde hem kaynak kullanımını kontrol altına alabilirsin hem de her birini bağımsız olarak ölçekleyebilirsin.
Kurulum
Ubuntu/Debian tabanlı sistemlerde kurulum oldukça basit:
# Nginx ve PHP-FPM kurulumu
sudo apt update
sudo apt install nginx php8.2-fpm php8.2-cli php8.2-mysql php8.2-curl php8.2-gd php8.2-mbstring php8.2-xml php8.2-zip -y
# Servislerin durumunu kontrol et
sudo systemctl status nginx
sudo systemctl status php8.2-fpm
# Otomatik başlatmayı aktif et
sudo systemctl enable nginx
sudo systemctl enable php8.2-fpm
RHEL/CentOS/Rocky Linux sistemlerde ise:
# EPEL ve Remi repository ekle
sudo dnf install epel-release -y
sudo dnf install https://rpms.remirepo.net/enterprise/remi-release-9.rpm -y
# PHP 8.2 modülünü etkinleştir
sudo dnf module enable php:remi-8.2 -y
# Kurulum
sudo dnf install nginx php-fpm php-mysqlnd php-curl php-gd php-mbstring php-xml php-zip -y
PHP-FPM Pool Yapılandırması
PHP-FPM’in gücü pool yapılandırmasından gelir. Varsayılan olarak /etc/php/8.2/fpm/pool.d/www.conf dosyası gelir ama production ortamında bunu ciddi şekilde özelleştirmen gerekiyor.
Özellikle büyük projelerde her uygulama için ayrı bir pool oluşturmak güvenlik ve izolasyon açısından kritik önem taşır:
# /etc/php/8.2/fpm/pool.d/myapp.conf
[myapp]
; Kullanıcı ve grup ayarları - her uygulama kendi kullanıcısıyla çalışsın
user = myapp
group = myapp
; Unix socket kullan - TCP'den daha hızlı
listen = /run/php/php8.2-fpm-myapp.sock
listen.owner = www-data
listen.group = www-data
listen.mode = 0660
; Process manager tipi
; static: sabit sayıda worker
; dynamic: talebe göre değişen worker sayısı
; ondemand: sadece istek geldiğinde worker başlat
pm = dynamic
; dynamic mod için parametreler
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 3
pm.max_spare_servers = 10
pm.max_requests = 500
; Yavaş istekleri logla (saniye cinsinden)
slowlog = /var/log/php-fpm/myapp-slow.log
request_slowlog_timeout = 5s
; Maksimum istek süresi
request_terminate_timeout = 60s
; Ortam değişkenleri
env[HOSTNAME] = $HOSTNAME
env[APP_ENV] = production
; PHP ayarları (php.ini override)
php_admin_value[error_log] = /var/log/php-fpm/myapp-error.log
php_admin_flag[log_errors] = on
php_value[memory_limit] = 256M
php_value[upload_max_filesize] = 64M
php_value[post_max_size] = 64M
php_value[max_execution_time] = 60
Process Manager Modlarını Anlamak
PM modlarını seçerken sunucunun karakterini anlamak önemli:
- static:
pm.max_childrenkadar worker her zaman ayakta durur. Yüksek ve sürekli trafik olan sitelerde tercih edilir. Bellek tüketimi öngörülebilir olur ama boşta bile kaynak harcar. - dynamic: En yaygın kullanılan mod.
pm.start_serversile başlar, trafik arttıkçapm.max_children‘a kadar büyür, düşüncepm.min_spare_serversseviyesine geri döner. - ondemand: Düşük trafikli ya da çok sayıda site barındıran ortamlarda mantıklıdır. Worker’lar boştayken tamamen kapanır, bellek tasarrufu sağlar ama ilk istek biraz yavaş olabilir.
pm.max_children Değerini Hesaplamak
Bu değeri rastgele vermek yerine hesaplamak lazım. Basit formül şu şekilde:
# Sunucuda ne kadar boş RAM var?
free -m
# Ortalama bir PHP-FPM worker ne kadar RAM kullanıyor?
ps aux | grep php-fpm | grep -v grep | awk '{print $6}' | sort -n
# Hesaplama: Kullanılabilir RAM / Ortalama Worker RAM Kullanımı
# Örnek: 2048 MB kullanılabilir RAM, ortalama 50 MB worker
# max_children = 2048 / 50 = ~40 (biraz marj bırakarak)
Nginx Yapılandırması
PHP-FPM ile çalışacak Nginx server block’unu doğru yapılandırmak kritik. Temel bir yapılandırma şöyle görünür:
# /etc/nginx/sites-available/myapp.conf
server {
listen 80;
listen [::]:80;
server_name myapp.example.com;
root /var/www/myapp/public;
index index.php index.html;
# Güvenlik başlıkları
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
# Access log
access_log /var/log/nginx/myapp-access.log;
error_log /var/log/nginx/myapp-error.log warn;
# Statik dosyalar için cache
location ~* .(jpg|jpeg|png|gif|ico|css|js|woff|woff2|ttf|svg)$ {
expires 30d;
add_header Cache-Control "public, immutable";
access_log off;
}
# Ana location bloğu - Laravel, Symfony gibi framework'ler için
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# PHP dosyalarını PHP-FPM'e ilet
location ~ .php$ {
# Güvenlik: sadece var olan dosyaları işle
try_files $uri =404;
fastcgi_split_path_info ^(.+.php)(/.+)$;
# Unix socket kullan
fastcgi_pass unix:/run/php/php8.2-fpm-myapp.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
include fastcgi_params;
# Timeout değerleri
fastcgi_connect_timeout 60s;
fastcgi_send_timeout 60s;
fastcgi_read_timeout 60s;
# Buffer ayarları
fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
}
# .htaccess ve gizli dosyalara erişimi engelle
location ~ /.(?!well-known) {
deny all;
}
# PHP-FPM status sayfası (sadece yerel erişim)
location ~ ^/(fpm-status|fpm-ping)$ {
allow 127.0.0.1;
deny all;
fastcgi_pass unix:/run/php/php8.2-fpm-myapp.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
Nginx Global Optimizasyonları
Sadece site konfigürasyonu değil, ana nginx.conf dosyası da optimize edilmeli:
# /etc/nginx/nginx.conf
user www-data;
# CPU çekirdek sayısına eşit ayarla
worker_processes auto;
# Her worker için maksimum açık dosya sayısı
worker_rlimit_nofile 65535;
error_log /var/log/nginx/error.log warn;
pid /run/nginx.pid;
events {
# Worker başına bağlantı sayısı
worker_connections 1024;
# Linux'ta en verimli metod
use epoll;
# Birden fazla bağlantıyı aynı anda kabul et
multi_accept on;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Performans optimizasyonları
sendfile on;
tcp_nopush on;
tcp_nodelay on;
# Keep-alive bağlantıları
keepalive_timeout 65;
keepalive_requests 100;
# Gzip sıkıştırma
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json
application/javascript application/xml+rss
application/atom+xml image/svg+xml;
# Client body buffer
client_body_buffer_size 128k;
client_max_body_size 64m;
client_header_buffer_size 1k;
large_client_header_buffers 4 16k;
# Timeout'lar
client_body_timeout 12;
client_header_timeout 12;
send_timeout 10;
# Sunucu versiyonunu gizle
server_tokens off;
# Log formatı
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'rt=$request_time uct=$upstream_connect_time '
'uht=$upstream_header_time urt=$upstream_response_time';
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
OPcache Yapılandırması
PHP-FPM performansından bahsediyorsak OPcache’i atlamak olmaz. OPcache, PHP dosyalarını derleme aşamasından sonra bytecode olarak bellekte saklar. Her istek için dosyayı yeniden parse etmek ve derlemek zorunda kalmazsın:
# /etc/php/8.2/fpm/conf.d/10-opcache.ini
[opcache]
opcache.enable=1
opcache.enable_cli=0
; Kaç MB bellek ayrılsın (büyük uygulamalar için artır)
opcache.memory_consumption=256
; Kaç adet PHP dosyası cache'lenebilir
opcache.max_accelerated_files=20000
; String interning için bellek (MB)
opcache.interned_strings_buffer=16
; Dosya değişikliklerini kontrol etme sıklığı (saniye)
; Production'da 0 yap ve her deploy'da cache'i temizle
opcache.revalidate_freq=0
opcache.validate_timestamps=0
; Hızlı kapatma
opcache.fast_shutdown=1
; JIT - PHP 8.x ile gelen özellik
opcache.jit_buffer_size=100M
opcache.jit=1255
Production ortamında validate_timestamps=0 ayarını kullandığında her PHP deploy sonrasında cache’i manuel temizlemeyi unutma:
# OPcache'i temizleme scripti
# /usr/local/bin/clear-opcache.sh
#!/bin/bash
php -r "if (function_exists('opcache_reset')) { opcache_reset(); echo 'OPcache clearedn'; }"
# Ya da web üzerinden (deploy script içinde)
curl -s http://localhost/opcache-clear.php
Gerçek Dünya Senaryosu: Yüksek Trafikli Bir WordPress Sitesi
Diyelim ki aylık 500.000 sayfa görüntülemesi olan bir WordPress sitesi yönetiyorsun. Standart bir kurulumla saatlik trafik zirvelerinde server 502 hatası fırlatmaya başlıyor. İşte bu senaryoda izleyeceğim adımlar:
Önce mevcut durumu analiz et:
# PHP-FPM durumunu kontrol et
curl http://127.0.0.1/fpm-status?full
# Nginx'in upstream hataları
grep "upstream" /var/log/nginx/error.log | tail -50
# PHP-FPM log'larına bak
tail -f /var/log/php-fpm/www-error.log
# Anlık process durumu
watch -n1 "ps aux | grep php-fpm | grep -v grep | wc -l"
Çoğu zaman sorun şu şekilde ortaya çıkar: pm.max_children limitine ulaşılmış, yeni istekler kuyrukta bekliyor, Nginx bekleme süresini aşınca 502 dönüyor. Çözüm için önce pm.max_children değerini artır ama bunu yaparken RAM kullanımına dikkat et. Sonra Nginx’te upstream timeout değerlerini gözden geçir:
# WordPress için optimize edilmiş PHP-FPM location bloğu
location ~ .php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+.php)(/.+)$;
fastcgi_pass unix:/run/php/php8.2-fpm-wordpress.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
include fastcgi_params;
# WordPress için timeout'ları artır
fastcgi_read_timeout 300s;
# FastCGI cache - tekrar eden istekler için
fastcgi_cache wordpress_cache;
fastcgi_cache_valid 200 301 302 10m;
fastcgi_cache_use_stale error timeout updating http_500 http_503;
fastcgi_cache_background_update on;
fastcgi_cache_lock on;
# Cache bypass - giriş yapmış kullanıcılar için
fastcgi_cache_bypass $skip_cache;
fastcgi_no_cache $skip_cache;
add_header X-FastCGI-Cache $upstream_cache_status;
}
FastCGI cache için upstream bloğunu http context’ine ekle:
# /etc/nginx/nginx.conf http bloğu içine
fastcgi_cache_path /var/cache/nginx/wordpress levels=1:2
keys_zone=wordpress_cache:100m
max_size=2g
inactive=60m
use_temp_path=off;
# Cache key
fastcgi_cache_key "$scheme$request_method$host$request_uri";
Monitoring ve Sorun Giderme
Sağlıklı bir production ortamı için izleme şart. PHP-FPM’in yerleşik status sayfasını aktif et ve düzenli olarak kontrol et:
# PHP-FPM status sayfasına bak
curl -s "http://127.0.0.1/fpm-status" | grep -E "listen queue|active processes|idle processes"
# Slow log'u analiz et
grep -E "script_filename|request_slowlog" /var/log/php-fpm/myapp-slow.log | paste - -
# Hangi PHP dosyaları en çok çalışıyor?
awk '/script_filename/ {print $NF}' /var/log/php-fpm/myapp-slow.log | sort | uniq -c | sort -rn | head -20
Sık karşılaşılan sorunlara hızlı bir bakış:
- 502 Bad Gateway: PHP-FPM çalışmıyor, socket dosyası yok ya da
max_childrenlimitine ulaşıldı.systemctl status php8.2-fpmve log dosyalarını incele. - 504 Gateway Timeout: PHP betiği çok uzun sürüyor.
request_terminate_timeoutve Nginxfastcgi_read_timeoutdeğerlerini kontrol et. - Permission denied (socket): Socket dosyasının izinleri ile Nginx kullanıcısının uyumsuzluğu.
listen.ownervelisten.groupayarlarını gözden geçir. - No input file specified:
fastcgi_param SCRIPT_FILENAMEyanlış yapılandırılmış.$realpath_root$fastcgi_script_namekullandığından emin ol.
SSL/TLS ile Birlikte Kullanım
Production ortamında her şey HTTPS üzerinden çalışmalı. Let’s Encrypt ile Nginx + PHP-FPM kombinasyonu için:
# Certbot kurulumu
sudo apt install certbot python3-certbot-nginx -y
# Sertifika al
sudo certbot --nginx -d myapp.example.com -d www.myapp.example.com
# Otomatik yenilemeyi test et
sudo certbot renew --dry-run
Certbot Nginx konfigürasyonunu otomatik düzenler ama elle optimize etmek istersen:
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name myapp.example.com;
ssl_certificate /etc/letsencrypt/live/myapp.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.example.com/privkey.pem;
# Modern SSL konfigürasyonu
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_stapling on;
ssl_stapling_verify on;
# HSTS
add_header Strict-Transport-Security "max-age=63072000" always;
# PHP-FPM'e HTTPS bilgisini ilet
location ~ .php$ {
fastcgi_pass unix:/run/php/php8.2-fpm-myapp.sock;
fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
fastcgi_param HTTPS on;
include fastcgi_params;
}
}
Sonuç
Nginx ile PHP-FPM entegrasyonu, doğru yapılandırıldığında hem yüksek performanslı hem de güvenli bir web sunucusu ortamı sunar. Özetlemek gerekirse:
- Her uygulama için ayrı PHP-FPM pool kullan, bu hem güvenlik hem izolasyon sağlar.
- Unix socket’leri TCP’ye tercih et, local iletişimde belirgin performans farkı yaratır.
pm.max_childrendeğerini sunucunun gerçek RAM kapasitesine göre hesapla, rastgele verme.- OPcache olmadan PHP-FPM performansı yarıya düşer, production’da her zaman aktif tut.
- FastCGI cache, yüksek trafikli ve içerik değişim frekansı düşük sitelerde (WordPress, statik içerik ağırlıklı siteler) ciddi fark yaratır.
- Slow log’u aktif tut ve düzenli olarak incele, performans sorunlarını önceden yakala.
- Monitoring olmadan hiçbir optimizasyonun etkinliğini bilemezsin.
Bu kurulumu yaptıktan sonra ab ya da wrk gibi bir yük testi aracıyla gerçek dünya senaryolarını simüle etmeni öneririm. Kağıt üzerinde mükemmel görünen bir konfigürasyon, gerçek trafik altında bazen sürpriz yapabilir. Test etmeden production’a geçme, geçme ama test etmeden kesinlikle geçme.