Teknoloji

acme.sh ile Let’s Encrypt ve ZeroSSL Yedek CA Kurulumu

Yedekli CA ile hangi sorunu çözüyoruz?

Let’s Encrypt tarafındaki bir hizmet kesintisi veya oran limiti, sertifika yenilemesini geciktirebilir. ZeroSSL hesabını önceden hazırlamak ikinci bir düzenleme yolu sağlar. Ancak DNS yetkisi, CAA kısıtlaması veya hatalı alan adı doğrulaması iki CA’yı da etkileyebilir. Her başarısız komutta başka CA’ya geçmek sorunu çözmez; gereksiz siparişler ve daha fazla hata üretir.

Bu rehberde aynı alan adı için iki ayrı acme.sh yapılandırması hazırlıyor, sertifikaları önce güvenli bir hazırlık dizinine alıyor ve kontrol edilen dosyaları Nginx’e aktarıyoruz. Günlük yenileme kontrolü otomatik çalışır. İkinci CA’ya geçiş ise teşhis edilmiş bir CA olayı için açıkça etkinleştirilir; bilinmeyen hatada süreç durur. Örnek bir üretim kurulumu veya ölçülmüş başarı hikâyesi değildir.

ACME, doğrulama ve yedek CA sınırları

ACME, istemci ile sertifika otoritesi arasındaki sertifika alma ve yenileme protokolüdür. CA, alan adını kullanma yetkisini doğrular; sertifikayı düzenlemek, onu web sunucusunda etkinleştirmekle aynı iş değildir. Sipariş başarılı olsa bile yanlış dosya yolu veya başarısız reload yüzünden eski sertifika sunulabilir.

DNS-01, alan adının DNS bölgesinde oluşturulan TXT kaydıyla doğrulama yapar ve wildcard sertifikalar için uygundur. HTTP-01, uygun webroot erişimi olan basit kurulumlarda daha az DNS yetkisi gerektirebilir. Wildcard, yalnız belirli bir ad düzeyini kapsar: *.example.com, www.example.com için geçerlidir; kök alan adı ve a.b.example.com için tek başına yeterli değildir.

CAA kayıtları varsa iki CA’nın da gerekli yetkiye sahip olduğunu kontrol edin. issue ve issuewild farklı davranabilir; CA kimliğini sağlayıcının güncel belgesinden doğrulayın. DNS sağlayıcısındaki arıza veya API token hatası, CA değiştirerek giderilemez.

Örnek ortamı ve iki CA hesabını hazırlayın

Örnek, Debian 12 üzerinde çalışan bir Nginx sunucusu, Bash, systemd, OpenSSL ve Cloudflare DNS API’sini kullanır. Komutlar root kabuğunda gösterilmiştir; çünkü sertifika dosyaları ve Nginx reload yetkisi gerekir. example.com, e-posta adresi ve token alanlarını sahibi olduğunuz test alanına göre değiştirin. Başka dağıtımlarda paket, güven deposu ve servis adlarını uyarlayın. Önce mevcut Nginx yapılandırmasını ve sertifika yollarını yedekleyin.

İstemciyi resmi depodan alın, kullanılan revizyonu kaydedin ve güncellemeleri önce test ortamında değerlendirin. Aşağıdaki doğrudan depo kullanımı ayrıca cron kurmaz; mevcut bir acme.sh kurulumunuz varsa aynı alan adını yöneten eski cron kaydını inceleyin. Aynı sertifikayı iki bağımsız zamanlayıcıya yönettirmeyin.

apt-get update
apt-get install -y git curl openssl ca-certificates util-linux
git clone --depth 1 https://github.com/acmesh-official/acme.sh.git /opt/acme.sh
git -C /opt/acme.sh log -1 --format='%H %cs %s'
# Review and retain the chosen revision before running it.
/opt/acme.sh/acme.sh --version
install -d -m 700 /etc/acme-fallback /var/lib/acme-fallback/logs \
  /etc/ssl/acme-example/releases
install -m 600 /dev/null /etc/acme-fallback/env

