Nginx ile Let’s Encrypt Sertifika Yenileme Sorunları ve Çözümleri

Let’s Encrypt sertifika yenilemesi, çoğu zaman “kur ve unut” mantığıyla çalışan bir sistem gibi görünür. Certbot’u kurdun, cron job’ı ayarladın, her şey otomatik yenilenecek. Sonra bir gün sabah 3’te telefonun çalıyor, monitörün kırmızıya dönmüş ve sertifikan 30 gün önce sona ermiş. Nginx ile Let’s Encrypt kombinasyonunda bu tür sorunlar düşündüğünden çok daha sık yaşanıyor. Bu yazıda gerçek hayatta karşılaştığım senaryoları ve çözüm yollarını adım adım anlatacağım.

Sorunun Kökünü Anlamak: Certbot Nasıl Çalışır?

Hata ayıklamaya geçmeden önce certbot’un arka planda ne yaptığını anlamak şart. Certbot, sertifika yenilerken iki temel doğrulama yöntemi kullanır:

  • HTTP-01 challenge: Let’s Encrypt sunucusu, senin domain’ine HTTP üzerinden belirli bir URL’e istek atar. Certbot bu URL’e geçici bir dosya yerleştirir ve doğrulama tamamlanır.
  • DNS-01 challenge: Domain’in DNS kayıtlarına geçici bir TXT kaydı eklenir. Nginx olmadan da çalışır, sunucu kapalı olsa bile.
  • TLS-ALPN-01 challenge: 443 portu üzerinden çalışır, daha az yaygın.

Nginx ile genellikle HTTP-01 kullanılır ve bu yöntem birçok noktada kırılabilir. Sorunları doğru teşhis etmek için önce log’lara bakmak gerekiyor.

Log Dosyalarına Bakmak: İlk Adım Her Zaman Budur

Certbot’un kendi log’ları /var/log/letsencrypt/ altında tutulur. Nginx log’ları ise genellikle /var/log/nginx/ dizininde bulunur. İkisini birlikte incelemek büyük resmi görmeni sağlar.

# Certbot son yenileme denemesinin detaylarını görmek için
sudo cat /var/log/letsencrypt/letsencrypt.log | grep -A 20 "Attempting to renew"

# Nginx erişim ve hata loglarını aynı anda takip etmek
sudo tail -f /var/log/nginx/access.log /var/log/nginx/error.log

# Yenileme sırasında ne olduğunu görmek için timestamp ile filtreleme
sudo journalctl -u certbot.service --since "2024-01-15 00:00" --until "2024-01-15 06:00"

Certbot log’unda şuna benzer satırlar görüyorsan HTTP-01 challenge başarısız olmuş demektir:

Challenge failed for domain example.com
http-01 challenge for example.com
Cleaning up challenges
Some challenges have failed.

Bu noktada Nginx access log’unda Let’s Encrypt sunucusunun .well-known/acme-challenge/ path’ine istek atıp atmadığını kontrol etmelisin.

Senaryo 1: .well-known Dizinine Erişim Engellenmiş

En yaygın sorunlardan biri. Nginx konfigürasyonunda belki bir deny all direktifi var, belki bir rewrite kuralı tüm trafiği başka yere yönlendiriyor. Özellikle WordPress veya benzeri CMS’ler kullananlar bu sorunu sık yaşar.

Şunu test et:

# Let's Encrypt'in gördüğü şeyi simüle et
curl -I http://example.com/.well-known/acme-challenge/test

Eğer 301, 302, 403 veya 404 dönüyorsa sorun orada. Nginx konfigürasyonunu incele:

sudo nginx -T | grep -A 5 "well-known"
sudo nginx -T | grep -B 5 "deny all"

Çözüm için Nginx server bloğuna şu eklemeyi yapman gerekiyor:

server {
    listen 80;
    server_name example.com www.example.com;

    # ACME challenge için bu blok HER ZAMAN en üstte olmalı
    location ^~ /.well-known/acme-challenge/ {
        root /var/www/html;
        allow all;
        default_type "text/plain";
        try_files $uri =404;
    }

    # Geriye kalan tüm HTTP trafiğini HTTPS'e yönlendir
    location / {
        return 301 https://$host$request_uri;
    }
}

