Certbot ile Sertifika Otomasyonu: Hooks ve Yenileme Süreçleri

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 +x uygulanmamış, execute bit eksik
  • Script bulunamıyor: Shebang satırı (#!/bin/bash) eksik
  • Beklenmedik hata: Script set -e ile başladığında herhangi bir komutun çıkış kodu 0 dışında olursa script durur, bunu kontrol edin
  • Ortam değişkenleri boş: RENEWED_DOMAINS sadece 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.

Yorum yapın