Let's Encrypt Sertifikalarını Postfix ve Dovecot'ta Sıfır Kesintiyle Otomatik Yenilemek
certbot renew cron'da yeşil geçer ama posta sunucunuz hâlâ 60 gün önceki sertifikayı sunar. Postfix ve Dovecot'u canlı oturumları düşürmeden yeniden yükleyen, DANE-güvenli ve çift-algoritmalı bir otomatik yenileme hattını uçtan uca kuruyoruz.
EvilMail Team28 Temmuz 202611 dk okuma
Sertifika "yenilendi" ama posta sunucusu hâlâ eskiyi sunuyor
Klasik sahne: /var/log/letsencrypt/letsencrypt.log tertemiz, cron çıktısı yeşil, /etc/letsencrypt/live/mail.evilmail.pro/fullchain.pem dün gece güncellenmiş. Sonra bir sabah bir müşteri, Outlook'un fırlattığı "sunucunun güvenlik sertifikasının süresi doldu" uyarısının ekran görüntüsünü destek kuyruğuna düşürüyor. Diske bakıyorsunuz, dosya güncel. Panik.
Diskteki dosya yeni, porttan gelen sertifika eski. Sebep basit ve acımasız: Postfix ve Dovecot, TLS sertifikasını process başlarken belleğe yükler ve dosya değiştiğinde otomatik geri okumaz.
Let's Encrypt Mail Sertifikası Otomatik Yenileme: Postfix + Dovecot Reload — EvilMail Blog
certbot dosyayı değiştirmek dışında hiçbir şey yapmadıysa, daemon 60 gün önce açtığı process ömrü boyunca eski PEM'i sunmaya devam eder. Web'de bolca dolaşan
certbot renew && systemctl restart postfix
tavsiyesi hem yanlış hem eksik:
restart
, çalışan
smtpd
ve
imap
alt process'lerini anında öldürür — devam eden bir SMTP teslimatı yarıda kesilir, saatlerdir açık bir IMAP IDLE oturumu düşer. Doğru araç
reload
, doğru tetikleyici ise certbot'un
--deploy-hook
mekanizmasıdır.
certbot renew gerçekte ne yapar, ne yapmaz
certbot renew idempotenttir: kayıtlı her sertifikayı gezer, yenileme eşiğine gelmemiş olanları atlar, sadece süresi kritik daralanları yeniler. Klasik eşik, sertifika ömrünün son üçte biridir — 90 günlük bir sertifikada kalan 30 günden az. Bu yüzden certbot renew'i günde birkaç kez çağırmak zararsızdır; çoğu koşuda hiçbir şey yapmadan çıkar.
2026 gerçekliği bu ritmi zorunlu hale getirdi: Let's Encrypt kısa ömürlü (6 günlük) sertifika profilini yaygınlaştırdı. Bu profilde günde iki kez çalışan systemd timer bir tabandır; kısa ömürlü sertifikalarda daha sık koşmak güvenlidir ve önerilir. Timer'ın canlı olduğunu doğrulayın:
bash
systemctl status certbot.timer
# OnCalendar=*-*-* 00,12:00:00
# RandomizedDelaySec=43200 <-- ACME sunucusunu aynı dakikada boğmamak için
Gerçek bir yenileme tetiklemeden tüm zinciri test etmek için --dry-run kullanın; ACME staging ortamına gider, rate limit yemez, canlı sertifikaya dokunmaz:
bash
certbot renew --dry-run
Kritik ayrım hook'larda: --post-hookher renew koşusunda çalışır (çoğunlukla hiçbir şey yenilenmese bile), --deploy-hook ise yalnızca gerçekten yenilenen bir sertifika için çalışır. Daemon reload'ını her yarım günde bir boşuna tetiklemek istemezsiniz; bu yüzden reload mantığı daima deploy-hook'a girer.
Postfix ve Dovecot'u sıfır kesintiyle yeniden yükleme
Her iki daemon da SIGHUP tabanlı nazik bir yeniden yükleme destekler. reload sırasında ana (master) process ayakta kalır; sadece yeni bağlantılara hizmet verecek alt process'ler yeni yapılandırmayla — dolayısıyla yeni sertifikayla — doğar. Devam eden oturumlar kendi işini bitirene kadar eski process'te yaşamaya devam eder, kimse düşmez.
bash
postfix reload # master'a SIGHUP; smtpd/cleanup alt process'lerini nazikçe döndürür
systemctl reload dovecot # ya da: doveadm reload
Postfix tarafında 2026 için önerilen ayar smtpd_tls_chain_files. Bu direktif bir dosya listesi alır ve listeyi sırayla okur: her özel anahtar, hemen ardından gelen sertifika zinciriyle eşlenir. Bu yüzden her privkey.pem'i kendi fullchain.pem'i izlemelidir. İki lineage listeleyerek ECDSA ve RSA zincirlerini aynı anda sunarsınız — istemci hangisini destekliyorsa Postfix onu seçer:
ini
# main.cf — her privkey'i kendi fullchain'i izler
smtpd_tls_chain_files =
/etc/letsencrypt/live/mail.evilmail.pro/privkey.pem
/etc/letsencrypt/live/mail.evilmail.pro/fullchain.pem
/etc/letsencrypt/live/mail.evilmail.pro-rsa/privkey.pem
/etc/letsencrypt/live/mail.evilmail.pro-rsa/fullchain.pem
Sırayı bozmak — örneğin iki privkey.pem'i art arda yazmak — Postfix'in "anahtara karşılık sertifika yok" hatasıyla hiç açılmamasına yol açar. Tek algoritma yetiyorsa, eski ama hâlâ geçerli iki-direktifli stil daha yalındır:
Reload'ın restart'tan neden bu kadar farklı olduğu, canlı oturumlara etkisini gördüğünüzde netleşir:
Deploy-hook script'i: çift zincir, izinler, doğrulama
/etc/letsencrypt/renewal-hooks/deploy/ altına konan her executable dosya, yalnızca gerçekten yenilenen sertifikalar için çalışır ve $RENEWED_LINEAGE ile $RENEWED_DOMAINS ortam değişkenlerini alır. Aşağıdaki script üç işi doğru yapar: yalnızca çalışan servisi reload eder, izinleri düzeltir ve reload sonrası porttan gerçekten yeni NotAfter'ı okuyarak sessiz başarısızlığı yakalar.
bash
#!/usr/bin/env bash
# /etc/letsencrypt/renewal-hooks/deploy/reload-mail.sh
set -euo pipefail
# Sadece mail lineage'ları ilgilendirir
case "$RENEWED_DOMAINS" in
*mail.evilmail.*) ;;
*) exit 0 ;;
esac
# Dovecot'un login process'i privkey'i okuyabilsin
chgrp dovecot "$RENEWED_LINEAGE/privkey.pem" || true
chmod 640 "$RENEWED_LINEAGE/privkey.pem"
# Yalnızca çalışan daemon'ı nazikçe reload et
pgrep -x master >/dev/null && postfix reload
pgrep -x dovecot >/dev/null && doveadm reload
# Gerçeklik kontrolü: port hâlâ eski sertifikayı mı sunuyor?
sleep 2
NEW=$(openssl x509 -noout -enddate -in "$RENEWED_LINEAGE/cert.pem")
LIVE=$(openssl s_client -connect mail.evilmail.pro:993 \
-servername mail.evilmail.pro 2>/dev/null \
| openssl x509 -noout -enddate)
if [ "$NEW" != "$LIVE" ]; then
echo "HOOK FAIL: port 993 hâlâ eski sertifikayı sunuyor ($LIVE)" >&2
exit 1
fi
İzin ayrıntısı önemli: Postfix master process root olarak çalışıp privkey'i ayrıcalık düşürmeden önce okur, ama Dovecot SSL yapılandırmasını daha düşük yetkili login process ile okur. privkey.pem'i 640 root:dovecot yapmak, dosyayı dünyaya açmadan Dovecot'un erişmesini sağlar. Hook içindeki pgrep koruması, o an durdurulmuş bir servisi reload etmeye çalışıp script'i gereksiz yere exit 1 ile patlatmayı önler.
DANE/TLSA ve çift algoritmalı (ECDSA + RSA) tuzaklar
DANE kullanıyorsanız — yani DNSSEC imzalı bölgenizde _25._tcp.mail.evilmail.pro. IN TLSA 3 1 1 <SPKI SHA-256> yayınlıyorsanız — sertifika yenilemesi bir mayın tarlasına dönüşür. 3 1 1 kaydı sertifikanın açık anahtarının (SPKI) SHA-256 özetini sabitler. Zincir değişse bile anahtar aynı kalırsa kayıt kırılmaz; ama certbot varsayılan olarak her yenilemede yeni bir anahtar üretir, bu da yeni bir SPKI hash demektir ve eşleşmeyen TLSA kaydı gönderen sunucuların teslimatı reddetmesiyle sonuçlanır.
İki güvenli yol var:
`--reuse-key`: Yenilemede aynı anahtarı korur, böylece 3 1 1 kaydı geçerli kalır. Basit ama anahtar hijyeni açısından sonsuza dek aynı anahtarı tutmak ideal değildir.
TLSA rollover: Yeni anahtarın hash'ini eski hash ile birlikte yayınlayın (iki TLSA kaydı), DNS TTL süresi kadar bekleyin ki tüm resolver'lar yeni kaydı görsün, ancak ondan sonra sertifikayı yeni anahtarla değiştirip eski kaydı kaldırın. Bu, --reuse-key olmadan güvenli döndürmenin tek doğru yoludur.
Eski istemciler için ECDSA + RSA'yı birlikte sunuyorsanız iki ayrı lineage tutun (--key-type ecdsa varsayılanı ve --key-type rsa ile ikinci sertifika), ikisini de smtpd_tls_chain_files'a ekleyin. Her lineage'ın kendi TLSA hash'i olur — rollover yaparken her iki algoritmanın kayıtlarını senkronize etmeyi unutmayın, yoksa yalnızca RSA istemcileri sessizce reddedilir. DANE ile MTA-STS'i birlikte tutuyorsanız, MTA-STS policy dosyanızdaki mx girdisinin sertifika üzerindeki SAN ile eşleştiğinden emin olun; algoritma değişimi SAN'ı etkilemez ama yeni bir hostname eklemek MTA-STS'i de kırar.
İzleme: sessiz başarısızlığı erkenden yakala
Cron'un exit 0 dönmesi hiçbir şey ispatlamaz — daemon reload edilmediyse port hâlâ eskiyi sunar ve cron bunu bilmez. Tek güvenilir sinyal, sertifikayı canlı porttan çekip NotAfter'ını ölçmektir. Günlük bir kontrol yeterli:
bash
for hp in 25:smtp 587:smtp 465: 993: 995:; do
port=${hp%%:*}; st=${hp##*:}
end=$(openssl s_client -connect mail.evilmail.pro:$port \
${st:+-starttls $st} -servername mail.evilmail.pro 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
days=$(( ( $(date -d "$end" +%s) - $(date +%s) ) / 86400 ))
[ "$days" -lt 10 ] && echo "ALARM port $port: $days gün kaldı ($end)"
done
25 ve 587 STARTTLS ister (-starttls smtp), 465/993/995 doğrudan TLS'tir. Prometheus kullanıyorsanız blackbox_exporter içindeki tls modülü probe_ssl_earliest_cert_expiry metriğini her port için döker; probe_ssl_earliest_cert_expiry - time() < 10*86400 üzerine bir alert kurmak, hook'un sessizce patladığı günü değil, patladığı saati yakalar.
Kontrol
Neyi yakalar
systemctl status certbot.timer
Timer hiç çalışmıyor
/var/log/letsencrypt/letsencrypt.log
ACME challenge / DNS hatası
Diskteki cert.pem NotAfter
certbot yeniledi mi
Porttan okunan NotAfter
Daemon reload edildi mi (asıl sinyal)
TLSA hash eşleşmesi
DANE senkronu bozuldu mu
Uçtan uca kurulum checklist'i
systemctl status certbot.timer aktif ve günde en az 2 kez tetikliyor (kısa ömürlü sertifikada daha sık).
certbot renew --dry-run staging'de temiz geçiyor.
Deploy-hook /etc/letsencrypt/renewal-hooks/deploy/ altında ve chmod +x ile executable.
Hook postfix reload + doveadm reload kullanıyor — hiçbir yerde restart yok.
privkey.pem izni 640 root:dovecot; Dovecot login process okuyabiliyor.
Postfix smtpd_tls_chain_files (veya cert/key çifti) yeni PEM yollarını gösteriyor; her privkey'i kendi fullchain'i izliyor.
25/465/587/993/995 portlarının hepsi porttan okunduğunda güncel NotAfter dönüyor.
DANE varsa: --reuse-key açık ya da TLSA rollover prosedürü yazılı; ECDSA + RSA kayıtları senkron.
Port bazlı NotAfter alarmı kurulu ve bir kez bilerek tetiklenerek test edilmiş.
Sertifika yenileme, "kurdum unuttum" diye anılan ama en sinsi biçimde patlayan işlerden biri. Farkı yaratan tek şey, diskteki dosyaya değil canlı porta bakan bir doğrulama adımıdır — onu hook'un içine koyduğunuz anda "sessizce dolmuş sertifika" sınıfı sorunlar tamamen ortadan kalkar.