/etc/acme-fallback/env dosyasını yalnız root tarafından okunup değiştirilecek biçimde doldurun. DNS token’ını yalnız gerekli bölge ve DNS düzenleme izinleriyle sınırlayın; bölge bulma için gereken okuma iznini kullanılan dns_cf sürümüne göre sağlayın. Bu dosya kabuk tarafından okunacağından güvenilmeyen içerik içermemelidir.

export CF_Token='REPLACE_WITH_SCOPED_DNS_API_TOKEN'
export CF_Account_ID='REPLACE_WITH_CLOUDFLARE_ACCOUNT_ID'
ALLOW_FALLBACK=0

İki CA için ayrı config-home ve cert-home kullanacağız. Böylece hesaplar, sertifika kayıtları ve yenileme ayarları birbirine karışmaz. Her çağrıda --server açıkça verilir; global varsayılan CA’yı değiştirmeyiz. ZeroSSL EAB kullanır; acme.sh’nin incelenen sürümü kayıt e-postasıyla EAB bilgilerini alma yolunu destekler. Bu işlem başarısızsa hesabın geçerli EAB yöntemini resmi belgelerden kontrol edin; anahtar uydurmayın. Elle EAB parametresi kullanırken ortam değişkeninin komut argümanına açılmasının süreç listesinden görünürlüğü tek başına engellemediğini unutmayın.

İlk ECDSA sertifikalarını alın ve dışa aktarın

İlk kurulum betiği her CA’da aynı kök alan adı ve wildcard için ECDSA P-256 sertifika oluşturur. Bu ilk alım iki ayrı sertifika siparişidir; üretim kotasına dahildir. Önce ayrı bir test alanında doğrulama izinlerini ve CA koşullarını kontrol edin. Bash dizisi kullanımı her -d argümanını ayrı taşır; wildcard tırnak içinde kaldığı için yerel dosya adlarına genişlemez.

#!/usr/bin/env bash
set -euo pipefail
umask 077
source /etc/acme-fallback/env
ACME=/opt/acme.sh/acme.sh
STATE=/var/lib/acme-fallback
DOMAIN=example.com
DOMAINS=(-d "$DOMAIN" -d "*.$DOMAIN")

for ca in letsencrypt zerossl; do
  install -d -m 700 "$STATE/$ca/config" "$STATE/$ca/certs" "$STATE/$ca/stage"
  COMMON=(--home /opt/acme.sh --config-home "$STATE/$ca/config"
          --cert-home "$STATE/$ca/certs" --server "$ca")
  "$ACME" "${COMMON[@]}" --register-account -m ops@example.com
  "$ACME" "${COMMON[@]}" --issue "${DOMAINS[@]}" --dns dns_cf --keylength ec-256
  "$ACME" "${COMMON[@]}" --install-cert -d "$DOMAIN" --ecc \
    --cert-file "$STATE/$ca/stage/leaf.pem" \
    --key-file "$STATE/$ca/stage/key.pem" \
    --fullchain-file "$STATE/$ca/stage/fullchain.pem" \
    --reloadcmd 'true'
done

Betiği örneğin /root/acme-bootstrap.sh adıyla kaydedip bash /root/acme-bootstrap.sh ile ilk kurulumda bir kez çalıştırın. Bir CA adımı başarısızsa devam etmeyin. Tekrar çalıştırırken “yenileme gerekli değil” sonucunu yeni bir sipariş hatasıyla karıştırmayın; önce hangi adımın tamamlandığını kontrol edin. Her iki hazırlık dizininde leaf.pem, key.pem ve fullchain.pem bulunduğunu doğrulayın.

--install-cert istemcinin yönettiği dosyaları sabit hazırlık yollarına kopyalar. Nginx’i acme.sh’nin iç dosya düzenine bağlamayın. Buradaki --reloadcmd true bilinçlidir: yenileme yalnız hazırlık dosyalarını günceller; canlı reload aşağıdaki doğrulama ve dağıtım adımına aittir. İstemcinin güncel varsayılan anahtar tipini tahmin etmek yerine --keylength ec-256 ve sonraki işlemlerde --ecc açıkça kullanılır.