^~ operatörü önemli. Bu sayede bu location bloğu diğer regex location’lardan önce eşleşir ve challenge dosyalarına erişim garantilenir.

Senaryo 2: HTTP’den HTTPS’e Yönlendirme Sorunu

Birçok konfigürasyonda tüm HTTP trafiği direkt HTTPS’e yönlendirilir. Certbot webroot veya standalone modunda çalışıyorsa bu yönlendirme challenge’ı bozabilir. Özellikle şu konfigürasyon sorun çıkarır:

# SORUNLU KONFİGÜRASYON
server {
    listen 80;
    server_name example.com;
    return 301 https://example.com$request_uri;
}

Bu blokta .well-known için bir istisna yoksa certbot başarısız olur. Yukarıdaki çözümde gösterdiğim gibi location ^~ /.well-known/acme-challenge/ bloğunu yönlendirmeden önce eklemelisin.

Eğer certbot’u --nginx plugin ile kullanıyorsan, certbot bu değişikliği otomatik yapar. Ancak elle düzenlenmiş konfigürasyonlarda certbot bazen kendi eklediği blokları bozabilir. Şunu kontrol et:

# Certbot'un nginx konfigürasyonunu nasıl gördüğünü kontrol et
sudo certbot renew --dry-run --nginx -v 2>&1 | head -50

# Mevcut sertifika durumunu ve yenileme konfigürasyonunu gör
sudo certbot certificates

Senaryo 3: Port 80 Açık Değil veya Firewall Engeli

Let’s Encrypt’in HTTP-01 doğrulaması için dışarıdan port 80’e erişilebilir olması şart. Cloud sunucularda (AWS, GCP, Azure, Hetzner) security group veya firewall kuralları bu portu kapatmış olabilir.

# Dışarıdan port 80'in açık olup olmadığını test et
# Başka bir makineden veya online araçla
nc -zv example.com 80
curl -v telnet://example.com:80

# Sunucu üzerinde nginx'in 80'i dinleyip dinlemediğini kontrol et
sudo ss -tlnp | grep :80
sudo netstat -tlnp | grep nginx

# UFW kullanıyorsan
sudo ufw status
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# iptables kullanıyorsan mevcut kuralları listele
sudo iptables -L -n | grep -E "80|443"

Cloud ortamlarında sunucu üzerindeki firewall açık olsa bile provider’ın network-level firewall’u (AWS Security Groups, GCP Firewall Rules vb.) engelliyor olabilir. Bu ayarları ilgili cloud konsolundan kontrol etmen gerekir.

Senaryo 4: Sertifika Yenileme Cron Job’ı Çalışmıyor

Certbot kurulumunda genellikle bir systemd timer veya cron job oluşturulur. Bunların düzgün çalışıp çalışmadığını kontrol etmek önemlidir.

# Systemd timer kontrolü (modern sistemlerde)
sudo systemctl status certbot.timer
sudo systemctl list-timers | grep certbot

# Cron job kontrolü
sudo crontab -l
sudo cat /etc/cron.d/certbot

# Timer'ı manuel tetikle ve sonucu izle
sudo systemctl start certbot.service
sudo journalctl -u certbot.service -f

# Snap ile kurulduysa (Ubuntu 20.04+)
sudo snap services certbot
sudo snap logs certbot.renew

Eğer systemd timer devre dışıysa etkinleştir:

sudo systemctl enable certbot.timer
sudo systemctl start certbot.timer
sudo systemctl status certbot.timer

Cron alternatifi tercih ediyorsan şu satırı /etc/cron.d/certbot dosyasına ekle:

# Her gün iki kez kontrol et, yenileme gerekiyorsa yenile
0 */12 * * * root certbot renew --quiet --post-hook "systemctl reload nginx"

--quiet parametresi sadece hata durumunda çıktı üretir, --post-hook ise başarılı yenilemeden sonra Nginx’i yeniden yükler.

Senaryo 5: Nginx Reload Yapılmıyor, Yeni Sertifika Devreye Girmiyor

Bu ilginç bir senaryo. Sertifika başarıyla yenilendi ama Nginx hala eski sertifikayı sunuyor. Certbot sertifikayı yeniledi fakat Nginx’e “yeni sertifikayı kullan” demedik.

