Nginx ile HTTP/2 Sorunları: Protokol Hatalarını Tespit ve Çözme

HTTP/2 protokolü, modern web altyapısının vazgeçilmez bir parçası haline geldi. Multiplexing, header compression ve server push gibi özellikler sayesinde sayfa yükleme sürelerini dramatik biçimde düşürüyor. Ancak Nginx üzerinde HTTP/2 yapılandırırken karşılaşılan protokol hataları, deneyimli sysadminleri bile çileden çıkarabiliyor. Bu yazıda gerçek dünya senaryoları üzerinden HTTP/2 sorunlarını nasıl tespit edip çözeceğini adım adım ele alacağız.

HTTP/2 Sorunlarının Temeli: Neyi Arıyoruz?

Nginx’te HTTP/2 sorunları genellikle sessiz sedasız ortaya çıkar. Kullanıcılar “site yavaş” ya da “bazen açılmıyor” diye şikayet eder, sen de logları açtığında net bir hata mesajı bulamazsın. İşin can sıkıcı yanı budur.

HTTP/2 hataları birkaç temel kategoride toplanır:

  • Protokol anlaşmazlığı (Protocol Negotiation Failure): İstemci ile sunucu HTTP/2 üzerinde anlaşamıyor
  • TLS yapılandırma sorunları: HTTP/2 HTTPS gerektirdiği için yanlış cipher suite veya eski TLS sürümleri
  • HPACK sıkıştırma hataları: Header sıkıştırma mekanizmasındaki tutarsızlıklar
  • Stream hataları: Çoklu stream yönetimindeki başarısızlıklar
  • Upstream proxy sorunları: Backend sunucularla HTTP/2 uyumsuzlukları

İlk yapacağın şey Nginx’in HTTP/2 modülünün aktif olup olmadığını kontrol etmek:

nginx -V 2>&1 | grep -o with-http_v2_module

Eğer çıktı boşsa HTTP/2 modülü derlenmemiş demektir. Ubuntu/Debian sistemlerde nginx-extras veya nginx-full paketine geçmen gerekir.

Log Yapılandırmasını Doğru Kurmak

HTTP/2 sorunlarını yakalamak için standart log formatı yetersiz kalır. Önce log formatını genişletmen gerekiyor.

http {
    log_format http2_debug '$remote_addr - $remote_user [$time_local] '
                           '"$request" $status $body_bytes_sent '
                           '"$http_referer" "$http_user_agent" '
                           'http2=$http2 '
                           'upstream_addr=$upstream_addr '
                           'upstream_status=$upstream_status '
                           'request_time=$request_time '
                           'upstream_response_time=$upstream_response_time '
                           'ssl_protocol=$ssl_protocol '
                           'ssl_cipher=$ssl_cipher';

    access_log /var/log/nginx/access.log http2_debug;
    error_log /var/log/nginx/error.log debug;
}

Bu yapılandırmada $http2 değişkeni kritik öneme sahiptir. Eğer bir istek HTTP/2 üzerinden geldiyse bu değişken h2 değerini taşır, HTTP/1.1 üzerinden geldiyse boş kalır. Logları incelediğinde hangi isteklerin gerçekten HTTP/2 kullandığını bu sayede anlarsın.

Şimdi bu logları gerçek zamanlı izlemek için:

tail -f /var/log/nginx/access.log | awk '$0 ~ /http2=/ {print}'

# Sadece HTTP/2 olmayan istekleri görmek için
tail -f /var/log/nginx/access.log | awk '$0 !~ /http2=h2/ {print}'

# HTTP/2 oranını hesaplamak için
awk '{if ($0 ~ /http2=h2/) h2++; else h1++} END {print "HTTP/2:", h2, "HTTP/1.1:", h1}' /var/log/nginx/access.log

Senaryo 1: ALPN Müzakeresi Başarısız

Gerçek bir vakadan bahsedeyim. Bir e-ticaret sitesinin Nginx’ini HTTP/2’ye geçirdik. Yapılandırma doğru görünüyordu ama Chrome DevTools’ta her şey HTTP/1.1 olarak geliyordu. Hiçbir hata yoktu, sadece HTTP/2 çalışmıyordu.