Kontrollü Let’s Encrypt → ZeroSSL geçişi

Günlük betik önce Let’s Encrypt yenilemesini kontrol eder. acme.sh için dönüş kodu 2, bazı durumlarda yenilemenin atlandığını belirtir; her sıfır dışı sonuç hata değildir. Üstelik hiç düzenlenmemiş alan adı da bu kodu verebilir. Bu nedenle örnek önceden dışa aktarılmış sertifika dosyasını arar ve dağıtım öncesi ayrıca geçerlilik kontrolü yapar.

Birincil işlem başarısız olduğunda blocked dosyası sonraki zamanlayıcı denemelerini durdurur. Yalnız CA kaynaklı olay önceden teşhis edilmiş ve env dosyasında ALLOW_FALLBACK=1 yapılmışsa o çalıştırmada ZeroSSL denenir. Bu bayrak bir hata sınıflandırıcısı değildir; DNS, kimlik bilgisi, CAA ve yerel dosya hatalarında açılmamalıdır. Ortak eski logdan “rate” veya “timeout” sözcüğü arayarak karar vermeyiz; her denemenin ayrı logu vardır.

Nginx TLS sunucu bloğunu aşağıdaki sabit yollara yönlendirin. Mevcut ayarların yedeğini tutun; ilk betik çalışması dosyaları yerleştirip nginx -t başarılı olmadan reload yapmaz. Hazırlık dizinleri yalnız root’a açıktır; Nginx’in root ana süreci sertifikaları okuyabilmelidir. Rootless kurulumda ayrı, dar yetkili dağıtım mekanizması gerekir.

ssl_certificate     /etc/ssl/acme-example/current/fullchain.pem;
ssl_certificate_key /etc/ssl/acme-example/current/key.pem;

Aşağıdaki betiği /usr/local/sbin/acme-fallback.sh olarak kaydedin. Geçerli sertifika, anahtar eşleşmesi ve zincir kontrolünden sonra yeni dosyalar ayrı bir sürüm dizinine alınır. Tek symlink değişimi dosya çiftini birlikte seçer; Nginx testi veya reload başarısızsa önceki yol geri konur. Dosyayı değiştirmek, dışarıdan sunulan sertifikanın doğrulandığı anlamına gelmez; bunu ayrıca test edeceğiz.

#!/usr/bin/env bash
set -euo pipefail
umask 077

ACME=/opt/acme.sh/acme.sh
STATE=/var/lib/acme-fallback
LIVE=/etc/ssl/acme-example
DOMAIN=example.com

exec 9>"$STATE/renew.lock"
flock -n 9 || exit 0
[[ ! -e "$STATE/blocked" ]] || exit 1
# Trusted root-owned file, mode 0600; no untrusted input.
source /etc/acme-fallback/env
ALLOW_FALLBACK=${ALLOW_FALLBACK:-0}

acme_for() {
  local ca=$1
  shift
  "$ACME" --home /opt/acme.sh \
    --config-home "$STATE/$ca/config" \
    --cert-home "$STATE/$ca/certs" --server "$ca" "$@"
}

renew_one() {
  local ca=$1 log rc
  log=$(mktemp "$STATE/logs/$ca.log.XXXXXXXX") || return 1
  if acme_for "$ca" --renew -d "$DOMAIN" --ecc --log "$log"; then
    return 0
  else
    rc=$?
    # acme.sh uses 2 for skipped renewals, including unissued domains.
    if [[ $rc -eq 2 && -s "$STATE/$ca/stage/leaf.pem" ]]; then
      return 0
    fi
    printf 'Renewal failed for %s; inspect %s\n' "$ca" "$log" >&2
    return 1
  fi
}

