Nginx ile SSL/TLS Yapılandırması ve HTTPS Zorlama

Web sunucunuzu internete açtığınız anda güvenlik sorumluluğunuz da başlıyor. Özellikle kullanıcı verisi işleyen, form alan ya da oturum yöneten her uygulama için HTTPS artık bir tercih değil, zorunluluk. Nginx, bu konuda hem performans hem de esneklik açısından en güçlü seçeneklerden biri. Bu yazıda sıfırdan SSL/TLS yapılandırması yapacak, modern şifreleme standartlarını uygulayacak ve HTTP trafiğini HTTPS’e zorla yönlendireceksiniz.

SSL Sertifikası Edinme

Yapılandırmaya geçmeden önce elinizde geçerli bir SSL sertifikasının olması gerekiyor. İki ana yol var: Let’s Encrypt ile ücretsiz sertifika almak ya da ticari bir CA’dan satın almak. Büyük çoğunluk için Let’s Encrypt yeterli, hatta ideal.

Certbot ile Let’s Encrypt Sertifikası

# Ubuntu/Debian
sudo apt update
sudo apt install certbot python3-certbot-nginx

# CentOS/RHEL
sudo dnf install certbot python3-certbot-nginx

# Sertifika alma (Nginx eklentisi ile otomatik yapılandırma)
sudo certbot --nginx -d example.com -d www.example.com

# Sadece sertifika al, Nginx'i elle yapılandır
sudo certbot certonly --nginx -d example.com -d www.example.com

# Webroot yöntemi (çalışan bir HTTP sunucusu varsa)
sudo certbot certonly --webroot -w /var/www/html -d example.com

Certbot başarılı olursa sertifikalarınız /etc/letsencrypt/live/example.com/ altına yerleşir. Bu dizinde dört dosya bulunur:

  • fullchain.pem: Sertifikanız + ara CA sertifikaları (Nginx’e bu dosyayı verirsiniz)
  • privkey.pem: Özel anahtarınız (kimseyle paylaşmayın, izinleri kontrol edin)
  • cert.pem: Sadece domain sertifikanız
  • chain.pem: Sadece ara CA zinciri

Ticari Sertifika için CSR Oluşturma

Let’s Encrypt yerine ticari sertifika kullanacaksanız önce bir CSR (Certificate Signing Request) oluşturmanız gerekiyor:

# Özel anahtar oluştur
openssl genrsa -out /etc/nginx/ssl/example.com.key 4096

# CSR oluştur
openssl req -new -key /etc/nginx/ssl/example.com.key 
  -out /etc/nginx/ssl/example.com.csr 
  -subj "/C=TR/ST=Istanbul/L=Istanbul/O=Sirket Adi/CN=example.com"

# CSR içeriğini kontrol et
openssl req -text -noout -verify -in /etc/nginx/ssl/example.com.csr

CSR dosyasını CA’ya gönderiyorsunuz, onlar size .crt uzantılı sertifikayı geri dönüyor. Ara sertifikaları da birleştirmeniz gerekebilir:

# Sertifikaları birleştir (önce domain sertifikası, sonra zincir)
cat example.com.crt intermediate.crt root.crt > /etc/nginx/ssl/example.com_fullchain.crt

Temel HTTPS Yapılandırması

Sertifikalarınız hazırsa Nginx konfigürasyonuna geçelim. Tipik bir yapılandırma iki server bloğundan oluşur: biri HTTP’yi HTTPS’e yönlendirmek için, diğeri gerçek HTTPS trafiğini işlemek için.

# /etc/nginx/sites-available/example.com

# HTTP -> HTTPS yönlendirme
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # Let's Encrypt doğrulama için izin ver
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    # Geri kalan her şeyi HTTPS'e yönlendir
    location / {
        return 301 https://$host$request_uri;
    }
}