# Mevcut sertifikanın son kullanma tarihini kontrol et
sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem -noout -dates

# Nginx'in gerçekte hangi sertifikayı sunduğunu kontrol et
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates

# İkisi farklıysa nginx reload yeterli
sudo nginx -t && sudo systemctl reload nginx

Bunu kalıcı olarak çözmek için certbot’un deploy hook’unu kullanmalısın:

# /etc/letsencrypt/renewal-hooks/deploy/ dizinine script ekle
sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Dosya içeriği:

#!/bin/bash
# Sertifika yenilendikten sonra nginx'i yeniden yükle
nginx -t && systemctl reload nginx
echo "$(date): Nginx yeniden yüklendi - sertifika yenileme sonrası" >> /var/log/certbot-deploy.log
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Deploy hook’ları pre, deploy ve post olmak üzere üç kategoride çalışır ve certbot her başarılı yenilemeden sonra otomatik tetikler.

Senaryo 6: Rate Limit’e Takılmak

Let’s Encrypt’in rate limit’leri gerçek bir tuzak. Özellikle test yaparken veya bir şeyler yanlış gidip certbot’u defalarca çalıştırdığında şu hatayla karşılaşırsın:

Error: urn:ietf:params:acme:error:rateLimited
too many certificates already issued for exact set of domains

Rate limit detayları:

  • Aynı domain için haftada 5 sertifika
  • Aynı IP’den saatte 5 başarısız doğrulama
  • Aynı IP’den 3 saatte 500 istek
# Dry-run ile test et, gerçek sertifika almaz, rate limit tüketmez
sudo certbot renew --dry-run
sudo certbot certonly --dry-run --nginx -d example.com

# Mevcut sertifikaları listele ve tarihlerini gör
sudo certbot certificates

# Rate limit durumunu kontrol etmek için crt.sh kullan
curl -s "https://crt.sh/?q=example.com&output=json" | python3 -m json.tool | grep -E "not_before|common_name" | head -20

Rate limit’e takıldıysan tek çözüm beklemektir. Bu süreçte staging ortamını kullanarak testlerini yapabilirsin:

# Staging ortamı ile test - gerçek rate limit'e tabi değil
sudo certbot certonly --staging --nginx -d example.com -d www.example.com

Senaryo 7: Wildcard Sertifika Yenileme Sorunları

*.example.com gibi wildcard sertifikalar DNS-01 challenge gerektirir ve bu yöntemin otomasyonu ayrı bir baş ağrısıdır. DNS provider’ına göre farklı certbot plugin’leri gerekir.

# Manuel DNS challenge ile wildcard alma (otomatik değil, test için)
sudo certbot certonly --manual --preferred-challenges dns 
  -d "*.example.com" -d "example.com"

# Cloudflare DNS API ile otomatik yenileme
sudo pip3 install certbot-dns-cloudflare

# Cloudflare API token dosyası oluştur
sudo nano /etc/letsencrypt/cloudflare.ini

Cloudflare konfigürasyon dosyası:

# /etc/letsencrypt/cloudflare.ini
dns_cloudflare_api_token = BURAYA_CLOUDFLARE_API_TOKEN_GEL
sudo chmod 600 /etc/letsencrypt/cloudflare.ini

# Wildcard sertifika al
sudo certbot certonly --dns-cloudflare 
  --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini 
  -d "*.example.com" -d "example.com"

Yenileme konfigürasyonu /etc/letsencrypt/renewal/example.com.conf dosyasında otomatik kaydedilir ve sonraki certbot renew çalıştırmalarında DNS plugin’i otomatik kullanılır.

Hata Ayıklama Kontrol Listesi