deploy_one() {
  local ca=$1 stage cert_key private_key release previous name
  stage="$STATE/$ca/stage"
  # Validate the first certificate in the fullchain that Nginx will load.
  openssl x509 -in "$stage/fullchain.pem" -noout -checkend 86400 || return 1
  for name in "$DOMAIN" "www.$DOMAIN"; do
    openssl verify -purpose sslserver -verify_hostname "$name" \
      -CApath /etc/ssl/certs -untrusted "$stage/fullchain.pem" \
      "$stage/fullchain.pem" || return 1
  done
  openssl pkey -in "$stage/key.pem" -check -noout || return 1
  cert_key=$(openssl x509 -in "$stage/fullchain.pem" -pubkey -noout |
    openssl pkey -pubin -outform DER | sha256sum) || return 1
  private_key=$(openssl pkey -in "$stage/key.pem" -pubout -outform DER |
    sha256sum) || return 1
  [[ "$cert_key" == "$private_key" ]] || return 1
  if cmp -s "$stage/fullchain.pem" "$LIVE/current/fullchain.pem" &&
     cmp -s "$stage/key.pem" "$LIVE/current/key.pem"; then
    return 0
  fi
  release=$(mktemp -d "$LIVE/releases/release.XXXXXXXX") || return 1
  install -m 600 "$stage/key.pem" "$release/key.pem" || return 1
  install -m 600 "$stage/fullchain.pem" "$release/fullchain.pem" || return 1
  previous=$(readlink -f "$LIVE/current" || true)
  ln -sfn "$release" "$LIVE/next" || return 1
  mv -Tf "$LIVE/next" "$LIVE/current" || return 1
  if nginx -t && systemctl reload nginx; then
    printf 'Certificate installed from %s\n' "$ca"
    return 0
  fi
  if [[ -n "$previous" && -d "$previous" ]]; then
    ln -sfn "$previous" "$LIVE/next" || return 1
    mv -Tf "$LIVE/next" "$LIVE/current" || return 1
    nginx -t && systemctl reload nginx || true
  else
    rm -f "$LIVE/current"
  fi
  return 1
}

selected=letsencrypt
if ! renew_one letsencrypt; then
  # Stop later timer runs until the incident and Retry-After are reviewed.
  touch "$STATE/blocked"
  [[ "$ALLOW_FALLBACK" == 1 ]] || exit 1
  # Set this flag only for a diagnosed CA-side incident, not a DNS/token error.
  renew_one zerossl || exit 1
  selected=zerossl
fi
# Deployment failure must never trigger another CA order.
if ! deploy_one "$selected"; then
  touch "$STATE/blocked"
  exit 1
fi

Bu örnek root yetkileri ve tek bir sabit alan adı içindir; tenant girdisini dosya yolu veya kabuk komutu olarak kabul eden genel bir hizmet değildir. blocked oluştuğunda ilgili servis hatasını ve o çalıştırmanın logunu inceleyin. Düzeltme tamamlanıp Retry-After zamanı beklendikten sonra fallback bayrağını yeniden 0 yapın, dosyayı kaldırın ve tek bir kontrollü servis çalıştırmasıyla sonucu gözleyin. Dağıtım hatası yeni bir CA siparişini tetiklemez.

Zamanlayıcı, oran limitleri ve yenileme sırası

Servis ile timer tanımlarını ayrı dosyalara kaydedin. Örnekte root kullanımı, özel anahtar yolları ve Nginx yönetimiyle tutarlıdır. Varsayılan yetkileriyle User=acme yazıp aynı dizinlere yazmasını veya Nginx’i reload etmesini beklemeyin. Daha dar servis hesabı kullanılacaksa yalnız gereken dosya erişimi ve sabit dağıtım komutu için yetki tasarlayın.

# /etc/systemd/system/acme-fallback.service
[Unit]
Description=Reviewable ACME renewal and controlled CA fallback
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
User=root
UMask=0077
ExecStart=/usr/local/sbin/acme-fallback.sh
# /etc/systemd/system/acme-fallback.timer
[Unit]
Description=Daily ACME renewal check

[Timer]
OnCalendar=daily
RandomizedDelaySec=1800
Persistent=true

[Install]
WantedBy=timers.target
bash -n /usr/local/sbin/acme-fallback.sh
chmod 700 /usr/local/sbin/acme-fallback.sh
systemctl daemon-reload
systemctl start acme-fallback.service
systemctl status acme-fallback.service --no-pager
journalctl -u acme-fallback.service --since today
# Enable the timer only after checking the first deployment.
systemctl enable --now acme-fallback.timer
systemctl list-timers acme-fallback.timer

