Apache ile Node.js Uygulaması için Reverse Proxy Kurulumu

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, a2enmod adımını atlamayın
  • ProxyPreserveHost On ve ProxyRequests Off her konfigürasyonda zorunlu
  • X-Forwarded-Proto header’ı ve Express’te trust proxy ayarı SSL ile çalışan uygulamalar için olmazsa olmaz
  • WebSocket kullanıyorsanız RewriteEngine kuralları şart
  • PM2 olmadan production ortamında Node.js çalıştırmak risk
  • apache2ctl configtest alış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.

Yorum yapın