Modern web altyapılarında tek bir sunucuya güvenmek artık lüks değil, risk. Trafiğin artması, sunucu bakımları, beklenmedik çökmeler… Bunların hepsini yönetmenin en temiz yolu iyi kurulmuş bir reverse proxy ve load balancer mimarisinden geçiyor. Apache’nin mod_proxy modülü, bu ihtiyacı karşılamak için yıllardır savaşta test edilmiş, olgun ve güçlü bir çözüm sunuyor. Bu yazıda sıfırdan başlayıp production ortamına kadar götürebileceğiniz bir yapı kuracağız.
mod_proxy Nedir ve Neden Kullanmalısınız?
Apache HTTP Server, mod_proxy modülü sayesinde hem reverse proxy hem de load balancer olarak çalışabilir. Peki bu ikisi arasındaki fark ne?
Reverse proxy: İstemciden gelen istekleri alıp arka taraftaki sunuculara (backend) ileten yapıdır. İstemci, arka sunucuların varlığından habersizdir. Yani app.sirketim.com adresine gelen istek, aslında localhost:3000 üzerinde koşan bir Node.js uygulamasına yönlendiriliyor olabilir.
Load balancer: Reverse proxy’nin üstüne kurulu, gelen trafiği birden fazla backend sunucusuna dağıtan yapıdır. Bir sunucu çöktüğünde trafik otomatik olarak diğerlerine yönlenir.
mod_proxy‘yi tercih etmenizin birkaç somut sebebi var:
- Apache zaten altyapınızda varsa ek bir yazılım kurmak zorunda kalmazsınız
- SSL termination, header manipülasyonu ve erişim kontrolü aynı yerden yönetilir
- Kapsamlı loglama ve izleme seçenekleri sunar
- Nginx kadar hype almasa da production ortamlarında son derece kararlı çalışır
Gerekli Modüllerin Aktifleştirilmesi
Debian/Ubuntu tabanlı sistemlerde modülleri etkinleştirmek oldukça basit:
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_balancer
sudo a2enmod lbmethod_byrequests
sudo a2enmod lbmethod_bytraffic
sudo a2enmod lbmethod_bybusyness
sudo a2enmod headers
sudo a2enmod rewrite
sudo systemctl restart apache2
RHEL/CentOS/Rocky Linux tarafında modüller genellikle zaten yüklüdür, ama /etc/httpd/conf.modules.d/00-proxy.conf dosyasını kontrol etmeniz gerekir:
# Mevcut durumu kontrol et
cat /etc/httpd/conf.modules.d/00-proxy.conf
# Aktif modülleri görmek için
httpd -M | grep proxy
Çıktıda şunları görmelisiniz: proxy_module, proxy_http_module, proxy_balancer_module.
Temel Reverse Proxy Yapılandırması
En basit senaryo ile başlayalım: 80 portuna gelen tüm trafiği, lokalda 8080 portunda çalışan bir uygulamaya yönlendirmek.
# /etc/apache2/sites-available/reverse-proxy.conf
<VirtualHost *:80>
ServerName app.sirketim.com
ServerAlias www.app.sirketim.com
# Proxy ayarları
ProxyPreserveHost On
ProxyRequests Off
# Tüm istekleri backend'e yönlendir
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
# Hata logları
ErrorLog ${APACHE_LOG_DIR}/app-error.log
CustomLog ${APACHE_LOG_DIR}/app-access.log combined
</VirtualHost>
Burada iki kritik direktif dikkat çekiyor:
- ProxyPreserveHost On: Orijinal
Hostheader’ını backend’e iletir. Backend uygulamanız hostname’e göre davranış değiştiriyorsa bu şart - ProxyRequests Off: Forward proxy özelliğini kapatır. Açık bırakırsanız sunucunuz açık proxy olarak kötüye kullanılabilir, bunu kesinlikle kapatın
Yapılandırmayı aktifleştirip test edin:
sudo a2ensite reverse-proxy.conf
sudo apache2ctl configtest
sudo systemctl reload apache2
Belirli Path’leri Farklı Backend’lere Yönlendirmek
Gerçek dünyada çok daha sık karşılaştığınız senaryo şu: Monolitik bir uygulamayı microservice’lere bölüyorsunuz ve /api/ yolunu ayrı bir servise, /static/ yolunu başka bir yere yönlendirmeniz gerekiyor.
# /etc/apache2/sites-available/microservices.conf
<VirtualHost *:80>
ServerName portal.sirketim.com
ProxyPreserveHost On
ProxyRequests Off
# API isteklerini Node.js backend'e yönlendir
ProxyPass /api/ http://localhost:3000/api/
ProxyPassReverse /api/ http://localhost:3000/api/
# Auth servisi ayrı çalışıyor
ProxyPass /auth/ http://localhost:4000/
ProxyPassReverse /auth/ http://localhost:4000/
# Static dosyalar için ayrı bir Python servisi
ProxyPass /static/ http://localhost:9000/static/
ProxyPassReverse /static/ http://localhost:9000/static/
# Geri kalan her şey ana PHP uygulamasına
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
ErrorLog ${APACHE_LOG_DIR}/portal-error.log
CustomLog ${APACHE_LOG_DIR}/portal-access.log combined
</VirtualHost>
Önemli bir detay: ProxyPass direktifleri yukarıdan aşağıya okunur. Daha spesifik path’ler her zaman daha genel olanlardan önce gelmeli. / en sona yazılmalı.
Load Balancer Yapılandırması
Şimdi işin eğlenceli kısmına geliyoruz. Üç backend sunucusuna trafik dağıtan bir load balancer kuralım:
# /etc/apache2/sites-available/loadbalancer.conf
<VirtualHost *:80>
ServerName lb.sirketim.com
ProxyRequests Off
ProxyPreserveHost On
# Balancer grubunu tanımla
<Proxy balancer://webcluster>
BalancerMember http://192.168.1.101:8080 route=node1
BalancerMember http://192.168.1.102:8080 route=node2
BalancerMember http://192.168.1.103:8080 route=node3
# Load balancing metodu
ProxySet lbmethod=byrequests
</Proxy>
ProxyPass / balancer://webcluster/
ProxyPassReverse / balancer://webcluster/
ErrorLog ${APACHE_LOG_DIR}/lb-error.log
CustomLog ${APACHE_LOG_DIR}/lb-access.log combined
</VirtualHost>
Load balancing metodları arasındaki farklar önemli:
- byrequests: Her backend eşit sayıda istek alır. Varsayılan ve çoğu senaryo için idealdir
- bytraffic: Byte cinsinden trafik miktarına göre dağıtır. Dosya indirme gibi büyük transfer senaryolarında kullanışlı
- bybusyness: Aktif bağlantı sayısına göre dağıtır. Uzun süreli bağlantılarda tercih edilebilir
Ağırlıklı Load Balancing ve Yedek Sunucu Tanımlama
Tüm sunucularınız eşit kapasitede olmayabilir. Bir sunucu daha güçlüyse ona daha fazla trafik göndermek mantıklı. Ayrıca acil durumlar için yedek (hot standby) sunucu tanımlamak da production ortamlarında kritik önem taşır.
<Proxy balancer://appcluster>
# Güçlü sunucu - ağırlık 3
BalancerMember http://192.168.1.101:8080 loadfactor=3 route=node1
# Orta sunucu - ağırlık 2
BalancerMember http://192.168.1.102:8080 loadfactor=2 route=node2
# Zayıf sunucu - ağırlık 1 (varsayılan)
BalancerMember http://192.168.1.103:8080 loadfactor=1 route=node3
# Yedek sunucu - sadece diğerleri çöktüğünde devreye girer
BalancerMember http://192.168.1.104:8080 status=+H route=node4
ProxySet lbmethod=byrequests
</Proxy>
status=+H ile işaretlenen sunucu hot standby olarak çalışır. Normal koşullarda trafik almaz, ancak diğer tüm sunucular devre dışı kaldığında otomatik olarak devreye girer. Bu özelliği kullanmak için mod_proxy_balancer modülünün aktif olması şart.
Sağlık Kontrolü (Health Check)
Arka sunuculardan biri çöktüğünde trafik ondan otomatik yönlendiriliyor mu? Bunu sağlamak için sağlık kontrolü mekanizmasını da yapılandırmanız gerekiyor.
# mod_proxy_hcheck modülünü aktifleştir
sudo a2enmod proxy_hcheck
# Yapılandırma
<Proxy balancer://healthcluster>
BalancerMember http://192.168.1.101:8080 route=node1 hcmethod=GET hcuri=/health hcinterval=10 hcpasses=2 hcfails=3
BalancerMember http://192.168.1.102:8080 route=node2 hcmethod=GET hcuri=/health hcinterval=10 hcpasses=2 hcfails=3
BalancerMember http://192.168.1.103:8080 route=node3 hcmethod=GET hcuri=/health hcinterval=10 hcpasses=2 hcfails=3
ProxySet lbmethod=byrequests
</Proxy>
Bu yapılandırmayla sistem her 10 saniyede bir her backend’in /health endpoint’ine GET isteği atar. Bir sunucu arka arkaya 3 kez başarısız olursa devre dışı bırakılır. 2 kez başarılı olduğunda yeniden aktife alınır.
Backend uygulamanızda basit bir health check endpoint’i olması bu yüzden önemli. Sadece 200 dönen bir route yeterli.
SSL Termination ile Güvenli Yapılandırma
Production ortamında mutlaka HTTPS kullanmalısınız. SSL termination reverse proxy katmanında yapılır, backend sunucular şifrelemeyle uğraşmak zorunda kalmaz.
# /etc/apache2/sites-available/secure-proxy.conf
<VirtualHost *:80>
ServerName app.sirketim.com
# HTTP'yi HTTPS'e yönlendir
Redirect permanent / https://app.sirketim.com/
</VirtualHost>
<VirtualHost *:443>
ServerName app.sirketim.com
# SSL ayarları
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/app.sirketim.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/app.sirketim.com/privkey.pem
# Modern SSL ayarları
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384
SSLHonorCipherOrder off
ProxyPreserveHost On
ProxyRequests Off
# Backend'e iletilecek header'lar
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Forwarded-Port "443"
<Proxy balancer://securecluster>
BalancerMember http://192.168.1.101:8080 route=node1
BalancerMember http://192.168.1.102:8080 route=node2
ProxySet lbmethod=byrequests
</Proxy>
ProxyPass / balancer://securecluster/
ProxyPassReverse / balancer://securecluster/
# Güvenlik header'ları
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
ErrorLog ${APACHE_LOG_DIR}/secure-app-error.log
CustomLog ${APACHE_LOG_DIR}/secure-app-access.log combined
</VirtualHost>
X-Forwarded-Proto header’ı kritik. Backend uygulamanız bu header’ı okuyarak bağlantının HTTPS üzerinden geldiğini anlayabilir. Bunu set etmezseniz, backend uygulamanız kendisini HTTP üzerinden çalışıyor zanneder ve cookie’ler, redirect’ler gibi konularda sorun yaşarsınız.
Sticky Session (Oturum Yapışkanlığı)
Uygulamanız durum bilgisi tutuyorsa (stateful), yani kullanıcı oturumunu bellekte saklıyorsa, aynı kullanıcının her seferinde aynı backend’e gitmesi gerekir. Buna sticky session denir.
<VirtualHost *:443>
ServerName crm.sirketim.com
# SSL ayarları burada...
<Proxy balancer://crmcluster>
BalancerMember http://192.168.1.101:8080 route=node1
BalancerMember http://192.168.1.102:8080 route=node2
BalancerMember http://192.168.1.103:8080 route=node3
# Sticky session için
ProxySet stickysession=ROUTEID
</Proxy>
ProxyPass / balancer://crmcluster/
ProxyPassReverse / balancer://crmcluster/
# Route bilgisini cookie'ye yaz
Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED
</VirtualHost>
Bu yapılandırma ile Apache, kullanıcının tarayıcısına ROUTEID adında bir cookie yazar. Sonraki isteklerde bu cookie’yi okuyarak kullanıcıyı aynı backend’e yönlendirir. O backend çöktüğünde ise başka birine geçer, yani işlevselliği bozulmaz.
Ancak şunu belirtmem gerek: Mümkünse uygulamanızı stateless yapın. Oturum verisini Redis veya Memcached gibi harici bir cache’e taşırsanız sticky session ihtiyacı ortadan kalkar ve load balancer çok daha verimli çalışır.
Balancer Manager ile Canlı İzleme
Apache’nin güzel özelliklerinden biri de web tabanlı balancer manager arayüzü. Canlı olarak backend’lerin durumunu görebilir, manuel olarak devre dışı bırakabilirsiniz.
# Balancer manager için güvenli erişim
<Location /balancer-manager>
SetHandler balancer-manager
# Sadece yönetici IP'lerinden erişim
Require ip 192.168.1.0/24
Require ip 10.0.0.1
</Location>
# Server status da ekleyelim
<Location /server-status>
SetHandler server-status
Require ip 192.168.1.0/24
</Location>
Bu arayüze https://app.sirketim.com/balancer-manager adresinden erişebilirsiniz. Acil durumda bir backend’i trafik dışı bırakmak için komut satırına koşmanıza gerek kalmaz.
Zaman Aşımı ve Bağlantı Optimizasyonu
Production ortamında muhakkak dikkat edilmesi gereken timeout ve bağlantı yönetimi ayarları:
# /etc/apache2/conf-available/proxy-tuning.conf
# Proxy zaman aşımı ayarları
ProxyTimeout 300
# Backend bağlantı havuzu
<Proxy balancer://tunedcluster>
BalancerMember http://192.168.1.101:8080
route=node1
connectiontimeout=5
timeout=300
retry=30
max=100
ttl=120
BalancerMember http://192.168.1.102:8080
route=node2
connectiontimeout=5
timeout=300
retry=30
max=100
ttl=120
ProxySet lbmethod=bybusyness
</Proxy>
Bu parametreler ne anlama geliyor:
- connectiontimeout=5: Backend’e bağlantı kurmak için en fazla 5 saniye bekle
- timeout=300: Yanıt almak için en fazla 300 saniye bekle
- retry=30: Bir backend hata verirse 30 saniye bekle, sonra tekrar dene
- max=100: Bu backend’e aynı anda en fazla 100 bağlantı aç
- ttl=120: Bağlantıları 120 saniye sonra kapat, yenisini aç
Gerçek Dünya Senaryosu: E-Ticaret Platformu
Şimdi her şeyi bir araya getirelim. Orta ölçekli bir e-ticaret platformu için örnek yapılandırma:
# /etc/apache2/sites-available/eticaret.conf
<VirtualHost *:80>
ServerName www.magazam.com
Redirect permanent / https://www.magazam.com/
</VirtualHost>
<VirtualHost *:443>
ServerName www.magazam.com
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/www.magazam.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/www.magazam.com/privkey.pem
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
ProxyPreserveHost On
ProxyRequests Off
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Real-IP %{REMOTE_ADDR}s
# Ödeme API'si - tek sunucu, hassas sistem
ProxyPass /api/payment/ http://payment-internal.magazam.local:9000/
ProxyPassReverse /api/payment/ http://payment-internal.magazam.local:9000/
# Genel API - 3 sunucu, yük dengeleme
<Proxy balancer://apicluster>
BalancerMember http://api1.magazam.local:8080 route=api1 loadfactor=2
BalancerMember http://api2.magazam.local:8080 route=api2 loadfactor=2
BalancerMember http://api3.magazam.local:8080 route=api3 loadfactor=1
BalancerMember http://api-standby.magazam.local:8080 status=+H route=standby
ProxySet lbmethod=byrequests
ProxySet stickysession=SESSID
</Proxy>
ProxyPass /api/ balancer://apicluster/api/
ProxyPassReverse /api/ balancer://apicluster/api/
# Frontend - 2 sunucu
<Proxy balancer://webcluster>
BalancerMember http://web1.magazam.local:80 route=web1
BalancerMember http://web2.magazam.local:80 route=web2
ProxySet lbmethod=bybusyness
</Proxy>
ProxyPass / balancer://webcluster/
ProxyPassReverse / balancer://webcluster/
# Session cookie
Header add Set-Cookie "SESSID=.%{BALANCER_WORKER_ROUTE}e; path=/; Secure; HttpOnly" env=BALANCER_ROUTE_CHANGED
# Güvenlik header'ları
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Balancer yönetimi - sadece iç ağdan
<Location /balancer-manager>
SetHandler balancer-manager
Require ip 10.0.0.0/8
</Location>
ErrorLog ${APACHE_LOG_DIR}/magazam-error.log
CustomLog ${APACHE_LOG_DIR}/magazam-access.log combined
</VirtualHost>
Sık Karşılaşılan Sorunlar ve Çözümleri
Sorun: Backend URL’de trailing slash sorunu
ProxyPass /app http://backend:8080 ile ProxyPass /app/ http://backend:8080/ arasındaki fark önemli. Trailing slash olmazsa Apache path’i doğru işleyemeyebilir. Her zaman her ikisinde de trailing slash kullanın ya da hiçbirinde kullanmayın, ama tutarlı olun.
Sorun: 502 Bad Gateway hataları
Backend hazır değilken veya çöktüğünde bu hatayı alırsınız. Error log’a bakın:
sudo tail -f /var/log/apache2/app-error.log | grep proxy
retry=0 ayarı hatalı backend’i hemen devre dışı bırakır. Veya retry=60 ile 60 saniye bekletebilirsiniz.
Sorun: Büyük dosya yüklemelerinde timeout
# Bu ayarı artırın
ProxyTimeout 600
LimitRequestBody 524288000
Sonuç
mod_proxy ile yapabileceğiniz şeyler burada anlattıklarımla sınırlı değil. WebSocket desteği için mod_proxy_wstunnel, FastCGI için mod_proxy_fcgi, AJP protokolü için mod_proxy_ajp gibi kardeş modüller de mevcut. Ancak yukarıdaki yapılandırmaları sağlam anlarsanız diğerlerine geçmek çok kolay olacak.
Production ortamına geçmeden önce şu kontrol listesini geçin: ProxyRequests Off mutlaka kapalı olsun, güvenlik header’ları eklenmiş olsun, balancer manager sadece iç ağdan erişilebilir olsun, health check aktif olsun ve loglama doğru yapılandırılmış olsun. Bunları sağladıktan sonra apache2ctl configtest ile sözdizimi kontrolü yapın, sonra yavaşça production’a alın.
En kritik öneri: Değişiklikleri önce staging ortamında test edin. Load balancer yapılandırması yanlış gittiğinde tüm siteniz çöker, tek tek sunucu sorunlarından çok daha kötü bir senaryo bu.