Timer günlük kontrol yapar; her gün zorla yeni sertifika istemez. Yenileme zamanını istemci belirler. Kaynakta desteklenen ACME Renewal Information (ARI) davranışını korumak için normal yenilemede --renew kullanın; rutin --force yeni sipariş yükü ve limit riski yaratır. Let’s Encrypt oran limitleri ve Retry-After yanıtı, yeniden deneme zamanını belirler; 429 tek başına otomatik CA değişikliği kararı değildir.

Filoyu küçük gruplara bölün, aynı DNS bölgesindeki paralelliği sınırlayın ve gecikme ekleyin. SAN veya wildcard seçimini yalnız sertifika sayısına göre yapmayın: aynı özel anahtarın etki alanı ve yenilemede tüm adların doğrulanması gereksinimi de önemlidir. Çok alan adında SSL ve oran limiti planlaması bu kapasite kararlarını tamamlar.

Anahtarlar, dağıtım ve geri dönüş

API token’ı, CA hesap anahtarları ve sertifika özel anahtarları ayrı hassas verilerdir. Config, cert-home, hazırlık, log ve sürüm dizinlerini yedeklerken erişimlerini koruyun. Normal çalışmada ayrıntılı debug açmayın; hata incelemesi için açılan logları paylaşmadan önce sırları ayıklayın. Özel anahtarları hata bildirimi içine kopyalamayın.

Başarılı reload sonrasında bile eski sürüm dizinini hemen silmeyin. Dosya sistemi yedeğini ve bilinen iyi Nginx yapılandırmasını koruyun. İlk kurulum başarısız olup önceki current bağlantısı yoksa otomatik sertifika geri dönüşü yapılamaz; yedeklenen TLS yapılandırması ve sertifika yolları üzerinden geri dönün. Root’un bulunmadığı konteynerlerde bu dağıtım örneğini aynen kullanmayın.

Sunulan sertifikayı ve yenileme durumunu gözleyin

Servis sonucu, CA siparişi ve dışarıdan görülen HTTPS durumu ayrı ölçümlerdir. Aşağıdaki istemci kontrolü SNI ve hostname doğrulamasını kullanır; yalnız bağlantı kurulması geçerli sertifika sunulduğunu kanıtlamaz. DNS veya yük dengeleme birden fazla uç noktaya gidiyorsa her düğümü ayrıca kontrol edin.

openssl s_client -connect example.com:443 -servername example.com \
  -verify_hostname example.com -verify_return_error </dev/null
openssl x509 -in /etc/ssl/acme-example/current/fullchain.pem \
  -noout -issuer -dates -ext subjectAltName

Sunulan sertifikanın bitiş tarihi için bağımsız alarm kurun. blocked dosyası, başarısız systemd birimi, yeni CA seçimi ve hazırlık dosyalarının eskimesi de izlenmelidir. Örnekteki 24 saatlik dağıtım kontrolü operasyonel yenileme hedefi değildir; alarm ve müdahale payını daha erken belirleyin. Bekleyen olay için yalnız günlük timer’a güvenmeyin.

Test ortamı, canary ve çift sertifika

Let’s Encrypt staging hesabını üretimden ayrı config/cert-home ve ayrı test sunucusuyla kullanın. Staging sertifikaları herkese açık güven zincirine sahip değildir; canlı sertifika yoluna kurmayın. ZeroSSL için test uç noktası bulunduğunu varsaymayın; mevcut hesabın resmi koşullarını kontrol edin. Bir test alanında CA başarısızlığı, yanlış DNS yetkisi, kilit çakışması, eksik dosya ve başarısız reload senaryolarını ayrı ayrı deneyin.

Canary, seçilmiş küçük bir alan adı grubunda yenileme ve dağıtım yolunu önce gözlemlemek demektir; her gün zorla sertifika almak değildir. İstemci güncellemesi veya CA değişikliğinden sonra zinciri farklı istemcilerle doğrulayın. Yeni CA hesabı veya EAB kaydının başarılı olması, her alan adına sertifika alınabildiğini göstermez.

