Apache ile WebSocket Proxy Yapılandırması

Modern web uygulamalarının vazgeçilmezi haline gelen WebSocket teknolojisi, gerçek zamanlı iletişim için mükemmel bir çözüm sunuyor. Ancak Apache arkasında bir WebSocket sunucusu çalıştırıyorsanız ve bu bağlantıları doğru şekilde proxy’lemek istiyorsanız, birkaç kritik yapılandırma adımını atlamanız durumunda ciddi sorunlarla karşılaşabilirsiniz. Bu yazıda Apache üzerinde WebSocket proxy yapılandırmasını en ince ayrıntısına kadar ele alacağız.

WebSocket ve Apache Proxy: Temel Kavramlar

WebSocket, HTTP’nin aksine kalıcı, çift yönlü bir bağlantı protokolüdür. Normal bir HTTP isteğinde istemci bir şey sorar, sunucu cevap verir ve bağlantı kapanır. WebSocket’te ise bağlantı açık kalır ve her iki taraf da istediği zaman veri gönderebilir. Chat uygulamaları, canlı bildirimler, oyunlar, finansal ticker’lar bunların hepsi WebSocket kullanır.

Apache, varsayılan olarak HTTP/1.1 upgrade mekanizmasını otomatik olarak işleyemez. Bu yüzden mod_proxy_wstunnel modülünü devreye almamız gerekiyor. Bu modül, gelen WebSocket bağlantılarını (ws:// veya wss://) arka plandaki gerçek WebSocket sunucusuna iletmekten sorumludur.

Gerçek dünya senaryomuz şu: Elimizde Node.js ile yazılmış bir chat uygulaması var, 3000 portunda çalışıyor ve WebSocket bağlantıları kabul ediyor. Biz bu uygulamayı Apache’nin arkasına koyacağız ve dışarıya sadece 443 (HTTPS/WSS) portu üzerinden hizmet vereceğiz.

Gerekli Modüllerin Aktifleştirilmesi

Öncelikle Apache’de hangi modüllerin etkin olduğunu kontrol edelim:

apache2ctl -M | grep -E "proxy|rewrite|ssl"

WebSocket proxy için aşağıdaki modüllere ihtiyacımız var:

  • mod_proxy: Temel proxy işlevselliği
  • mod_proxy_http: HTTP proxy desteği
  • mod_proxy_wstunnel: WebSocket tünelleme (en önemli olan bu)
  • mod_rewrite: URL yeniden yazma
  • mod_ssl: HTTPS/WSS desteği için

Ubuntu/Debian üzerinde bu modülleri aktifleştirelim:

sudo a2enmod proxy
sudo a2enmod proxy_http
sudo a2enmod proxy_wstunnel
sudo a2enmod rewrite
sudo a2enmod ssl
sudo a2enmod headers
sudo systemctl restart apache2

CentOS/RHEL sistemlerde modüller genellikle httpd.conf veya /etc/httpd/conf.modules.d/ altındaki dosyalarda tanımlanır:

# /etc/httpd/conf.modules.d/00-proxy.conf dosyasini kontrol edin
grep -n "proxy_wstunnel" /etc/httpd/conf.modules.d/00-proxy.conf

# Satir yorumlanmissa (#) onundeki # isaretini kaldirin
sudo sed -i 's/#LoadModule proxy_wstunnel_module/LoadModule proxy_wstunnel_module/' /etc/httpd/conf.modules.d/00-proxy.conf

sudo systemctl restart httpd

Temel WebSocket Proxy Yapılandırması

İlk olarak basit bir HTTP üzerinden WebSocket proxy kurulumunu görelim. Bu yapıyı test ortamlarında veya Apache’nin önünde başka bir SSL sonlandırıcı (örneğin bir load balancer) olduğu durumlarda kullanabilirsiniz:

<VirtualHost *:80>
    ServerName chat.orneksite.com
    
    # Proxy ayarlari
    ProxyRequests Off
    ProxyPreserveHost On
    
    # WebSocket baglantilari icin wss/ws proxy
    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} websocket [NC]
    RewriteCond %{HTTP:Connection} upgrade [NC]
    RewriteRule ^/?(.*) "ws://localhost:3000/$1" [P,L]
    
    # Normal HTTP istekleri icin proxy
    ProxyPass / http://localhost:3000/
    ProxyPassReverse / http://localhost:3000/
    
    ErrorLog ${APACHE_LOG_DIR}/chat-error.log
    CustomLog ${APACHE_LOG_DIR}/chat-access.log combined