# HTTPS ana blok
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com www.example.com;

    # Sertifika dosyaları
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    root /var/www/example.com;
    index index.html index.php;

    location / {
        try_files $uri $uri/ =404;
    }
}

Konfigürasyonu test edip yükleyin:

sudo nginx -t
sudo systemctl reload nginx

Bu temel yapılandırma çalışır ama henüz güvenli değil. TLS sürüm kısıtlaması, şifreleme suite seçimi ve güvenlik başlıkları olmadan bu yapılandırma birçok güvenlik açığına karşı savunmasız kalır.

Güçlü TLS Yapılandırması

Modern bir TLS yapılandırması için önerilen ayarları ayrı bir include dosyasına almak, birden fazla site yönetirken hayat kurtarır. SSL parametrelerini merkezi bir dosyada tutup her server bloğunda include edersiniz.

sudo mkdir -p /etc/nginx/ssl
sudo openssl dhparam -out /etc/nginx/ssl/dhparam.pem 4096

DH parametresi oluşturma biraz uzun sürebilir, bu normal. Şimdi merkezi SSL yapılandırma dosyasını oluşturalım:

# /etc/nginx/ssl/ssl_params.conf

# TLS protokol sürümleri - TLS 1.0 ve 1.1 kesinlikle devre dışı
ssl_protocols TLSv1.2 TLSv1.3;

# Şifreleme suite'leri - zayıf algoritmalar dışarıda
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;

# Sunucu şifreleme tercihini aktif et
ssl_prefer_server_ciphers off;

# DH parametreleri (Forward Secrecy için)
ssl_dhparam /etc/nginx/ssl/dhparam.pem;

# SSL session cache (performans için)
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;

Bu dosyayı her site konfigürasyonuna dahil edin:

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com www.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Merkezi SSL parametrelerini dahil et
    include /etc/nginx/ssl/ssl_params.conf;

    # Güvenlik başlıkları
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;

    root /var/www/example.com;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

HSTS ve Preload

Strict-Transport-Security başlığı, tarayıcıya “bu siteye bir daha HTTP ile bağlanma” mesajı verir. Yanlış yapılandırılırsa sitenize erişimi engelleyebilir, dikkatli olun.

HSTS’yi aşamalı olarak devreye alın:

  • İlk aşama: max-age=300 ile test edin, 5 dakika
  • İkinci aşama: max-age=86400 (1 gün), sorun yoksa devam
  • Üçüncü aşama: max-age=2592000 (30 gün)
  • Final: max-age=31536000; includeSubDomains; preload

preload direktifini eklemeden önce tüm subdomain’lerinizin HTTPS desteğinin olduğundan emin olun. Sonra [hstspreload.org](https://hstspreload.org) adresine başvurarak sitenizi tarayıcıların HSTS preload listesine ekletebilirsiniz.

OCSP Stapling

OCSP Stapling, sertifika iptal kontrolünü hızlandırır. Normalde tarayıcı her bağlantıda CA’nın OCSP sunucusuna gidip sertifikanın geçerli olup olmadığını sorar. OCSP Stapling ile Nginx bu kontrolü periyodik olarak kendisi yapar ve yanıtı “zımbalar”, tarayıcıya doğrudan sunar. Hem gizlilik hem de performans açısından avantajlı.

# OCSP Stapling'in çalışıp çalışmadığını test et
echo | openssl s_client -connect example.com:443 -status 2>/dev/null | grep -A 10 "OCSP Response"

# Daha detaylı test
openssl s_client -connect example.com:443 -tls1_2 -tlsextdebug -status < /dev/null 2>&1 | grep -i ocsp

Sertifika Yenileme Otomasyonu

Let’s Encrypt sertifikaları 90 günde bir yenilenmesi gerekiyor. Manuel yapmak istemiyorsunuz, Certbot’un timer’ı zaten yenilemeyi hallediyor ama Nginx’i de yenileme sonrası reload etmeniz lazım.

# Mevcut otomatik yenileme timer'ını kontrol et
sudo systemctl status certbot.timer

# Yenileme hook'u oluştur
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy

sudo tee /etc/letsencrypt/renewal-hooks/deploy/nginx-reload.sh << 'EOF'
#!/bin/bash
systemctl reload nginx
echo "$(date): Nginx reloaded after cert renewal" >> /var/log/letsencrypt/nginx-reload.log
EOF

sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/nginx-reload.sh

# Yenileme işlemini simüle et (gerçek yenileme yapmaz)
sudo certbot renew --dry-run

Cron ile de yönetmek isterseniz:

# Crontab'a ekle
sudo crontab -e

# Her gün sabah 3'te kontrol et
0 3 * * * certbot renew --quiet --deploy-hook "systemctl reload nginx"

Çoklu Domain Yapılandırması

Birden fazla domain yönetiyorsanız her biri için ayrı server bloğu oluşturup aynı SSL parametrelerini include edin. Wildcard sertifika kullanıyorsanız DNS doğrulaması gerekiyor:

# Wildcard sertifika (DNS doğrulaması gerekli)
sudo certbot certonly --manual --preferred-challenges dns 
  -d example.com -d "*.example.com"

Wildcard sertifika ile birden fazla subdomain’i tek yapılandırmada yönetebilirsiniz:

# /etc/nginx/sites-available/subdomains.conf

# api.example.com
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name api.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/nginx/ssl/ssl_params.conf;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location / {
        proxy_pass http://localhost:3000;
        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;
    }
}

# app.example.com
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name app.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/nginx/ssl/ssl_params.conf;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    location / {
        proxy_pass http://localhost:8080;
        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;
    }
}

SSL Yapılandırmasını Test Etme

Yapılandırmanızı canlıya almadan ve aldıktan sonra mutlaka test edin. Hem komut satırından hem de online araçlarla kontrol yapabilirsiniz.

# Temel SSL bağlantı testi
openssl s_client -connect example.com:443 -servername example.com

# Desteklenen protokolleri test et
openssl s_client -connect example.com:443 -tls1
openssl s_client -connect example.com:443 -tls1_1
openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3

# Sertifika geçerlilik tarihi
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null | openssl x509 -noout -dates

# Şifreleme suite'ini görüntüle
echo | openssl s_client -connect example.com:443 2>/dev/null | grep Cipher

# nmap ile kapsamlı SSL taraması
nmap --script ssl-enum-ciphers -p 443 example.com

# HTTP güvenlik başlıklarını kontrol et
curl -I https://example.com

Online test araçları arasında SSL Labs (ssllabs.com/ssltest) en kapsamlı olanı. A+ puan almak için HSTS, güçlü şifreleme suite’leri ve güncel TLS sürümleri yeterli. Ayrıca securityheaders.com güvenlik başlıklarınızı değerlendiriyor.

Gerçek Dünya Senaryosu: E-ticaret Sitesi

Bir e-ticaret projesi üzerinde çalıştığımı ve PCI-DSS uyumluluğu gerektiren bir yapılandırma kurmam gerektiğini düşünelim. TLS 1.0 ve 1.1 kesinlikle yasak, session ticket’lar kapalı olmalı, forward secrecy zorunlu.

# /etc/nginx/sites-available/shop.example.com

server {
    listen 80;
    server_name shop.example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name shop.example.com;

    ssl_certificate /etc/letsencrypt/live/shop.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/shop.example.com/privkey.pem;
    include /etc/nginx/ssl/ssl_params.conf;

    # PCI-DSS uyumlu güvenlik başlıkları
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' https://js.stripe.com; frame-src https://js.stripe.com; connect-src 'self' https://api.stripe.com;" always;

    # Hassas dosyaları gizle
    location ~ /. {
        deny all;
    }

    location ~* .(env|log|sql|bak)$ {
        deny all;
    }

    # Uygulama
    location / {
        proxy_pass http://localhost:5000;
        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 https;
        proxy_set_header X-Forwarded-Port 443;
    }
}

Yaygın Sorunlar ve Çözümleri

Mixed Content Uyarısı: Sayfa HTTPS üzerinden geliyor ama içindeki resimler, scriptler HTTP’den yükleniyorsa tarayıcı uyarı verir. Nginx seviyesinde Content-Security-Policy: upgrade-insecure-requests başlığı ekleyerek bunu kısmen aşabilirsiniz. Asıl çözüm uygulama seviyesinde kaynakları düzeltmek.

Sertifika Zinciri Eksikliği: Tarayıcı “sertifika güvenilir değil” diyorsa büyük ihtimalle fullchain.pem yerine sadece cert.pem kullanıyorsunuzdur. Daima fullchain.pem kullanın.

SNI Sorunları: Aynı IP’de birden fazla SSL domain çalıştırıyorsanız ve eski istemci sorunları yaşıyorsanız, SNI (Server Name Indication) desteği olmayan eski SSL istemciler var demektir. Nginx varsayılan olarak SNI destekliyor, sorun istemci tarafında.

# Hangi sertifikanın sunulduğunu kontrol et
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject

# Nginx'in SSL modülünü doğrula
nginx -V 2>&1 | grep ssl

Sertifika Süresi Dolmuş Uyarısı: Otomatik yenileme çalışmıyorsa şunu kontrol edin:

# Timer aktif mi?
systemctl is-active certbot.timer

# Son yenileme ne zaman olmuş?
sudo certbot renew --dry-run

# Sertifika bitiş tarihlerini listele
sudo certbot certificates

Nginx SSL Performans Optimizasyonu

SSL handshake işlemi CPU yoğun bir işlemdir. Binlerce eş zamanlı bağlantı söz konusu olduğunda birkaç optimizasyon büyük fark yaratır.

# nginx.conf içinde http bloğuna ekle
http {
    # SSL session cache (birden fazla worker arasında paylaşılan)
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # Keep-alive bağlantıları (her request için yeni handshake yapmamak için)
    keepalive_timeout 65;
    keepalive_requests 100;

    # Buffer boyutları
    ssl_buffer_size 4k;
}

TLS 1.3’ün 0-RTT (Zero Round Trip Time Resumption) özelliği ilk bağlantı kurma süresini ciddi ölçüde azaltıyor. Nginx 1.15.4+ sürümünde TLS 1.3 tam desteği var, sisteminizi güncel tutun.

Sonuç

Nginx ile SSL/TLS yapılandırması göründüğü kadar karmaşık değil ama detaylar önemli. Sadece sertifika ekleyip 443 portu açmak yetmiyor; TLS protokol sürümlerini kısıtlamak, güçlü şifreleme suite’leri seçmek, güvenlik başlıklarını doğru yapılandırmak ve otomatik yenilemeyi kurmak da işin parçası.

Özetle yapmanız gerekenler şunlar: TLS 1.0/1.1’i devre dışı bırakın, TLS 1.2 ve 1.3 kullanın, zayıf şifreleme algoritmaları olan RC4, DES, 3DES’i dışlayın, HSTS başlığını ekleyin, OCSP Stapling’i aktif edin, sertifika yenilemeyi otomatikleştirin ve düzenli olarak SSL Labs ile test edin. Bu adımları tamamladığınızda hem A+ puanı alacak hem de gerçekten güvenli bir yapıya sahip olacaksınız.

Yapılandırmayı bir kez kurmak yetmez. Sertifika tarihlerini, TLS protokol güncellemelerini ve yeni güvenlik açıklarını takip etmek de sysadmin rutininizin parçası olmalı. ssl_protocols satırını bir kez yazıp unutmak yerine, her birkaç ayda bir güncel tavsiyelere bakın, OpenSSL sürümünüzü güncel tutun.

Yorum yapın