Node.js uygulamalarını doğrudan 3000, 8080 gibi portlarda dışarıya açmak hem güvenlik açısından hem de yönetim kolaylığı bakımından ideal bir yaklaşım değil. Apache’yi reverse proxy olarak kullanmak bu sorunu zarif biçimde çözüyor: SSL sonlandırma, log yönetimi, statik dosya servisi ve load balancing gibi konuları tek bir noktadan halledebiliyorsun. Bu yazıda gerçek bir production senaryosunu adım adım ele alacağız.
Temel Kavramlar ve Mimari
Reverse proxy basitçe şu anlama geliyor: Kullanıcı Apache’ye bağlanıyor, Apache bu isteği arka planda çalışan Node.js uygulamanıza iletiyor, cevabı alıp kullanıcıya geri döndürüyor. Kullanıcı hiçbir zaman Node.js’in hangi portta çalıştığını görmüyor.
Bu mimarinin avantajları:
- Port yönetimi: Node.js 3000 portunda çalışıyor, dışarıya sadece 80/443 açık
- SSL merkezi yönetimi: Sertifika işlemlerini Apache üstleniyor, Node.js’e şifresiz trafik gidiyor
- Statik dosyalar: CSS, JS, resim gibi dosyaları Apache doğrudan servis edebiliyor
- Log birleştirme: Tüm erişim logları tek formatta tutuluyor
- Rate limiting ve güvenlik: Apache seviyesinde ek koruma katmanı ekleniyor
- Birden fazla uygulama: Tek sunucuda farklı subdomain/path altında birden fazla Node.js uygulaması çalıştırılabiliyor
Senaryo olarak şunu hayal edelim: Bir e-ticaret firmasının Node.js ile yazılmış API backend’ini ve ayrı bir admin panelini aynı sunucuda çalıştırmanız gerekiyor. api.sirket.com ve admin.sirket.com subdomain’leri farklı Node.js process’lerine yönlendirilecek.
Gereksinimler ve Ön Hazırlık
Başlamadan önce sisteminizde şunların kurulu olması gerekiyor:
- Ubuntu 20.04/22.04 veya CentOS 7/8 (örneklerimiz Ubuntu üzerinden)
- Apache 2.4+
- Node.js 16+ ve npm/yarn
- PM2 (process manager olarak)
- Root veya sudo yetkisi
Apache’nin hangi version’da olduğunu kontrol edelim:
apache2 -v
# ya da
httpd -v # CentOS için
# Servis durumunu kontrol et
systemctl status apache2
Eğer Apache kurulu değilse:
sudo apt update
sudo apt install apache2 -y
sudo systemctl enable apache2
sudo systemctl start apache2
Node.js uygulamanızı production’da yönetmek için PM2 şart. PM2 olmadan uygulama çöktüğünde veya sunucu restart atıldığında Node.js process’i ayağa kalkmıyor.
sudo npm install -g pm2
# Örnek Node.js uygulamamızı başlatalım
# Uygulama /var/www/myapp dizininde, port 3000'de çalışıyor
pm2 start /var/www/myapp/app.js --name "myapp" --env production
# Sistem başlangıcında otomatik başlatma
pm2 startup
pm2 save
Apache Modüllerinin Aktifleştirilmesi
Reverse proxy için ihtiyacımız olan Apache modülleri varsayılan kurulumda devre dışı. Bunları aktifleştirmeden hiçbir şey çalışmayacak.
sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_balancer
sudo a2enmod lbmethod_byrequests
sudo a2enmod headers
sudo a2enmod rewrite
sudo a2enmod ssl
# Modülleri yüklemek için Apache'yi yeniden başlat
sudo systemctl restart apache2
# Aktif modülleri doğrula
apache2ctl -M | grep proxy
Hangi modülün ne işe yaradığını bilmek sorun giderirken kritik önem taşıyor:
- proxy: Temel proxy motoru, diğer proxy modülleri için zorunlu
- proxy_http: HTTP/HTTPS protokolü üzerinden proxy yapabilmek için
- proxy_balancer: Birden fazla backend arasında yük dağıtımı
- lbmethod_byrequests: Load balancing algoritması (istek sayısına göre dağıtım)
- headers: İstek ve yanıt header’larını değiştirme imkânı
- rewrite: URL yeniden yazma kuralları
İlk Virtual Host Konfigürasyonu
Basit bir senaryoyla başlayalım: myapp.com domain’i, port 3000’de çalışan Node.js uygulamasına yönlendirilecek.
sudo nano /etc/apache2/sites-available/myapp.com.conf
<VirtualHost *:80>
ServerName myapp.com
ServerAlias www.myapp.com
# Proxy ayarları
ProxyPreserveHost On
ProxyRequests Off
# Ana uygulama proxy'si
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/
# Header düzenlemeleri
RequestHeader set X-Forwarded-Proto "http"
RequestHeader set X-Real-IP %{REMOTE_ADDR}s
# Log dosyaları
ErrorLog ${APACHE_LOG_DIR}/myapp-error.log
CustomLog ${APACHE_LOG_DIR}/myapp-access.log combined
</VirtualHost>
# Siteyi aktifleştir
sudo a2ensite myapp.com.conf
# Konfigürasyonu test et (restart öncesi mutlaka yap)
sudo apache2ctl configtest
# Sorun yoksa reload
sudo systemctl reload apache2
ProxyPreserveHost On satırı kritik. Bu olmadan Node.js uygulamanız gelen isteğin hangi domain’e geldiğini bilemiyor, localhost görüyor. Bu durum özellikle multi-tenant uygulamalarda büyük sorun çıkarıyor.
ProxyRequests Off da mutlaka olmalı. Bu satır Apache’nin forward proxy olarak kullanılmasını engelliyor. Aksi halde sunucunuz açık bir proxy haline gelir ve kötüye kullanılır.
Subdomain Bazlı Çoklu Uygulama Konfigürasyonu
Şimdi gerçek senaryoya geçelim. api.sirket.com port 3000’e, admin.sirket.com port 4000’e yönlendirelim. İki ayrı Virtual Host dosyası oluşturuyoruz:
# API uygulaması için
sudo nano /etc/apache2/sites-available/api.sirket.com.conf
<VirtualHost *:80>
ServerName api.sirket.com
ProxyPreserveHost On
ProxyRequests Off
# WebSocket desteği için gerekli
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://localhost:3000/$1" [P,L]
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/
# Timeout ayarları - uzun süren API istekleri için
ProxyTimeout 300
Timeout 300
# CORS header'ları Apache'de yönetmek
Header always set Access-Control-Allow-Origin "https://sirket.com"
Header always set Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS"
Header always set Access-Control-Allow-Headers "Authorization, Content-Type"
RequestHeader set X-Forwarded-Proto "http"
RequestHeader set X-Real-IP %{REMOTE_ADDR}s
ErrorLog ${APACHE_LOG_DIR}/api-error.log
CustomLog ${APACHE_LOG_DIR}/api-access.log combined
</VirtualHost>
WebSocket desteği için RewriteEngine kurallarına dikkat edin. Socket.io veya benzeri kütüphane kullanıyorsanız bu satırlar olmadan gerçek zamanlı bağlantılar kesilecek.
SSL/HTTPS Konfigürasyonu
Production ortamında HTTP üzerinden servis vermek kabul edilemez. Let’s Encrypt ile ücretsiz SSL sertifikası alıp Apache konfigürasyonuna entegre edelim:
# Certbot kurulumu
sudo apt install certbot python3-certbot-apache -y
# SSL sertifikası al ve Apache'yi otomatik yapılandır
sudo certbot --apache -d api.sirket.com -d admin.sirket.com
# Otomatik yenilemeyi test et
sudo certbot renew --dry-run
Certbot Apache konfigürasyonunu otomatik güncelliyor ancak manuel kontrol etmek her zaman iyi alışkanlık. SSL sonrası konfigürasyon şu hale geliyor:
<VirtualHost *:80>
ServerName api.sirket.com
# HTTP'yi HTTPS'e yönlendir
RewriteEngine On
RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
</VirtualHost>
<VirtualHost *:443>
ServerName api.sirket.com
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/api.sirket.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/api.sirket.com/privkey.pem
# Modern SSL güvenlik ayarları
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
SSLHonorCipherOrder off
ProxyPreserveHost On
ProxyRequests Off
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://localhost:3000/$1" [P,L]
ProxyPass / http://localhost:3000/
ProxyPassReverse / http://localhost:3000/
# HTTPS olduğunu Node.js'e bildiriyoruz
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Real-IP %{REMOTE_ADDR}s
# HSTS header'ı
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
ErrorLog ${APACHE_LOG_DIR}/api-ssl-error.log
CustomLog ${APACHE_LOG_DIR}/api-ssl-access.log combined
</VirtualHost>
X-Forwarded-Proto header’ı Node.js uygulamanızın SSL farkındalığı için kritik. Express.js kullanıyorsanız uygulamanızda şu satırı mutlaka ekleyin:
// Express uygulamasında proxy güveni tanımlama
app.set('trust proxy', 1);
// req.secure artık doğru çalışacak
app.get('/check-ssl', (req, res) => {
res.json({
isSecure: req.secure,
protocol: req.protocol,
ip: req.ip // Gerçek client IP'si
});
});
trust proxy olmadan req.ip her zaman 127.0.0.1 dönecek, session cookie’leri secure: true ile düzgün çalışmayacak.
Path Bazlı Yönlendirme
Bazı senaryolarda subdomain yerine path üzerinden yönlendirme yapmak gerekiyor. sirket.com/api isteklerini Node.js’e, diğer istekleri başka bir servise göndermek gibi:
<VirtualHost *:443>
ServerName sirket.com
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/sirket.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/sirket.com/privkey.pem
# Statik dosyaları Apache doğrudan servis etsin
DocumentRoot /var/www/sirket.com/public
<Directory /var/www/sirket.com/public>
Options -Indexes +FollowSymLinks
AllowOverride None
Require all granted
</Directory>
# /api/* isteklerini Node.js'e yönlendir
ProxyPass /api/ http://localhost:3000/
ProxyPassReverse /api/ http://localhost:3000/
# /admin/* isteklerini admin uygulamasına yönlendir
ProxyPass /admin/ http://localhost:4000/
ProxyPassReverse /admin/ http://localhost:4000/
# Diğer tüm istekler DocumentRoot'tan servis edilecek
ProxyPass / !
ErrorLog ${APACHE_LOG_DIR}/sirket-error.log
CustomLog ${APACHE_LOG_DIR}/sirket-access.log combined
</VirtualHost>
ProxyPass / ! satırı önemli. Ünlem işareti o path’in proxy’ye gönderilmemesi anlamına geliyor. Sıralama kritik: Daha spesifik kurallar önce gelmelidir.
Load Balancing ile Yüksek Erişilebilirlik
Uygulamanızın yükü arttıkça tek bir Node.js process yetmeyebilir. cluster modülü veya birden fazla PM2 instance’ı ile load balancing yapabilirsiniz:
# PM2 ile 4 instance başlat
pm2 start app.js --name "myapp" -i 4
# 3000-3003 portlarında 4 process çalışıyor
pm2 list
Apache konfigürasyonunda bu 4 instance’ı load balancer arkasına alalım:
<VirtualHost *:443>
ServerName myapp.com
SSLEngine On
SSLCertificateFile /etc/letsencrypt/live/myapp.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/myapp.com/privkey.pem
# Backend havuzu tanımla
<Proxy balancer://myapp_cluster>
BalancerMember http://localhost:3000 route=node1
BalancerMember http://localhost:3001 route=node2
BalancerMember http://localhost:3002 route=node3
BalancerMember http://localhost:3003 route=node4
# Yük dağıtım algoritması: byrequests, bytraffic, bybusyness
ProxySet lbmethod=byrequests
</Proxy>
ProxyPreserveHost On
ProxyPass / balancer://myapp_cluster/
ProxyPassReverse / balancer://myapp_cluster/
# Sticky session - oturum bazlı yönlendirme
Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED
RequestHeader set X-Forwarded-Proto "https"
RequestHeader set X-Real-IP %{REMOTE_ADDR}s
ErrorLog ${APACHE_LOG_DIR}/myapp-error.log
CustomLog ${APACHE_LOG_DIR}/myapp-access.log combined
</VirtualHost>
Sticky session yani ROUTEID cookie kısmı özellikle session tabanlı uygulamalar için önemli. Kullanıcı her istekte farklı bir instance’a düşerse oturumu bozulabilir.
Yaygın Sorunlar ve Çözümleri
502 Bad Gateway Hatası
En sık karşılaşılan hata. Neredeyse her zaman Node.js uygulamasının çalışmadığı anlamına geliyor.
# Node.js process'lerini kontrol et
pm2 list
pm2 logs myapp --lines 50
# Port'ta gerçekten dinleme var mı?
sudo ss -tlnp | grep 3000
# veya
sudo netstat -tlnp | grep 3000
# Apache error log'unu incele
sudo tail -f /var/log/apache2/myapp-error.log
403 Forbidden Hatası
SELinux aktifse Apache’nin localhost’a bağlanmasına izin verilmesi gerekiyor:
# SELinux durumunu kontrol et
getenforce
# Apache'nin network bağlantısı için izin ver
sudo setsebool -P httpd_can_network_connect 1
Büyük Dosya Upload Sorunları
Node.js’e büyük dosya yüklerken timeout veya boyut limiti hatası alınabilir:
# Virtual host konfigürasyonuna ekle
LimitRequestBody 104857600 # 100MB
ProxyTimeout 600
Timeout 600
WebSocket Bağlantıları Kopuyor
Nginx’ten alışkınlar için bu sürpriz olabiliyor. Apache’de WebSocket için özel rewrite kuralı şart:
# Bu satırlar olmadan WebSocket upgrade isteği düzgün iletilmiyor
RewriteEngine On
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteCond %{HTTP:Connection} upgrade [NC]
RewriteRule ^/?(.*) "ws://localhost:3000/$1" [P,L]
Performans Optimizasyonu
Bir kaç ince ayar ile Apache reverse proxy performansını önemli ölçüde artırabilirsiniz:
sudo nano /etc/apache2/mods-available/proxy.conf
# Proxy modülü genel ayarları
<IfModule mod_proxy.c>
# Keep-alive bağlantı havuzu
ProxyVia Off
ProxyAddHeaders On
# Connection pool ayarları
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5
</IfModule>
# HTTP/1.1 keep-alive için
<IfModule mod_proxy_http.c>
ProxyHTTPVersion 1.1
SetEnv proxy-nokeepalive 0
SetEnv force-proxy-request-1.0 0
</IfModule>
Statik dosyaları Apache üzerinden servis ediyorsanız mod_cache ile browser caching eklemek de büyük fark yaratıyor:
sudo a2enmod cache
sudo a2enmod cache_disk
sudo a2enmod expires
# Virtual host içine ekle
<LocationMatch ".(css|js|png|jpg|gif|ico|woff2)$">
ExpiresActive On
ExpiresDefault "access plus 1 month"
Header append Cache-Control "public"
</LocationMatch>
Güvenlik Sertleştirme
Reverse proxy üzerinden bilgi sızdırmamak için bazı header’ları gizlemek iyi pratik:
# /etc/apache2/apache2.conf veya virtual host içine
ServerTokens Prod
ServerSignature Off
# Header modülü ile Apache versiyonunu gizle
Header always unset X-Powered-By
Header always unset Server
# Clickjacking koruması
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Content-Type-Options "nosniff"
Header always set X-XSS-Protection "1; mode=block"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Belirli IP adreslerini veya user-agent’ları engellemek için:
<VirtualHost *:443>
# ...diğer ayarlar...
# Zararlı bot'ları engelle
RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} (curl|wget|libwww-perl) [NC]
RewriteRule .* - [F,L]
# Belirli IP'yi engelle
<RequireAll>
Require all granted
Require not ip 192.168.1.100
</RequireAll>
</VirtualHost>
Log Analizi ve İzleme
Production ortamında logları takip etmek sorun gidermenin yarısı demek:
# Gerçek zamanlı hata takibi
sudo tail -f /var/log/apache2/myapp-error.log | grep -v "notice"
# En çok 500 hatası veren endpoint'leri bul
sudo awk '$9 == "500"' /var/log/apache2/myapp-access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head 20
# Son 1 saatin istek sayısı
sudo awk -v d="$(date -d '1 hour ago' '+%d/%b/%Y:%H')" '$4 > "["d' /var/log/apache2/myapp-access.log | wc -l
# Apache status modülü ile anlık istatistik
sudo a2enmod status
# /server-status endpoint'i sadece localhost'a aç
Sonuç
Apache reverse proxy kurulumu ilk bakışta karmaşık görünse de adımları sırayla takip ettiğinizde oldukça yönetilebilir bir yapı ortaya çıkıyor. Özetlemek gerekirse kritik noktalar şunlar:
- Proxy modüllerini aktifleştirmeden hiçbir şey çalışmıyor,
a2enmodadımını atlamayın ProxyPreserveHost OnveProxyRequests Offher konfigürasyonda zorunluX-Forwarded-Protoheader’ı ve Express’tetrust proxyayarı SSL ile çalışan uygulamalar için olmazsa olmaz- WebSocket kullanıyorsanız
RewriteEnginekuralları şart - PM2 olmadan production ortamında Node.js çalıştırmak risk
apache2ctl configtestalışkanlığı pek çok gereksiz restart’ı engelliyor
Bu yapıyı bir kez doğru kurduğunuzda, yeni Node.js uygulaması eklemek sadece yeni bir Virtual Host dosyası oluşturup a2ensite komutu çalıştırmak kadar basit hale geliyor. Subdomain eklemek, SSL sertifikası almak, log yönetmek: hepsi tek yerden hallediliyor. Sunucu sayısı arttıkça bu merkezi yönetimin değeri daha net anlaşılıyor.