</VirtualHost>

Burada kritik nokta şu: RewriteRule direktifleri ProxyPass direktiflerinden önce gelmelidir. Apache kuralları sırayla işler ve WebSocket upgrade isteği önce yakalanmalıdır. Eğer ProxyPass önce gelirse, Apache WebSocket bağlantısını normal HTTP trafiği gibi işlemeye çalışır ve bağlantı başarısız olur.

HTTPS/WSS ile Güvenli Yapılandırma

Prodüksiyon ortamında kesinlikle SSL kullanmalısınız. WSS (WebSocket Secure), WS’nin TLS üzerinden çalışan versiyonudur. Apache SSL’i sonlandırır ve arka planla HTTP üzerinden konuşur:

<VirtualHost *:443>
    ServerName chat.orneksite.com
    
    # SSL Ayarlari
    SSLEngine On
    SSLCertificateFile /etc/letsencrypt/live/chat.orneksite.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/chat.orneksite.com/privkey.pem
    
    # Guvenlik headerlari
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
    
    ProxyRequests Off
    ProxyPreserveHost On
    
    # SSL proxy ayari - arka plan sunucusu HTTP kullaniyorsa
    SSLProxyEngine Off
    
    RewriteEngine On
    
    # WebSocket upgrade isteklerini yonlendir
    RewriteCond %{HTTP:Upgrade} ^websocket$ [NC]
    RewriteCond %{HTTP:Connection} ^(.*,s*)?[Uu]pgrade(,.*)?$ [NC]
    RewriteRule ^/socket/(.*) ws://localhost:3000/socket/$1 [P,L]
    
    # Normal HTTP istekleri
    ProxyPass /socket/ http://localhost:3000/socket/
    ProxyPassReverse /socket/ http://localhost:3000/socket/
    
    # Diger istekler icin ayri backend
    ProxyPass / http://localhost:8080/
    ProxyPassReverse / http://localhost:8080/
    
    ErrorLog ${APACHE_LOG_DIR}/chat-ssl-error.log
    CustomLog ${APACHE_LOG_DIR}/chat-ssl-access.log combined
</VirtualHost>

# HTTP'yi HTTPS'e yonlendir
<VirtualHost *:80>
    ServerName chat.orneksite.com
    RewriteEngine On
    RewriteRule ^(.*)$ https://%{HTTP_HOST}$1 [R=301,L]
</VirtualHost>

Belirli Path Üzerinden WebSocket Proxy

Gerçek dünyada sıkça karşılaşılan senaryo şu: Ana siteniz orneksite.com üzerinde çalışıyor ama WebSocket bağlantıları sadece /ws ya da /socket.io gibi belirli bir path üzerinden geliyor. Socket.io kullanan bir uygulama için yapılandırma şu şekilde olmalı:

<VirtualHost *:443>
    ServerName orneksite.com
    
    SSLEngine On
    SSLCertificateFile /etc/ssl/certs/orneksite.crt
    SSLCertificateKeyFile /etc/ssl/private/orneksite.key
    
    ProxyRequests Off
    ProxyPreserveHost On
    
    RewriteEngine On
    
    # Socket.io polling (HTTP long-polling fallback)
    RewriteCond %{QUERY_STRING} transport=polling
    RewriteRule /socket.io/(.*) http://localhost:3000/socket.io/$1 [P,L]
    
    # Socket.io WebSocket upgrade
    RewriteCond %{HTTP:Upgrade} ^websocket$ [NC]
    RewriteRule /socket.io/(.*) ws://localhost:3000/socket.io/$1 [P,L]
    
    # Socket.io genel proxy
    ProxyPass /socket.io/ http://localhost:3000/socket.io/
    ProxyPassReverse /socket.io/ http://localhost.3000/socket.io/
    
    # Ana uygulama
    ProxyPass / http://localhost:8080/
    ProxyPassReverse / http://localhost:8080/
    
    # Onemli: ProxyPass timeout ayarlari
    ProxyTimeout 3600
    
    ErrorLog ${APACHE_LOG_DIR}/orneksite-error.log
    CustomLog ${APACHE_LOG_DIR}/orneksite-access.log combined
</VirtualHost>

ProxyTimeout değerini yüksek tutmak çok önemli. Varsayılan değer genellikle 60 saniyedir ve WebSocket bağlantısı 60 saniye boyunca herhangi bir veri alışverişi olmazsa Apache bağlantıyı keser. Chat uygulamalarında kullanıcılar saatlerce bir şey yazmayabilir, bu yüzden bu değeri 3600 (1 saat) veya daha yüksek ayarlamanız gerekiyor.

Load Balancer Arkasında WebSocket Proxy

Birden fazla WebSocket sunucunuz varsa Apache’yi bir load balancer olarak da kullanabilirsiniz. Ancak WebSocket için sticky session (yapışkan oturum) zorunludur. Bir kullanıcının WebSocket bağlantısı kurulduğunda, sonraki tüm istekler aynı backend sunucusuna gitmek zorundadır:

<VirtualHost *:443>
    ServerName ws.orneksite.com
    
    SSLEngine On
    SSLCertificateFile /etc/ssl/certs/ws-orneksite.crt
    SSLCertificateKeyFile /etc/ssl/private/ws-orneksite.key
    
    ProxyRequests Off
    
    # WebSocket sunucu kümesi - sticky session ile
    <Proxy balancer://wscluster>
        BalancerMember ws://ws1.internal:3000 route=ws1
        BalancerMember ws://ws2.internal:3000 route=ws2
        BalancerMember ws://ws3.internal:3000 route=ws3
        ProxySet lbmethod=byrequests
        ProxySet stickysession=ROUTEID
    </Proxy>
    
    <Proxy balancer://httpcluster>
        BalancerMember http://ws1.internal:3000 route=ws1
        BalancerMember http://ws2.internal:3000 route=ws2
        BalancerMember http://ws3.internal:3000 route=ws3
        ProxySet lbmethod=byrequests
        ProxySet stickysession=ROUTEID
    </Proxy>
    
    RewriteEngine On
    RewriteCond %{HTTP:Upgrade} ^websocket$ [NC]
    RewriteCond %{HTTP:Connection} ^(.*,s*)?[Uu]pgrade(,.*)?$ [NC]
    RewriteRule ^/(.*) balancer://wscluster/$1 [P,L]
    
    ProxyPass / balancer://httpcluster/
    ProxyPassReverse / balancer://httpcluster/
    
    # Cookie uzerinden sticky session
    Header add Set-Cookie "ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/" env=BALANCER_ROUTE_CHANGED
    
    ProxyTimeout 3600
    
    ErrorLog ${APACHE_LOG_DIR}/ws-lb-error.log
    CustomLog ${APACHE_LOG_DIR}/ws-lb-access.log combined
</VirtualHost>

Yaygın Sorunlar ve Çözümleri

400 Bad Request Hatası

Bu en sık karşılaşılan sorundur. WebSocket el sıkışması (handshake) sırasında Apache doğru başlıkları iletmiyordur. mod_proxy_wstunnel yüklü olmadığında veya RewriteRule’lar yanlış sıralandığında bu hata alınır. Kontrol etmek için:

# Apache error logunu canli izle
sudo tail -f /var/log/apache2/error.log

# WebSocket el sikismasini test et (wscat gerekli)
npm install -g wscat
wscat -c wss://chat.orneksite.com/socket

# HTTP upgrade basliklarini kontrol et
curl -i -N 
  -H "Connection: Upgrade" 
  -H "Upgrade: websocket" 
  -H "Sec-WebSocket-Version: 13" 
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" 
  https://chat.orneksite.com/socket

Bağlantı Zaman Aşımı Sorunları

WebSocket bağlantıları uzun süre açık kalabilir. Sistem seviyesinde de bazı ayarlar gerekebilir:

# Apache'nin KeepAlive ayarlarini kontrol et
grep -r "KeepAlive|Timeout" /etc/apache2/apache2.conf

# /etc/apache2/apache2.conf veya ilgili vhost dosyasina ekle
# KeepAliveTimeout 65
# Timeout 3600

# TCP keepalive ayarlari - uzun surecli WS baglantilari icin
echo 'net.ipv4.tcp_keepalive_time = 600' >> /etc/sysctl.conf
echo 'net.ipv4.tcp_keepalive_intvl = 60' >> /etc/sysctl.conf
echo 'net.ipv4.tcp_keepalive_probes = 10' >> /etc/sysctl.conf
sysctl -p

mod_proxy_wstunnel Yüklü Olup Olmadığını Doğrulama

# Modülün yüklü olup olmadigini kesin olarak dogrula
apache2ctl -M 2>/dev/null | grep -i "proxy_wstunnel"

# Cikti bu olmali:
# proxy_wstunnel_module (shared)

# Yoksa yukle
sudo a2enmod proxy_wstunnel
sudo systemctl reload apache2

# Apache'nin WebSocket baglantisini nasil isledigini gormek icin debug log
sudo sed -i 's/LogLevel warn/LogLevel debug/' /etc/apache2/apache2.conf
sudo systemctl reload apache2
sudo tail -f /var/log/apache2/error.log | grep -i websocket

Güvenlik Yapılandırması

WebSocket proxy kurulurken güvenliği de göz önünde bulundurmalısınız. Açık proxy olmamasına dikkat edin ve sadece belirlediğiniz backend’lere izin verin:

<VirtualHost *:443>
    ServerName chat.orneksite.com
    
    SSLEngine On
    SSLCertificateFile /etc/letsencrypt/live/chat.orneksite.com/fullchain.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/chat.orneksite.com/privkey.pem
    
    # Acik proxy'yi devre disi birak
    ProxyRequests Off
    
    # Sadece belirli originlere izin ver
    Header always set Access-Control-Allow-Origin "https://chat.orneksite.com"
    
    # Guvenlik headerlari
    Header always set X-Frame-Options "SAMEORIGIN"
    Header always set X-XSS-Protection "1; mode=block"
    Header always set X-Content-Type-Options "nosniff"
    
    # Rate limiting icin mod_ratelimit veya mod_evasive kullanin
    # Cok sayida WS baglantisi acmaya calisan IP'leri engelle
    <Location /socket>
        # Ayni IP'den max 100 eszamanli baglanti
        SetEnvIf Remote_Addr "^(.*)$" CLIENT_IP=$1
    </Location>
    
    RewriteEngine On
    
    # Sadece izin verilen originlerden gelen WS isteklerini kabul et
    RewriteCond %{HTTP:Origin} !^https://chat.orneksite.com$ [NC]
    RewriteCond %{HTTP:Upgrade} ^websocket$ [NC]
    RewriteRule .* - [F,L]
    
    RewriteCond %{HTTP:Upgrade} ^websocket$ [NC]
    RewriteCond %{HTTP:Connection} ^(.*,s*)?[Uu]pgrade(,.*)?$ [NC]
    RewriteRule ^/socket/(.*) ws://localhost:3000/socket/$1 [P,L]
    
    ProxyPass /socket/ http://localhost:3000/socket/
    ProxyPassReverse /socket/ http://localhost:3000/socket/
    
    ProxyTimeout 3600