Sorunun kaynağı ALPN (Application-Layer Protocol Negotiation) müzakeresidir. TLS el sıkışması sırasında istemci ve sunucu hangi protokolü kullanacaklarını ALPN üzerinden kararlaştırır. Bunu test etmek için:

# OpenSSL ile ALPN müzakeresini test et
openssl s_client -connect example.com:443 -alpn h2 2>&1 | grep -E "ALPN|Protocol"

# Daha detaylı TLS bilgisi için
openssl s_client -connect example.com:443 -alpn h2 -status 2>&1 | head -50

# curl ile HTTP/2 bağlantısını zorla
curl -v --http2 https://example.com 2>&1 | grep -E "< HTTP|ALPN|protocol"

Bu vakada sorun şuydu: Nginx’in önüne bir donanım load balancer koyulmuştu ve bu cihaz SSL termination yapıyordu. Nginx’e HTTP olarak yönlendirdiği için ALPN müzakeresi hiç Nginx’e ulaşmıyordu. Çözüm Nginx yapılandırmasında şu direktifi eklemekti:

server {
    listen 443 ssl;
    http2 on;  # Nginx 1.25.1+ için yeni syntax
    
    # Eski syntax (1.25.1 öncesi)
    # listen 443 ssl http2;
    
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    
    # HTTP/2 için kritik: HSTS ve diğer güvenlik başlıkları
    add_header Strict-Transport-Security "max-age=63072000" always;
}

Önemli not: Nginx 1.25.1 sürümüyle birlikte http2 on direktifi ayrı bir direktif olarak kullanılmaya başlandı. listen 443 ssl http2 sözdizimi deprecated sayılıyor. Nginx sürümünü kontrol etmeyi unutma:

nginx -v

Senaryo 2: Upstream Proxy ve HTTP/2 Çakışması

HTTP/2 sorunlarının en sinsi versiyonu upstream ile yaşanan çakışmalardır. Nginx bir reverse proxy olarak kullanıldığında, frontend’de HTTP/2 aktif olsa bile backend bağlantıları genellikle HTTP/1.1 üzerinden kurulur. Bu çoğu zaman sorun çıkarmaz ancak bazı durumlarda header boyutları veya bağlantı yönetimi sorun yaratır.

upstream backend {
    server 127.0.0.1:8080;
    keepalive 32;
    keepalive_requests 1000;
    keepalive_timeout 60s;
}

server {
    listen 443 ssl;
    http2 on;
    
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # Buffer ayarları HTTP/2 multiplexing için önemli
        proxy_buffering on;
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
    }
}

proxy_set_header Connection "" satırı kritiktir. Bu olmadan HTTP/1.0 connection semantiği devreye girer ve her istek için yeni bağlantı açılır, bu da HTTP/2’nin multiplexing avantajını tamamen yok eder.

Upstream sorunlarını tespit etmek için error logunu incele:

# Upstream bağlantı hatalarını filtrele
grep -E "upstream|connect|timed out" /var/log/nginx/error.log | tail -100

# 502 ve 504 hatalarını access logdan çek
awk '$9 == "502" || $9 == "504"' /var/log/nginx/access.log | tail -50

Senaryo 3: Header Boyut Sorunları ve HPACK Hataları

HTTP/2’de header sıkıştırma için HPACK algoritması kullanılır. Büyük cookie’ler veya uzun authorization token’ları taşıyan istekler Nginx’in varsayılan buffer limitlerini aşabilir. Bu durumda istemci ERR_HTTP2_PROTOCOL_ERROR alır.

# Error logda HPACK veya header hatalarını ara
grep -iE "http2|h2|header|hpack|RST_STREAM" /var/log/nginx/error.log

# Örnek hata çıktısı şuna benzer:
# [error] 1234#0: *567 http2 request: peer closed stream
# [warn] 1234#0: *567 http2: client sent invalid header

Çözüm için Nginx’in HTTP/2 buffer ayarlarını düzenle:

http {
    # HTTP/2 spesifik ayarlar
    http2_max_field_size 16k;      # Tek bir header alanının max boyutu
    http2_max_header_size 32k;     # Tüm header bloğunun max boyutu
    http2_max_requests 1000;       # Bağlantı başına max istek sayısı
    http2_idle_timeout 3m;         # Boşta bağlantı timeout
    http2_recv_buffer_size 256k;   # Receive buffer boyutu
    
    # Large client header için
    large_client_header_buffers 4 32k;
    client_header_buffer_size 8k;
}