Eski istemci ihtiyacı varsa RSA ve ECDSA birlikte sunulabilir; her ikisi de aynı CA köklerine her istemcide güvenildiği anlamına gelmez. RSA için ayrı anahtar tipi, sertifika kaydı, hazırlık ve canlı dosya yolları kullanın; --keylength 2048 gibi açık seçim yapın ve RSA yenilemesinde --ecc kullanmayın. Nginx yapılandırmasını iki sertifika için ayrıca test edin. Ana örnek tek ECDSA sertifika çiftini yönetir; RSA’yı aynı dosyaların üzerine yazmaz.

Örnek olay: birincil CA kesintisi

Varsayımsal bir örnekte Let’s Encrypt yenilemesi başarısız olur; çalışan sertifika henüz geçerlidir. Betik yeni denemeleri engelleyen dosyayı oluşturur. Operatör o çalıştırmanın logunu ve CA durumunu kontrol eder; DNS TXT kaydının oluşturulabildiğini, CAA ve hesap izinlerini doğrular. Sorun CA tarafındaysa ve yedek CA kullanımı uygunsa bir sonraki kontrollü çalıştırma için fallback bayrağını açar ve blok dosyasını kaldırır.

Birincil yine başarısız olursa ZeroSSL’deki kayıt yenilenir veya mevcut geçerli sertifika kullanılır. Dağıtım kontrolleri ve Nginx testi geçerse dosyalar etkinleştirilir. Blok dosyası sonraki otomatik denemeleri durdurmaya devam eder; operatör dışarıdan sertifikayı kontrol eder, yeniden deneme zamanını planlar ve bayrağı kapatır. Birincil yolun dönüşü de planlı kontrolden geçer; sırf varsayılan CA değişti diye canlı sertifikanın değiştiği varsayılmaz. Bu, ölçülmüş bir kesintisizlik veya maliyet sonucu değildir.

Kullanılan resmi kaynaklar

Uygulamadan önce son kontrol

  • Aynı alan adı ve SAN listesi iki CA’da da doğrulanıyor; CAA izinleri uygun.
  • İlk sertifikalar ayrı dizinlere alındı; anahtar, zincir ve DNS adı kontrol edildi.
  • Tek zamanlayıcı var; skip kodu hata sayılmıyor, rutin –force kullanılmıyor.
  • Fallback yalnız teşhis edilmiş olayda açılıyor; tekrar denemeler blok ve Retry-After ile yönetiliyor.
  • Dağıtım hatası CA değiştirmiyor; önceki dosyalar ve yapılandırma geri getirilebiliyor.
  • Canlı uç noktaların bitiş tarihi bağımsız izleniyor; servis ve olay bildirimleri bir sorumluya ulaşıyor.

Önce sahip olduğunuz test alanında ilk alım, yenileme, kontrollü geçiş ve geri dönüşü tamamlayın. Ardından bir canary grubuna geçin; ancak sonuçları gördükten sonra kapsamı büyütün.

Sıkça Sorulan Sorular

Önce hatanın CA kaynaklı olduğunu doğrulayın; DNS, CAA ve token sorunlarını düzeltin. Bu örnekte olay için fallback bayrağı açılır, incelenen blok kaldırılır ve tek kontrollü çalışma başlatılır. Yeni sertifika kontrol edilmeden Nginx’e aktarılmaz.

DNS-01 wildcard doğrulamasını destekler ve webroot erişimine bağlı değildir; DNS API yetkisi gerektirir. HTTP-01 uygun webroot erişimi olan basit kurulumlarda yeterli olabilir. Her iki yöntemin önkoşulları ortamda doğrulanmalıdır.

Normal yenilemede istemcinin takvimini ve varsa ARI desteğini kullanın. Retry-After süresine uyun, işleri küçük gruplara dağıtın ve rutin --force kullanmayın. İkinci CA da kendi koşullarına ve limitlerine tabidir.
↑