Certbot kullanıyorsanız ve sertifikalarınız hala elle yenileniyor ya da servisleriniz restart edilmiyor, bu yazı tam size göre. Let’s Encrypt ekosisteminde certbot, sadece sertifika almakla kalmaz; hooks sistemi sayesinde yenileme öncesi ve sonrasında istediğiniz her şeyi otomatik olarak çalıştırabilir. Hadi bu mekanizmayı gerçek dünya senaryolarıyla birlikte inceleyelim.
Certbot Hooks Sistemi Nedir?
Certbot, sertifika yenileme sürecinin belirli aşamalarında özel script’ler çalıştırmanıza izin veren bir hook sistemi sunar. Bu hooks’ların temel mantığı şu: sertifika yenilenmeden önce bir şey yap, yenilendikten sonra bir şey yap, ya da sadece başarılı yenilemelerden sonra bir şey yap.
Üç temel hook tipi vardır:
- pre-hook: Yenileme işlemi başlamadan önce çalışır
- post-hook: Yenileme işlemi tamamlandıktan sonra çalışır (başarılı olsun ya da olmasın)
- deploy-hook: Sadece sertifika başarıyla yenilendiğinde çalışır
Bu ayrımı iyi anlamak önemli. Özellikle deploy-hook ile post-hook farkını karıştırmak çok yaygın bir hata. Nginx veya Apache’yi restart etmek istiyorsanız, bunu post-hook‘ta değil deploy-hook‘ta yapmanız gerekir. Aksi takdirde yenileme başarısız olsa bile servis restart edilir, bu da gereksiz downtime’a yol açabilir.
Hook Dizin Yapısı
Certbot, hook’ları iki farklı şekilde tanımlamanıza izin verir: komut satırı parametreleriyle ya da dizin bazlı yaklaşımla. Dizin bazlı yaklaşım çok daha temiz ve yönetilebilir.
ls -la /etc/letsencrypt/renewal-hooks/
# Çıktı:
# drwxr-xr-x deploy/
# drwxr-xr-x post/
# drwxr-xr-x pre/
Bu dizinlere koyduğunuz her script, ilgili aşamada otomatik olarak çalıştırılır. Script’lerin çalıştırılabilir olması şart, bunu unutmayın.
# Script'e çalıştırma izni vermek için
chmod +x /etc/letsencrypt/renewal-hooks/deploy/restart-nginx.sh
chmod +x /etc/letsencrypt/renewal-hooks/pre/stop-haproxy.sh
Dizin yapısını tercih etmenin bir diğer avantajı, birden fazla domain veya servis için farklı script’ler koyabilmeniz. Certbot, bu dizinlerdeki tüm script’leri alfabetik sırayla çalıştırır.
İlk Gerçek Dünya Senaryosu: Nginx ile Otomatik Yenileme
En yaygın senaryo: Nginx çalışıyor, sertifika dolmak üzere, certbot yenilemeyi yapıyor ama Nginx eski sertifikayı hala bellekte tutuyor. Bunu deploy-hook ile çözelim.
# /etc/letsencrypt/renewal-hooks/deploy/restart-nginx.sh
#!/bin/bash
set -e
# Nginx konfigürasyonunu test et
nginx -t 2>/dev/null
if [ $? -eq 0 ]; then
echo "Nginx konfigürasyonu geçerli, reload yapılıyor..."
systemctl reload nginx
echo "Nginx başarıyla reload edildi: $(date)"
else
echo "HATA: Nginx konfigürasyonu hatalı, reload iptal edildi!" >&2
exit 1
fi
Dikkat edin, restart değil reload kullanıyoruz. Reload işlemi aktif bağlantıları kesmeden yeni sertifikayı yükler. Production ortamda bu fark kritik olabilir.
Script’i yerleştirip test edelim:
chmod +x /etc/letsencrypt/renewal-hooks/deploy/restart-nginx.sh
# Dry-run ile test et
certbot renew --dry-run
# Çıktıda şunu görmelisiniz:
# Running deploy-hook command: /etc/letsencrypt/renewal-hooks/deploy/restart-nginx.sh
Standalone Mod için Pre/Post Hook Kullanımı
Bazı durumlarda certbot’u standalone modda çalıştırmak zorunda kalırsınız; bu mod 80 ve 443 portlarını geçici olarak kullanır. Bu durumda mevcut web sunucunuzu durdurmanız gerekir.
# /etc/letsencrypt/renewal-hooks/pre/stop-webserver.sh
#!/bin/bash
set -e
SERVICE="nginx"
if systemctl is-active --quiet $SERVICE; then
echo "$(date): $SERVICE durduruluyor..."
systemctl stop $SERVICE
# Servisin durmasını bekle
sleep 2
echo "$(date): $SERVICE durduruldu"
else
echo "$(date): $SERVICE zaten çalışmıyor, devam ediliyor"
fi
# /etc/letsencrypt/renewal-hooks/post/start-webserver.sh
#!/bin/bash
set -e
SERVICE="nginx"
if ! systemctl is-active --quiet $SERVICE; then
echo "$(date): $SERVICE başlatılıyor..."
systemctl start $SERVICE
# Servisin ayağa kalkmasını kontrol et
for i in {1..5}; do
if systemctl is-active --quiet $SERVICE; then
echo "$(date): $SERVICE başarıyla başlatıldı"
exit 0
fi
sleep 2
done
echo "HATA: $SERVICE başlatılamadı!" >&2
exit 1
fi
Bu iki script birlikte çalıştığında, certbot sertifika almak için portu kullanır, işi biter, servis tekrar ayağa kalkar. Pre/post hook’un güzelliği burada: post hook, işlem başarısız olsa da çalışır. Yani web sunucunuz asılı kalmaz.
Wildcard Sertifikalar ve DNS Hook’ları
Wildcard sertifikalar (*.example.com) HTTP doğrulaması yerine DNS doğrulaması gerektirir. Bu noktada certbot’un manuel DNS hook desteği devreye girer. Cloudflare kullananlar için çok temiz bir örnek verelim.
Önce gerekli paketi kurun:
pip install certbot-dns-cloudflare
# veya
apt install python3-certbot-dns-cloudflare
Cloudflare API token’ınızı bir dosyaya yazın:
# /etc/letsencrypt/cloudflare.ini
dns_cloudflare_api_token = your_api_token_here
chmod 600 /etc/letsencrypt/cloudflare.ini
# Wildcard sertifika al
certbot certonly
--dns-cloudflare
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini
-d "*.example.com"
-d "example.com"
--preferred-challenges dns-01
Yenileme için renewal konfigürasyon dosyasına bakın, certbot bu parametreleri otomatik olarak kaydeder:
cat /etc/letsencrypt/renewal/example.com.conf
Renewal Konfigürasyon Dosyaları
Her domain için /etc/letsencrypt/renewal/ dizininde bir .conf dosyası bulunur. Bu dosyalar yenileme davranışını belirler ve burada da hook tanımlayabilirsiniz.
# /etc/letsencrypt/renewal/example.com.conf
[renewalparams]
authenticator = nginx
installer = nginx
account = abc123youraccount
server = https://acme-v02.api.letsencrypt.org/directory
# Domain'e özel hook
renew_hook = systemctl reload nginx && systemctl reload php8.1-fpm
pre_hook = /usr/local/bin/pre-renewal-check.sh
post_hook = /usr/local/bin/send-renewal-notification.sh
Bu yaklaşım, farklı domain’ler için farklı davranışlar tanımlamanızı sağlar. Örneğin bir domain için sadece Nginx reload yeterken, başka bir domain için hem Nginx hem de uygulama sunucusunu yeniden başlatmanız gerekebilir.
Loglama ve Monitoring: Prodüksiyonda Şart
Sertifika yenileme sessiz sedasız çalışıyor olabilir, ama bir gün bir şeyler ters gidecek. O an için iyi bir loglama altyapısı hayat kurtarır.
# /etc/letsencrypt/renewal-hooks/deploy/log-and-notify.sh
#!/bin/bash
LOG_FILE="/var/log/certbot-renewals.log"
DOMAIN="$RENEWED_DOMAINS"
LINEAGE="$RENEWED_LINEAGE"
# Temel loglama
echo "$(date '+%Y-%m-%d %H:%M:%S') - Sertifika yenilendi: $DOMAIN" >> $LOG_FILE
echo "$(date '+%Y-%m-%d %H:%M:%S') - Lineage path: $LINEAGE" >> $LOG_FILE
# Sertifika son kullanma tarihini logla
EXPIRY=$(openssl x509 -noout -enddate -in "$LINEAGE/cert.pem" | cut -d= -f2)
echo "$(date '+%Y-%m-%d %H:%M:%S') - Yeni son kullanma tarihi: $EXPIRY" >> $LOG_FILE
# Slack bildirimi (isteğe bağlı)
if [ -n "$SLACK_WEBHOOK_URL" ]; then
curl -s -X POST "$SLACK_WEBHOOK_URL"
-H 'Content-type: application/json'
-d "{"text":"Sertifika yenilendi: $DOMAIN - Yeni bitiş: $EXPIRY"}"
fi
echo "---" >> $LOG_FILE
Certbot, deploy hook çalışırken RENEWED_DOMAINS ve RENEWED_LINEAGE ortam değişkenlerini otomatik olarak set eder. Bu değişkenleri script’lerinizde kullanabilirsiniz.
Systemd Timer ile Zamanlama
Birçok dağıtımda certbot kurulduğunda otomatik olarak bir systemd timer veya cron job oluşturulur. Ama bunu elle de yapabilir ve özelleştirebilirsiniz.
# Mevcut timer'ı kontrol et
systemctl status certbot.timer
systemctl list-timers | grep certbot
Eğer systemd timer yoksa veya özelleştirmek istiyorsanız:
# /etc/systemd/system/certbot-renew.service
[Unit]
Description=Certbot Renewal Service
After=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --no-random-sleep-on-renew
ExecStartPost=/bin/systemctl reload nginx
StandardOutput=journal
StandardError=journal
# /etc/systemd/system/certbot-renew.timer
[Unit]
Description=Certbot Renewal Timer
After=network-online.target
[Timer]
OnCalendar=*-*-* 02,14:30:00
RandomizedDelaySec=1h
Persistent=true
[Install]
WantedBy=timers.target
systemctl daemon-reload
systemctl enable --now certbot-renew.timer
systemctl status certbot-renew.timer
OnCalendar=--* 02,14:30:00 ifadesi her gün 02:30 ve 14:30’da çalıştırır. RandomizedDelaySec=1h ise Let’s Encrypt sunucularına aynı anda binlerce istek gitmesini engellemek için 1 saate kadar rastgele gecikme ekler. Bu Let’s Encrypt’in rate limit’lerine takılmamak için önemli.
HAProxy Senaryosu: Sertifika Birleştirme
HAProxy, Nginx veya Apache’den farklı olarak sertifika ve private key’i birleşik bir PEM dosyasında bekler. Bu gerçek hayatta çok sık karşılaşılan bir durum.
# /etc/letsencrypt/renewal-hooks/deploy/haproxy-deploy.sh
#!/bin/bash
set -e
DOMAIN="example.com"
CERT_DIR="/etc/letsencrypt/live/$DOMAIN"
HAPROXY_CERT_DIR="/etc/haproxy/certs"
# HAProxy için birleşik PEM oluştur
cat "$CERT_DIR/fullchain.pem" "$CERT_DIR/privkey.pem"
> "$HAPROXY_CERT_DIR/$DOMAIN.pem"
# İzinleri düzelt
chmod 600 "$HAPROXY_CERT_DIR/$DOMAIN.pem"
chown haproxy:haproxy "$HAPROXY_CERT_DIR/$DOMAIN.pem"
# HAProxy konfigürasyonunu test et
haproxy -c -f /etc/haproxy/haproxy.cfg
if [ $? -eq 0 ]; then
echo "$(date): HAProxy konfigürasyonu geçerli, reload yapılıyor..."
systemctl reload haproxy
echo "$(date): HAProxy reload tamamlandı"
else
echo "$(date) HATA: HAProxy konfigürasyonu hatalı!" >&2
exit 1
fi
Çoklu Servis Senaryosu
Bazen tek bir sertifika birden fazla servis tarafından kullanılır. Örneğin hem Nginx hem de Postfix aynı sertifikayı kullanıyor olabilir.
# /etc/letsencrypt/renewal-hooks/deploy/multi-service-deploy.sh
#!/bin/bash
set -e
DOMAIN="$RENEWED_DOMAINS"
LOG="/var/log/certbot-deploy.log"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') [$DOMAIN] $1" | tee -a "$LOG"
}
# Nginx reload
if systemctl is-active --quiet nginx; then
nginx -t 2>/dev/null && systemctl reload nginx
log "Nginx reload edildi"
else
log "Nginx çalışmıyor, atlanıyor"
fi
# Postfix/Dovecot için sertifika kopyalama
if systemctl is-active --quiet postfix; then
cp "/etc/letsencrypt/live/$DOMAIN/fullchain.pem"
"/etc/postfix/ssl/fullchain.pem"
cp "/etc/letsencrypt/live/$DOMAIN/privkey.pem"
"/etc/postfix/ssl/privkey.pem"
chmod 640 /etc/postfix/ssl/*.pem
systemctl reload postfix
log "Postfix reload edildi"
fi
# Dovecot
if systemctl is-active --quiet dovecot; then
systemctl reload dovecot
log "Dovecot reload edildi"
fi
log "Tüm servisler güncellendi"
Sertifika Doğrulama ve Smoke Test
Yenileme sonrası sertifikanın gerçekten çalıştığını doğrulayan bir smoke test eklemek, gecenin 3’ünde alacağınız alarm sayısını ciddi ölçüde azaltır.
# /etc/letsencrypt/renewal-hooks/deploy/verify-cert.sh
#!/bin/bash
DOMAIN="example.com"
EXPECTED_ISSUER="Let's Encrypt"
LOG="/var/log/certbot-verify.log"
# 5 saniye bekle, servislerin ayağa kalkması için
sleep 5
# TLS bağlantısını test et
CERT_INFO=$(echo | openssl s_client -servername "$DOMAIN"
-connect "$DOMAIN:443" 2>/dev/null | openssl x509 -noout -dates -issuer 2>/dev/null)
if [ -z "$CERT_INFO" ]; then
echo "$(date): HATA - $DOMAIN için TLS bağlantısı kurulamadı!" >> "$LOG"
# Opsiyonel: alert gönder
exit 1
fi
EXPIRY_DATE=$(echo "$CERT_INFO" | grep "notAfter" | cut -d= -f2)
ISSUER=$(echo "$CERT_INFO" | grep "issuer" | grep -o "O = [^,]*" | head -1)
echo "$(date): $DOMAIN doğrulandı - Bitiş: $EXPIRY_DATE - İssuer: $ISSUER" >> "$LOG"
# 30 günden az kaldıysa uyar (bu normalde olmamalı ama kontrol edelim)
EXPIRY_EPOCH=$(date -d "$EXPIRY_DATE" +%s 2>/dev/null || date -j -f "%b %d %T %Y %Z" "$EXPIRY_DATE" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRY_EPOCH - $NOW_EPOCH) / 86400 ))
echo "$(date): $DOMAIN için $DAYS_LEFT gün kaldı" >> "$LOG"
Hata Ayıklama: Bir Şeyler Ters Gidince
Hook’larınız neden çalışmadı? İşte ilk bakılacak yerler:
# Certbot'un son çalışma loglarını incele
journalctl -u certbot -n 50 --no-pager
# Certbot kendi log dosyası
tail -100 /var/log/letsencrypt/letsencrypt.log
# Hook'ların çalıştırılabilir olup olmadığını kontrol et
ls -la /etc/letsencrypt/renewal-hooks/deploy/
ls -la /etc/letsencrypt/renewal-hooks/pre/
ls -la /etc/letsencrypt/renewal-hooks/post/
# Dry-run ile tüm süreci simüle et
certbot renew --dry-run --cert-name example.com
# Verbose modda çalıştır
certbot renew --dry-run -v
Yaygın sorunlar ve çözümleri:
- Script çalışmıyor:
chmod +xuygulanmamış, execute bit eksik - Script bulunamıyor: Shebang satırı (
#!/bin/bash) eksik - Beklenmedik hata: Script
set -eile başladığında herhangi bir komutun çıkış kodu 0 dışında olursa script durur, bunu kontrol edin - Ortam değişkenleri boş:
RENEWED_DOMAINSsadece deploy hook’ta var, pre/post hook’ta kullanmayın
Renewal Grace Period ve Rate Limit Yönetimi
Let’s Encrypt’in rate limit’leri gerçekten can sıkıcı olabilir. Özellikle test ortamlarında sürekli sertifika almaya çalışırsanız bloklanırsınız.
# Staging environment ile test et (rate limit yok)
certbot renew --dry-run --staging
# Belirli bir domain için yenilemeyi zorla
certbot renew --force-renewal --cert-name example.com
# Sertifika durumunu kontrol et
certbot certificates
# Sertifikanın ne zaman dolacağını öğren
openssl x509 -noout -dates -in /etc/letsencrypt/live/example.com/cert.pem
Certbot normalde sertifikayı 30 gün kala yenilemeye çalışır. Bu eşiği değiştirmek için renewal config dosyasına şunu ekleyebilirsiniz:
# /etc/letsencrypt/renewal/example.com.conf dosyasına ekle
[renewalparams]
# 45 gün kala yenilemeyi başlat
renew_before_expiry = 45 days
Sonuç
Certbot’un hook sistemi, SSL sertifika yönetimini gerçek anlamda “set and forget” hale getirmenin en temiz yolu. Pre hook ile servisi durdur, certbot işini yapsın, post hook ile servisi başlat; ya da deploy hook ile sadece başarılı yenilemelerde gerekli aksiyonları al.
Prodüksiyon ortamında şu üç şeyi mutlaka yapın: Birincisi, tüm hook script’lerinize loglama ekleyin. İkincisi, certbot renew --dry-run komutunu periyodik olarak çalıştırın ve sonucu monitör edin. Üçüncüsü, sertifika bitiş tarihlerini Grafana, Datadog veya en azından basit bir cron+mail ile izleyin.
Certbot bazen “sadece çalışıyor” diye gözden kaçan bir araç haline gelir. Ama o gün gelir, bir sertifika yenilenmez, servis reload edilmez, kullanıcılar SSL hatası alır. O günü yaşamamak için hook’larınızı şimdi kurun, test edin ve loglarını takip edin. Gece 3’te telefon açılmasından daha iyi bir motivasyon yok.