Bu değerleri körü körüne artırma. Önce gerçek header boyutlarını ölç:

# Ortalama request header boyutunu hesapla
awk '{
    match($0, /"[A-Z]+ [^ ]+ HTTP/[0-9.]+"/);
    if (RSTART) {
        len = length(substr($0, RSTART, RLENGTH));
        total += len;
        count++;
    }
} END {
    print "Ortalama header boyutu:", total/count, "bytes"
}' /var/log/nginx/access.log

HTTP/2 Push Sorunları ve Çözümleri

Server push özelliği teoride harika, pratikte sorun çıkarıcı. Özellikle CDN veya cache katmanları arasında push direktifleri çakışmalara yol açabilir. Modern tarayıcılar zaten server push’u kaldırma yoluna gidiyor (Chrome 106’dan itibaren desteği kaldırdı), bu yüzden aktif kullandıysan kaldırmanı öneririm:

server {
    listen 443 ssl;
    http2 on;
    
    # Server push'u tamamen devre dışı bırak
    location / {
        # http2_push direktifini kullanmıyoruz
        # Preload header'larından push üretme
        http2_push_preload off;
    }
}

Sorun Tespiti İçin Kapsamlı Test Scripti

Tüm bu kontrolleri tek bir script’e toparlamak zaman kazandırır. Aşağıdaki scripti sunucuna koy ve düzenli çalıştır:

#!/bin/bash
# nginx_http2_check.sh - HTTP/2 sorun tespit scripti

DOMAIN="${1:-example.com}"
LOG_FILE="/var/log/nginx/access.log"
ERROR_LOG="/var/log/nginx/error.log"

echo "=== Nginx HTTP/2 Diagnostic Tool ==="
echo "Domain: $DOMAIN"
echo ""

# 1. Nginx HTTP/2 modül kontrolü
echo "--- Modul Kontrolu ---"
nginx -V 2>&1 | grep -o "with-http_v2_module" || echo "HATA: HTTP/2 modulu yok!"
echo ""

# 2. ALPN kontrolü
echo "--- ALPN Muzakeresi ---"
ALPN_RESULT=$(echo | openssl s_client -connect "$DOMAIN:443" -alpn h2 2>&1 | grep "ALPN protocol")
echo "$ALPN_RESULT"
echo ""

# 3. HTTP/2 oranı
echo "--- HTTP/2 Kullanim Orani (Son 1000 Istek) ---"
tail -1000 "$LOG_FILE" | awk '{
    if ($0 ~ /http2=h2/) h2++;
    else h1++;
    total++;
} END {
    printf "HTTP/2: %d (%.1f%%)nHTTP/1.1: %d (%.1f%%)n",
    h2, (h2/total)*100, h1, (h1/total)*100
}'
echo ""

# 4. Son hataları kontrol et
echo "--- Son HTTP/2 Hatalari ---"
grep -iE "http2|h2_stream|RST_STREAM|GOAWAY" "$ERROR_LOG" | tail -10
echo ""

# 5. TLS versiyonu dağılımı
echo "--- TLS Versiyon Dagilimi ---"
awk '{match($0, /ssl_protocol=([^ ]+)/, arr); if (arr[1]) print arr[1]}' "$LOG_FILE" | 
    sort | uniq -c | sort -rn
echo ""

# 6. En çok hata veren endpoint'ler
echo "--- En Cok 5xx Hata Veren Endpoint'ler ---"
awk '$9 >= 500 {print $7}' "$LOG_FILE" | sort | uniq -c | sort -rn | head -10
echo ""

echo "=== Tani Tamamlandi ==="

Script’i çalıştırılabilir yap ve kullan:

chmod +x nginx_http2_check.sh
./nginx_http2_check.sh example.com

Wireshark ve tcpdump ile Derin İnceleme

Bazen log analizi yetmez, paket düzeyinde inceleme yapmak gerekir. Özellikle TLS içinde HTTP/2 sorunlarını yakalamak için:

# Belirli bir IP'den gelen trafiği yakala
tcpdump -i eth0 -w /tmp/http2_capture.pcap host 192.168.1.100 and port 443