Herhangi bir sertifika sorununda sırayla şunları kontrol etmeni öneririm:

  • Sertifika durumu: sudo certbot certificates ile son kullanma tarihini ve yenileme durumunu gör
  • DNS çözümlemesi: dig A example.com ile domain’in doğru IP’ye işaret ettiğini doğrula. Domain farklı bir IP’ye işaret ediyorsa Let’s Encrypt doğrulamayı geçemez
  • Port erişimi: Hem 80 hem 443 portunun dışarıdan erişilebilir olduğunu kontrol et
  • Nginx konfigürasyonu: sudo nginx -t ile syntax hatasını tespit et
  • Certbot log’ları: /var/log/letsencrypt/letsencrypt.log dosyasının son satırlarını incele
  • Disk alanı: df -h ile disk doluysa certbot çalışamaz, log yazamaz
  • Sistem saati: timedatectl status ile sistem saatinin doğru olduğunu kontrol et. Saat yanlışsa TLS el sıkışmaları başarısız olur
# Hepsini tek seferde kontrol eden basit script
#!/bin/bash
echo "=== Sertifika Durumu ==="
certbot certificates 2>/dev/null

echo "=== Nginx Durumu ==="
nginx -t 2>&1
systemctl is-active nginx

echo "=== Port Kontrolü ==="
ss -tlnp | grep -E ":80|:443"

echo "=== Disk Alanı ==="
df -h /

echo "=== Son Certbot Logları ==="
tail -30 /var/log/letsencrypt/letsencrypt.log

Ocsp Stapling ve Sertifika Zinciri Sorunları

Bazen sertifika yenilendi ve Nginx yükledi ama tarayıcılar güvensiz diye işaretliyor. Bu genellikle eksik ara sertifika zinciriyle ilgilidir.

# Sertifika zincirini kontrol et
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -issuer -subject

# Tam zinciri test et
curl -I https://example.com --verbose 2>&1 | grep -E "SSL|certificate|verify"

# OCSP stapling durumunu kontrol et
echo QUIT | openssl s_client -connect example.com:443 -status 2>/dev/null | grep -A 10 "OCSP"

Nginx’te OCSP stapling aktifleştirmek için:

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

# fullchain.pem kullan, cert.pem değil!
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;

fullchain.pem yerine cert.pem kullanmak yaygın bir hatadır. fullchain.pem hem domain sertifikasını hem ara sertifikaları içerir, cert.pem sadece domain sertifikasını içerir.

Çoklu Domain ve Subdomain Yönetimi

Birden fazla domain veya subdomain için ayrı sertifika mı yoksa tek bir SAN sertifikası mı kullanmalısın? Her iki yaklaşımın da avantajları var.

# Birden fazla domain için tek sertifika
sudo certbot certonly --nginx 
  -d example.com 
  -d www.example.com 
  -d blog.example.com 
  -d api.example.com

# Mevcut sertifikaya yeni domain ekle (expand)
sudo certbot certonly --nginx --expand 
  -d example.com 
  -d www.example.com 
  -d yeni-subdomain.example.com

# Hangi sertifikanın hangi domain'leri kapsadığını listele
sudo certbot certificates

Her subdomain için ayrı Nginx server bloğu kullanıyorsan, sertifika yolu konfigürasyonlarının da doğru olduğundan emin ol. Yanlış sertifika yolu Nginx’in başlamamasına neden olur.

Sonuç

Let’s Encrypt ve Nginx kombinasyonundaki sorunların büyük çoğunluğu aslında birkaç temel kategoride toplanıyor: HTTP-01 challenge’ına erişim engeli, firewall kuralları, cron job’ın çalışmaması ve Nginx’e reload yapılmaması. Bu yazıdaki hata ayıklama adımlarını sırayla takip edersen çoğu sorunu 15-20 dakikada çözebilirsin.

En kritik tavsiyem şu: --dry-run parametresini kullanmayı alışkanlık haline getir. Gerçek sertifika almadan önce her zaman dry-run ile test et. Rate limit’e takılmak, gerçek bir üretim ortamında kurtarılması zor bir duruma dönüşebilir. Bunun yanı sıra Nginx deploy hook’unu mutlaka ekle, aksi takdirde sertifika yenilendi ama devreye girmedi diye gece yarısı alarm alabilirsin.

Monitoring konusunu da atlama. Basit bir cron job ile sertifika son kullanma tarihini kontrol edip kendine e-posta gönderebilirsin ya da Prometheus + Alertmanager gibi araçlarla bunu izleyebilirsin. Sorun çıkmadan önce haberdar olmak, gece yarısı yangın söndürmekten her zaman daha iyidir.

Bir yanıt yazın

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