Nginx ile PHP-FPM Entegrasyonu ve Optimizasyonu

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_children kadar 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_servers ile başlar, trafik arttıkça pm.max_children‘a kadar büyür, düşünce pm.min_spare_servers seviyesine 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_children limitine ulaşıldı. systemctl status php8.2-fpm ve log dosyalarını incele.
  • 504 Gateway Timeout: PHP betiği çok uzun sürüyor. request_terminate_timeout ve Nginx fastcgi_read_timeout değerlerini kontrol et.
  • Permission denied (socket): Socket dosyasının izinleri ile Nginx kullanıcısının uyumsuzluğu. listen.owner ve listen.group ayarlarını gözden geçir.
  • No input file specified: fastcgi_param SCRIPT_FILENAME yanlış yapılandırılmış. $realpath_root$fastcgi_script_name kullandığı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_children değ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.

Yorum yapın