# Nginx'in bağlandığı upstream trafiğini yakala
tcpdump -i lo -w /tmp/upstream_capture.pcap port 8080

# SSLKEYLOGFILE ile TLS şifresini çöz (geliştirme ortamında)
export SSLKEYLOGFILE=/tmp/ssl_keys.log
curl --http2 -v https://example.com

Pcap dosyasını Wireshark’a atıp tcp.port == 443 filtresiyle HTTP/2 frame’lerini inceleyebilirsin. RST_STREAM frame’leri ve GOAWAY mesajları sorunun tam yerini gösterir.

Performans Tuning: HTTP/2’yi Optimize Etmek

Sorunları çözdükten sonra HTTP/2 performansını en üst seviyeye taşımak için:

http {
    # TCP optimizasyonları
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    
    # Keepalive ayarları
    keepalive_timeout 65;
    keepalive_requests 1000;
    
    # HTTP/2 özel ayarlar
    http2_max_concurrent_streams 128;
    http2_chunk_size 8k;
    
    # Gzip yerine brotli kullan (HTTP/2 ile daha iyi çalışır)
    # brotli on;
    # brotli_comp_level 6;
    
    # SSL session cache
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;
    
    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;
}

HTTP/2 multiplexing’in düzgün çalışması için worker process ve connection ayarları da kritik:

worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

Sık Karşılaşılan Hata Mesajları ve Anlamları

Error logda göreceğin yaygın mesajları deşifre edelim:

  • peer closed stream: İstemci stream’i beklenmedik şekilde kapattı. Genellikle istemci taraflı bir sorun ya da timeout.
  • client sent GOAWAY: İstemci bağlantıyı kapatmak istediğini bildirdi. Sayfa yüklenirken kullanıcı sekmeyi kapattıysa normaldir.
  • upstream sent invalid header: Backend geçersiz header döndürüyor. proxy_pass_header ve proxy_hide_header direktiflerini kontrol et.
  • SSL_do_handshake() failed: TLS el sıkışması başarısız. Cipher suite ve sertifika geçerliliğini kontrol et.
  • no shared cipher: İstemci ve sunucu ortak cipher suite bulamadı. ssl_ciphers direktifini gözden geçir.
# Bu hataların frekansını ölç
grep -oP '] K[^:]+(?=:)' /var/log/nginx/error.log | sort | uniq -c | sort -rn | head -20

Monitoring ve Alerting

Sorunları proaktif yakalamak için Nginx stub status modülünü etkinleştir ve Prometheus ile entegre et:

server {
    listen 127.0.0.1:8080;
    
    location /nginx_status {
        stub_status on;
        access_log off;
        allow 127.0.0.1;
        deny all;
    }
}
# Anlık bağlantı durumunu izle
watch -n 2 'curl -s http://127.0.0.1:8080/nginx_status'

# HTTP/2 bağlantılarını ss komutuyla izle
ss -tnp | grep nginx | awk '{print $1, $2, $4, $5}' | column -t

# 5 dakikada bir HTTP/2 oranını logla
*/5 * * * * /usr/local/bin/nginx_http2_check.sh >> /var/log/nginx_http2_stats.log 2>&1

Sonuç

HTTP/2 sorunları sinsidir çünkü çoğu zaman sessizce HTTP/1.1’e düşer ve sen farkına bile varmazsın. Temel yaklaşım şu üç adımda özetlenebilir: önce doğru log formatıyla görünürlüğü artır, sonra ALPN ve TLS yapılandırmasını kontrol et, ardından upstream proxy zincirindeki her noktayı tek tek doğrula.

En sık yapılan hatalar şunlar: eski Nginx sürümü, yanlış listen direktifi syntax’ı, eksik proxy_set_header Connection "" satırı ve upstream buffer boyutlarının küçük kalması. Bu dört noktayı doğru yapılandırdığında HTTP/2 sorunlarının büyük çoğunluğu ortadan kalkar.

Bir de şunu söyleyelim: HTTP/2 sorunlarını çözerken sabırlı ol. Tarayıcı cache’ini temizle, incognito modda test et ve her zaman curl --http2 -v ile doğrula. Tarayıcıların HTTP/2 fallback mekanizmaları bazen gerçek sorunu gizleyebilir. Curl asla yalan söylemez.

Bir yanıt yazın

E-posta adresiniz yayınlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir