HTTPS Zorlama: API Trafiğini Şifreleme ve Güvenli Hale Getirme
Üretim ortamında çalışan bir API’ye baktığınızda, trafiğin şifrelenmeden aktığını gördüğünüzde içiniz sıkışır. Bu his tanıdık geliyorsa, yalnız değilsiniz. HTTPS zorlama, kulağa basit gibi gelir ama yanlış yapıldığında hem güvenlik açığı bırakır hem de uygulamalarınızı kırabilir. Bugün bu konuyu her açıdan ele alacağız.
Neden HTTPS Zorlama Bu Kadar Önemli?
HTTP üzerinden giden API trafiği, ağdaki herhangi bir nokta tarafından okunabilir ve değiştirilebilir. Özellikle REST API’lerde Authorization header’ları, Bearer token’lar veya API key’ler düz metin olarak gider. Bir man-in-the-middle saldırısı için bu biçilmiş kaftan.
Şöyle düşünün: Müşterinizin mobil uygulaması, ödeme bilgilerini API’nize gönderiyor. Eğer bu trafik HTTP üzerinden gidiyorsa, aynı Wi-Fi ağındaki biri Wireshark açıp tüm bu veriyi görebilir. HTTPS zorlama burada devreye girer ve HTTP üzerinden gelen her isteği ya HTTPS’e yönlendirir ya da tamamen reddeder.
API güvenliği söz konusu olduğunda iki yaklaşım var:
- Yönlendirme (301/302 Redirect): HTTP isteği geldiğinde HTTPS’e yönlendir
- Reddetme (403/400): HTTP isteği geldiğinde direkt hata döndür
API trafiği için ikinci yaklaşımı tercih ediyorum. Neden? Çünkü bir API istemcisi eğer HTTP kullanıyorsa, zaten yanlış bir şeyler yapıyor demektir. Bunu sessizce düzeltmek yerine gürültülü bir şekilde başarısız kılmak, geliştirici hatalarını erkenden yakalamanızı sağlar.
Nginx ile HTTPS Zorlama
Nginx, API gateway olarak kullanılan en yaygın web sunucularından biri. Temel yapılandırmadan başlayalım.
Temel HTTP’den HTTPS’e Yönlendirme
# /etc/nginx/sites-available/api.example.com
server {
listen 80;
server_name api.example.com;
# Tüm HTTP trafiğini HTTPS'e yönlendir
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
# Modern SSL yapılandırması
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
location /api/ {
proxy_pass http://backend_servers;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
}
}
API İçin Sert HTTPS Zorlama
API trafiğinde yönlendirme yerine reddetme yaklaşımı için:
server {
listen 80;
server_name api.example.com;
# HTTP üzerinden gelen API isteklerini reddet
location /api/ {
return 400 '{"error": "HTTPS required", "message": "Bu API sadece HTTPS üzerinden erişilebilir"}';
add_header Content-Type application/json;
}
# Health check için exception (monitoring araçları için)
location /health {
return 200 '{"status": "ok", "note": "Use HTTPS for API endpoints"}';
add_header Content-Type application/json;
}
}
HSTS Header Yapılandırması
HTTP Strict Transport Security, tarayıcılara ve bazı HTTP istemcilerine “bu domain’e sadece HTTPS ile bağlan” mesajı verir:
server {
listen 443 ssl http2;
server_name api.example.com;
# HSTS - 1 yıl, subdomain'leri de kapsa
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# Diğer güvenlik header'ları
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
# API güvenlik header'ları
add_header Cache-Control "no-store, no-cache, must-revalidate";
add_header Pragma no-cache;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Apache ile HTTPS Zorlama
Apache kullananlar için mod_rewrite ve mod_headers kombinasyonu:
# /etc/apache2/sites-available/api.example.com.conf
<VirtualHost *:80>
ServerName api.example.com
# API endpoint'leri için sert reddetme
<Location "/api/">
RewriteEngine On
RewriteRule ^ - [R=400,L]
Header always set Content-Type "application/json"
</Location>
# Diğer tüm HTTP trafiğini HTTPS'e yönlendir
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>
<VirtualHost *:443>
ServerName api.example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/api.example.com/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/api.example.com/privkey.pem
SSLCertificateChainFile /etc/letsencrypt/live/api.example.com/chain.pem
# Modern TLS ayarları
SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
SSLHonorCipherOrder off
# HSTS
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
ProxyPreserveHost On
ProxyPass /api/ http://127.0.0.1:8080/api/
ProxyPassReverse /api/ http://127.0.0.1:8080/api/
</VirtualHost>
Uygulama Katmanında HTTPS Zorlama
Nginx veya Apache’nin önünde bir load balancer olmadan direkt uygulama seviyesinde HTTPS zorlamak istiyorsanız, bunun nasıl yapıldığına bakalım.
Node.js Express ile
// middleware/httpsForce.js
const httpsForce = (req, res, next) => {
// X-Forwarded-Proto header'ını kontrol et (reverse proxy arkasında)
const proto = req.headers['x-forwarded-proto'];
const isHttps = proto === 'https' || req.secure;
if (!isHttps) {
// API istekleri için yönlendirme değil, hata döndür
if (req.path.startsWith('/api/')) {
return res.status(400).json({
error: 'HTTPS_REQUIRED',
message: 'API endpoint'leri sadece HTTPS üzerinden erişilebilir',
documentation: 'https://docs.example.com/security'
});
}
// Web arayüzü için yönlendirme
return res.redirect(301, `https://${req.headers.host}${req.url}`);
}
// HSTS header ekle
res.setHeader(
'Strict-Transport-Security',
'max-age=31536000; includeSubDomains'
);
next();
};
// app.js
const express = require('express');
const app = express();
// HTTPS zorlamayı en başa koy
app.use(httpsForce);
app.get('/api/v1/users', (req, res) => {
res.json({ users: [] });
});
Python FastAPI ile
# middleware/https_force.py
from fastapi import FastAPI, Request, Response
from fastapi.responses import JSONResponse
import uvicorn
app = FastAPI()
@app.middleware("http")
async def force_https(request: Request, call_next):
# Forwarded header'larını kontrol et
forwarded_proto = request.headers.get("x-forwarded-proto", "")
# Production'da HTTPS kontrolü
if forwarded_proto and forwarded_proto != "https":
# API path'leri için hata döndür
if request.url.path.startswith("/api/"):
return JSONResponse(
status_code=400,
content={
"error": "HTTPS_REQUIRED",
"message": "API endpoints require HTTPS connection",
"path": str(request.url.path)
}
)
response = await call_next(request)
# HSTS header ekle
response.headers["Strict-Transport-Security"] = (
"max-age=31536000; includeSubDomains"
)
return response
@app.get("/api/v1/health")
async def health_check():
return {"status": "healthy", "protocol": "https"}
if __name__ == "__main__":
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
# SSL sertifika yolları
ssl_keyfile="/etc/ssl/private/api.key",
ssl_certfile="/etc/ssl/certs/api.crt"
)
Sertifika Yönetimi ve Otomatizasyon
HTTPS zorlamanın en can sıkıcı kısmı sertifika yönetimidir. Let’s Encrypt ile bunu otomatize edelim:
#!/bin/bash
# ssl_renew.sh - Otomatik sertifika yenileme scripti
DOMAIN="api.example.com"
WEBROOT="/var/www/certbot"
LOG_FILE="/var/log/ssl_renew.log"
SLACK_WEBHOOK="https://hooks.slack.com/your-webhook-url"
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
}
notify_slack() {
curl -s -X POST "$SLACK_WEBHOOK"
-H 'Content-type: application/json'
--data "{"text": "SSL Sertifika: $1"}"
}
# Sertifika son kullanma tarihini kontrol et
EXPIRY_DATE=$(openssl x509 -enddate -noout
-in /etc/letsencrypt/live/$DOMAIN/cert.pem 2>/dev/null |
sed 's/notAfter=//')
if [ -z "$EXPIRY_DATE" ]; then
log "HATA: Sertifika dosyasi bulunamadi"
notify_slack "KRITIK: $DOMAIN sertifikasi bulunamadi!"
exit 1
fi
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s)
CURRENT_EPOCH=$(date +%s)
DAYS_REMAINING=$(( ($EXPIRY_EPOCH - $CURRENT_EPOCH) / 86400 ))
log "Kalan gun: $DAYS_REMAINING"
if [ $DAYS_REMAINING -lt 30 ]; then
log "Sertifika yenileniyor..."
certbot renew
--webroot
--webroot-path="$WEBROOT"
--cert-name "$DOMAIN"
--non-interactive
--agree-tos
--post-hook "systemctl reload nginx"
>> "$LOG_FILE" 2>&1
if [ $? -eq 0 ]; then
log "Sertifika basariyla yenilendi"
notify_slack "Basarili: $DOMAIN sertifikasi yenilendi ($DAYS_REMAINING gun kalmisti)"
else
log "HATA: Sertifika yenilenemedi"
notify_slack "KRITIK: $DOMAIN sertifika yenileme BASARISIZ!"
exit 1
fi
else
log "Yenileme gerekmiyor. $DAYS_REMAINING gun kaldi."
fi
Bu scripti crontab’a ekleyin:
# crontab -e
# Her gün sabah 3'te kontrol et
0 3 * * * /usr/local/bin/ssl_renew.sh
Gerçek Dünya Senaryosu: Mikroservis Ortamında HTTPS
Bir e-ticaret platformunda birden fazla mikroservis var diyelim. Her servis kendi API’sini expose ediyor. Bu durumda HTTPS zorlamayı merkezi bir noktada yapmak çok daha mantıklı.
# docker-compose.yml ile nginx reverse proxy örneği
# nginx/conf.d/api-gateway.conf
upstream payment_service {
server payment-api:8001;
keepalive 32;
}
upstream inventory_service {
server inventory-api:8002;
keepalive 32;
}
upstream user_service {
server user-api:8003;
keepalive 32;
}
# HTTP'yi tamamen kapat
server {
listen 80 default_server;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 400 '{"error": "HTTP_NOT_ALLOWED", "message": "Sadece HTTPS desteklenmektedir"}';
add_header Content-Type application/json always;
}
}
# API Gateway - HTTPS
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
# SSL güvenlik ayarları
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
add_header Strict-Transport-Security "max-age=63072000" always;
# Rate limiting
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=100r/m;
location /api/v1/payment/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://payment_service/;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
# Timeout ayarları
proxy_connect_timeout 10s;
proxy_read_timeout 30s;
}
location /api/v1/inventory/ {
limit_req zone=api_limit burst=50 nodelay;
proxy_pass http://inventory_service/;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
}
location /api/v1/users/ {
limit_req zone=api_limit burst=30 nodelay;
proxy_pass http://user_service/;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Real-IP $remote_addr;
}
}
HTTPS Yapılandırmasını Test Etme
Yapılandırmayı deploy ettikten sonra test etmek kritik önem taşır:
#!/bin/bash
# test_https.sh - API HTTPS yapılandırmasini test et
API_DOMAIN="api.example.com"
PASS=0
FAIL=0
check() {
local description="$1"
local condition="$2"
if eval "$condition"; then
echo "[PASS] $description"
((PASS++))
else
echo "[FAIL] $description"
((FAIL++))
fi
}
echo "=== HTTPS Yapılandırma Testi: $API_DOMAIN ==="
echo ""
# HTTP isteği 400 dönmeli (yönlendirme değil)
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}"
--max-time 10
"http://$API_DOMAIN/api/v1/test")
check "HTTP istegi 400 dondurdu (yonlendirme yok)"
'[ "$HTTP_STATUS" = "400" ]'
# HTTPS isteği çalışmalı
HTTPS_STATUS=$(curl -s -o /dev/null -w "%{http_code}"
--max-time 10
"https://$API_DOMAIN/api/v1/health")
check "HTTPS istegi basarili (200)"
'[ "$HTTPS_STATUS" = "200" ]'
# HSTS header mevcut olmalı
HSTS_HEADER=$(curl -s -I --max-time 10
"https://$API_DOMAIN/api/v1/health" |
grep -i "strict-transport-security")
check "HSTS header mevcut"
'[ -n "$HSTS_HEADER" ]'
# TLS 1.0 ve 1.1 reddedilmeli
TLS10_RESULT=$(curl -s -o /dev/null -w "%{http_code}"
--tls-max 1.0
"https://$API_DOMAIN/api/v1/health" 2>/dev/null)
check "TLS 1.0 reddedildi"
'[ "$TLS10_RESULT" = "000" ]'
# TLS 1.2 çalışmalı
TLS12_RESULT=$(curl -s -o /dev/null -w "%{http_code}"
--tlsv1.2 --tls-max 1.2
"https://$API_DOMAIN/api/v1/health" 2>/dev/null)
check "TLS 1.2 destekleniyor"
'[ "$TLS12_RESULT" = "200" ]'
# Sertifika geçerlilik kontrolü
CERT_VALID=$(curl -s -o /dev/null -w "%{http_code}"
"https://$API_DOMAIN/api/v1/health" 2>/dev/null)
check "SSL sertifikasi gecerli"
'[ "$CERT_VALID" = "200" ]'
# SSL Labs benzeri hızlı kontrol
CERT_EXPIRY=$(echo | openssl s_client -connect "$API_DOMAIN:443"
-servername "$API_DOMAIN" 2>/dev/null |
openssl x509 -noout -enddate 2>/dev/null |
sed 's/notAfter=//')
DAYS_LEFT=""
if [ -n "$CERT_EXPIRY" ]; then
EXPIRY_EPOCH=$(date -d "$CERT_EXPIRY" +%s)
CURRENT_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $CURRENT_EPOCH) / 86400 ))
fi
check "Sertifika 30+ gun gecerli"
'[ -n "$DAYS_LEFT" ] && [ "$DAYS_LEFT" -gt 30 ]'
echo ""
echo "=== Sonuc: $PASS passed, $FAIL failed ==="
if [ $CERT_EXPIRY ]; then
echo "Sertifika bitis tarihi: $CERT_EXPIRY ($DAYS_LEFT gun kaldi)"
fi
[ $FAIL -eq 0 ] && exit 0 || exit 1
Yaygın Sorunlar ve Çözümleri
Mixed Content Sorunu: HTTPS API’niz HTTP kaynaklara referans veriyorsa tarayıcılar uyarı verir. Tüm iç bağlantıları relative URL veya HTTPS ile yazın.
X-Forwarded-Proto Güveni: Load balancer arkasında çalışırken, sadece güvenilir proxy’lerden gelen bu header’ı kabul edin:
# Nginx'te sadece belirli IP'lerden X-Forwarded-Proto kabul et
server {
# Güvenilir upstream'den gelen gerçek IP'yi al
set_real_ip_from 10.0.0.0/8;
set_real_ip_from 172.16.0.0/12;
real_ip_header X-Forwarded-For;
# Sadece bu proxy'lerden header'ı güven
location /api/ {
if ($http_x_forwarded_proto != "https") {
return 400 '{"error": "HTTPS required"}';
}
proxy_pass http://backend;
}
}
Wildcard Sertifikaları: Çok sayıda subdomain API endpoint’iniz varsa wildcard sertifika düşünün. Let’s Encrypt ile DNS challenge kullanarak alabilirsiniz:
certbot certonly
--dns-cloudflare
--dns-cloudflare-credentials ~/.cloudflare/credentials.ini
-d "*.api.example.com"
-d "api.example.com"
--preferred-challenges dns-01
Sonuç
HTTPS zorlaması tek başına yeterli bir güvenlik önlemi değil, ama temel taş niteliğinde. Doğru yapılandırılmış bir HTTPS zorlama mekanizması şunları sağlar:
- Veri gizliliği: API key’ler, token’lar ve payload’lar şifreli gider
- Veri bütünlüğü: İçerik yolda değiştirilemez
- Kimlik doğrulama: İstemci, gerçekten sizin sunucunuzla konuştuğunu bilir
- Uyumluluk: PCI DSS, HIPAA gibi standartlar HTTPS zorunluluğu getirir
Yapılandırma sırasında dikkat etmeniz gereken kritik noktalar var: TLS 1.0 ve 1.1’i kapatın, çünkü bu protokoller artık güvensiz kabul ediliyor. HSTS header’ını mutlaka ekleyin ve preload listesine girmek için https://hstspreload.org adresine başvurun. Sertifika yenileme sürecini otomatize etmeden bırakmayın, aksi halde bir sabah API’niz tamamen erişilemez hale gelir.
Son olarak, bu yazıda anlattığım test scriptini CI/CD pipeline’ınıza entegre edin. Her deployment sonrasında HTTPS yapılandırmanızın hala sağlam olduğunu doğrulamak, sizi ileride büyük baş ağrısından kurtarır.