</VirtualHost>

Yapılandırmayı Test Etme ve İzleme

Kurulumu tamamladıktan sonra her şeyin doğru çalıştığını doğrulamanız gerekiyor:

# Yapilandirma sozdizimi kontrolu
sudo apache2ctl configtest
# Cikti: Syntax OK olmali

# Servisi yeniden yükle
sudo systemctl reload apache2

# WebSocket baglantisini test et
wscat -c wss://chat.orneksite.com/socket

# Baglanti kuruldugunda JSON mesaj gonder
# {"type": "ping"}

# Apache durumunu izle - mod_status ile
# Oncelikle mod_status'u aktif edin
sudo a2enmod status

# /etc/apache2/mods-enabled/status.conf dosyasini duzenle
# ExtendedStatus On ekle

# Canli izleme
watch -n 2 'curl -s http://localhost/server-status?auto | grep -E "BusyWorkers|IdleWorkers|Uptime"'

# WebSocket baglanti sayisini izle
ss -tnp | grep apache2 | wc -l

Logları düzenli olarak izlemek için bir alias eklemek de hayatı kolaylaştırır:

# ~/.bashrc veya ~/.bash_aliases dosyasina ekle
alias ws-log='sudo tail -f /var/log/apache2/chat-error.log | grep -E "websocket|proxy|tunnel|upgrade"'
alias ws-conn='watch -n 5 "ss -tnp | grep -E "3000|ESTAB" | wc -l"'

source ~/.bashrc

Performans Optimizasyonu

Yüksek trafikli WebSocket uygulamalarında Apache worker MPM veya event MPM kullanmalısınız. Prefork MPM WebSocket için uygun değildir çünkü her bağlantı için bir process oluşturur:

# Mevcut MPM'i kontrol et
apache2ctl -V | grep MPM

# Event MPM'e gec (en iyi performans)
sudo a2dismod mpm_prefork
sudo a2enmod mpm_event
sudo systemctl restart apache2

# /etc/apache2/mods-available/mpm_event.conf
# <IfModule mpm_event_module>
#     StartServers             2
#     MinSpareThreads         25
#     MaxSpareThreads         75
#     ThreadLimit             64
#     ThreadsPerChild         25
#     MaxRequestWorkers      400
#     MaxConnectionsPerChild   0
# </IfModule>

Sonuç

Apache ile WebSocket proxy yapılandırması ilk bakışta karmaşık görünse de temel prensipler anlaşıldığında oldukça yönetilebilir hale geliyor. Özetlemek gerekirse en kritik noktalar şunlar:

  • mod_proxy_wstunnel mutlaka yüklü ve aktif olmalı
  • RewriteRule direktifleri ProxyPass‘tan önce gelmeli
  • ProxyTimeout değeri WebSocket uygulamanızın kullanım senaryosuna göre ayarlanmalı
  • Prodüksiyonda mutlaka WSS (WebSocket Secure) kullanılmalı
  • Yüksek bağlantı sayısı için event MPM tercih edilmeli
  • Load balancer senaryolarında sticky session zorunlu

En sık yapılan hata, yapılandırmanın tarayıcıda test edilmesi ama wscat gibi araçlarla gerçek WebSocket el sıkışmasının denenmemesidir. Bir sorunla karşılaştığınızda her zaman Apache error logunu debug seviyesinde incelemeye başlayın ve wscat ile doğrudan bağlantı testi yapın. Sorunların büyük çoğunluğu ya yüklü olmayan modülden ya da yanlış sıralanmış RewriteRule direktiflerinden kaynaklanıyor.

Yorum yapın