Bir web sunucusunun SSL sertifikası var, HTTPS bağlantısı kurulabiliyor ama tarayıcı hâlâ “Bağlantınız güvenli değil” diyor. Ya da curl çalışıyor ama Java uygulaması bağlanamıyor. Bu saçma görünen durum aslında çoğu zaman sertifika zinciri sorunlarından kaynaklanıyor. SSL sertifika zinciri sorunları, sysadminlerin en çok zaman kaybettiği konuların başında geliyor çünkü hata mesajları genellikle yanıltıcı oluyor ve sorunun tam olarak nerede olduğunu bulmak sabır istiyor.
SSL Sertifika Zinciri Nedir?
Sertifika zincirini anlamadan sorunu çözmek neredeyse imkânsız. O yüzden önce temeli sağlam atalım.
Bir SSL sertifikası tek başına anlam taşımıyor. Tarayıcı ya da istemci, sunucunun sunduğu sertifikaya güvenip güvenmeyeceğine karar verirken bir “güven zinciri” izliyor. Bu zincir şu katmanlardan oluşuyor:
- Root CA (Kök Sertifika Otoritesi): İşletim sistemi veya tarayıcının güven deposunda bulunan, nihai güven kaynağı. Örneğin DigiCert, Let’s Encrypt ISRG Root X1, Comodo gibi.
- Intermediate CA (Ara Sertifika Otoritesi): Root CA’nın altında, kendi sertifikasını kök ile imzalatmış ara otorite. Çoğu zaman bir veya iki tane olabiliyor.
- End-Entity / Leaf Certificate: Sunucunuzda kurulu olan asıl sertifika. example.com için verilen sertifika budur.
Zincir şöyle çalışıyor: Tarayıcı sunucu sertifikasını alıyor, bunu imzalayan Intermediate CA’yı buluyor, onu imzalayan Root CA’yı buluyor ve eğer Root CA kendi güven deposunda varsa “tamam, güvenli” diyor. Bu zincirin herhangi bir halkası eksikse veya yanlışsa bağlantı hata veriyor.
En yaygın sorun: Sunucu yapılandırmasında Intermediate CA sertifikası eksik bırakılıyor. Leaf sertifika var, Root zaten tarayıcıda var, ama ortadaki halka yok.
Tanılama: Sorunu Tespit Etmek
OpenSSL ile Zinciri Kontrol Etme
Elinizin altındaki en güçlü araç OpenSSL. Şu komutla sunucunun sunduğu sertifika zincirini görebilirsiniz:
openssl s_client -connect example.com:443 -showcerts
Bu komutun çıktısında dikkat etmeniz gereken birkaç şey var. Önce “Certificate chain” bölümüne bakın:
openssl s_client -connect example.com:443 -showcerts 2>/dev/null | grep -E "s:|i:"
Sağlıklı bir çıktı şöyle görünmeli:
0 s:CN = example.com
i:CN = R3, O = Let's Encrypt, C = US
1 s:CN = R3, O = Let's Encrypt, C = US
i:CN = ISRG Root X1, O = Internet Security Research Group, C = US
Burada s: subject (bu sertifika), i: ise issuer (bunu imzalayan) anlamına geliyor. Eğer sadece 0 numaralı satırı görüyorsanız ve 1 numaralı ara sertifika yoksa, zincir eksik demektir.
SNI (Server Name Indication) kullanan sunucularda doğru sanal host sertifikasını görmek için:
openssl s_client -connect example.com:443 -servername example.com -showcerts
SSL Labs ile Uzaktan Test
Sunucu dışarıdan erişilebiliyorsa Qualys SSL Labs en kapsamlı testi yapıyor. Ama komut satırından da benzer bir test yapabilirsiniz:
curl -s "https://api.ssllabs.com/api/v3/analyze?host=example.com&startNew=on&all=done" | python3 -m json.tool | grep -E "grade|hasWarnings|incomplete"
Bu API biraz bekletebilir, sonuç hazır olana kadar polling yapmanız gerekebilir.
Sertifika Dosyasını Doğrudan İnceleme
Elinizde bir .pem veya .crt dosyası varsa:
# Tek sertifika bilgilerini göster
openssl x509 -in certificate.crt -text -noout | grep -E "Issuer:|Subject:|Not After"
# Zincir dosyasındaki tüm sertifikaları listele
openssl crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -text -noout | grep -E "Subject:|Issuer:"
Yaygın Sorunlar ve Çözümleri
1. Eksik Intermediate Sertifika
En sık karşılaşılan durum bu. Sunucu sadece leaf sertifikayı sunuyor, ara sertifikayı unuttunuz.
Tespit:
openssl s_client -connect example.com:443 2>&1 | grep "verify error"
# Çıktı: verify error:num=20:unable to get local issuer certificate
Çözüm – Nginx için:
# /etc/nginx/sites-available/example.com
server {
listen 443 ssl;
server_name example.com;
# Sadece sertifika DEĞİL, fullchain kullanın
# Yanlış:
# ssl_certificate /etc/ssl/certs/example.com.crt;
# Doğru - leaf + intermediate birleşik dosya:
ssl_certificate /etc/ssl/certs/fullchain.pem;
ssl_certificate_key /etc/ssl/private/example.com.key;
}
fullchain.pem dosyasını kendiniz oluşturabilirsiniz:
# Sıralama önemli: önce leaf, sonra intermediate(ler), en sona root (isteğe bağlı)
cat example.com.crt intermediate.crt > fullchain.pem
# Doğrulama
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt fullchain.pem
Apache için:
# /etc/apache2/sites-available/example.com.conf
<VirtualHost *:443>
SSLEngine on
SSLCertificateFile /etc/ssl/certs/example.com.crt
# Apache 2.4.8+ için SSLCertificateChainFile yerine bunu kullanın:
SSLCertificateFile /etc/ssl/certs/fullchain.pem
SSLCertificateKeyFile /etc/ssl/private/example.com.key
</VirtualHost>
2. Yanlış Sertifika Sıralaması
Zincirdeki sertifikalar yanlış sırayla birleştirilmiş. Doğru sıra: leaf -> intermediate -> (root).
# Dosyadaki sertifikaları sırayla kontrol et
awk 'BEGIN{n=0} /-----BEGIN CERTIFICATE-----/{n++; print "=== Sertifika " n " ==="}
/Subject:/{print} /Issuer:/{print}' fullchain.pem
Eğer sıra yanlışsa yeniden oluşturun. Ara sertifikanın issuer’ı, root sertifikanın subject’iyle eşleşmeli. Leaf’in issuer’ı, intermediate’in subject’iyle eşleşmeli.
3. Sertifika – Özel Anahtar Uyumsuzluğu
Sunucu başlıyor ama el sıkışma sırasında sorun yaşıyorsunuz. Sertifika ile private key’in eşleşip eşleşmediğini kontrol edin:
# Her iki komutun çıktısı aynı olmalı
openssl x509 -noout -modulus -in certificate.crt | openssl md5
openssl rsa -noout -modulus -in private.key | openssl md5
# Farklıysa sertifika ve anahtar eşleşmiyor demektir
4. Süresi Dolmuş Intermediate Sertifika
Leaf sertifikası geçerli ama zincirdeki bir ara sertifika süresi dolmuş. Bu durum özellikle Let’s Encrypt’in DST Root CA X3 süresi dolduğunda (Eylül 2021) büyük sorun yarattı.
# Tüm zincirdeki sertifikaların geçerlilik tarihlerini kontrol et
openssl s_client -connect example.com:443 -showcerts 2>/dev/null |
awk '/-----BEGIN CERTIFICATE-----/,/-----END CERTIFICATE-----/' |
csplit --quiet - '/-----BEGIN CERTIFICATE-----/' '{*}' &&
for f in xx*; do
echo "=== $f ==="
openssl x509 -in "$f" -noout -subject -dates 2>/dev/null
done
Java ve Özel Güven Depoları
Java kendi güven deposunu (cacerts / truststore) kullanıyor. İşletim sistemi güven deposuna güvenmiyor. Bu yüzden tarayıcı kabul etse bile Java uygulaması reddedebiliyor.
# Java güven deposundaki sertifikaları listele
keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit | grep -i "example|digicert|letsencrypt"
# Yeni bir sertifika ekle
keytool -import -trustcacerts -keystore $JAVA_HOME/lib/security/cacerts
-storepass changeit -noprompt
-alias "intermediate-ca"
-file intermediate.crt
# Sonucu doğrula
keytool -list -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -alias "intermediate-ca"
Microservice ortamında her servis için ayrı truststore kullanıyorsanız, sertifika güncellemelerini otomatize etmek şart. Aksi hâlde her deployment’ta elle müdahale gerekiyor.
Gerçek Dünya Senaryosu: Production’da Gece Yarısı Krizi
Klasik bir senaryo: Cuma akşamı saat 23:00, monitoring alert geliyor, e-ticaret sitesi HTTPS üzerinden erişilemiyor. Müşteriler ödeme yapamıyor.
İlk yapılacak şey panik yapmadan hızlıca tanılamak:
# Hızlı durum tespiti
echo | openssl s_client -connect shop.example.com:443 -servername shop.example.com 2>&1 |
grep -E "Verify return code|depth|error|Issuer|Subject|notAfter"
# Eğer sertifika süresi dolmuşsa
echo | openssl s_client -connect shop.example.com:443 2>/dev/null |
openssl x509 -noout -enddate
Diyelim ki sorun şu çıktıyı verdi:
verify error:num=21:unable to verify the first certificate
Verify return code: 21 (unable to verify the first certificate)
Bu durumda intermediate sertifika eksik. Let’s Encrypt kullanıyorsanız hızlı çözüm:
# Certbot ile yeniden düzenle
certbot certificates # Mevcut sertifikaları listele
certbot renew --cert-name shop.example.com --force-renewal
# Nginx reload
nginx -t && systemctl reload nginx
Eğer certbot yoksa manuel:
# Let's Encrypt intermediate'i indir
curl -O https://letsencrypt.org/certs/lets-encrypt-r3.pem
# Fullchain oluştur
cat /etc/ssl/certs/shop.example.com.crt lets-encrypt-r3.pem > /etc/ssl/certs/fullchain.pem
# Nginx config'i güncelle ve test et
nginx -t
systemctl reload nginx
# Doğrula
openssl s_client -connect shop.example.com:443 -showcerts 2>/dev/null | grep -E "s:|i:"
Monitoring: Sorunları Önceden Yakalamak
Reactive olmak yerine proactive olmak için basit bir monitoring scripti:
#!/bin/bash
# ssl-chain-check.sh
# Kullanım: ./ssl-chain-check.sh domain.com [port]
DOMAIN="${1}"
PORT="${2:-443}"
WARNING_DAYS=30
CRITICAL_DAYS=7
if [ -z "$DOMAIN" ]; then
echo "Kullanim: $0 domain.com [port]"
exit 1
fi
# Sertifika bilgilerini al
CERT_INFO=$(echo | openssl s_client -connect "${DOMAIN}:${PORT}"
-servername "${DOMAIN}" 2>/dev/null | openssl x509 -noout -dates -subject 2>/dev/null)
if [ $? -ne 0 ]; then
echo "CRITICAL: ${DOMAIN} - Baglanti kurulamadi veya sertifika alinamadi"
exit 2
fi
# Bitis tarihini hesapla
END_DATE=$(echo "$CERT_INFO" | grep "notAfter" | cut -d= -f2)
END_EPOCH=$(date -d "${END_DATE}" +%s 2>/dev/null || date -j -f "%b %d %T %Y %Z" "${END_DATE}" +%s)
NOW_EPOCH=$(date +%s)
DAYS_LEFT=$(( (END_EPOCH - NOW_EPOCH) / 86400 ))
# Zincir kontrolü
CHAIN_DEPTH=$(echo | openssl s_client -connect "${DOMAIN}:${PORT}"
-servername "${DOMAIN}" -showcerts 2>/dev/null | grep -c "BEGIN CERTIFICATE")
echo "Domain: ${DOMAIN}"
echo "Kalan gun: ${DAYS_LEFT}"
echo "Zincir derinligi: ${CHAIN_DEPTH}"
if [ "${CHAIN_DEPTH}" -lt 2 ]; then
echo "WARNING: Eksik intermediate sertifika olabilir (zincir derinligi: ${CHAIN_DEPTH})"
fi
if [ "${DAYS_LEFT}" -lt "${CRITICAL_DAYS}" ]; then
echo "CRITICAL: Sertifika ${DAYS_LEFT} gun icinde sona eriyor!"
exit 2
elif [ "${DAYS_LEFT}" -lt "${WARNING_DAYS}" ]; then
echo "WARNING: Sertifika ${DAYS_LEFT} gun icinde sona eriyor"
exit 1
else
echo "OK: Sertifika ${DAYS_LEFT} gun gecerli"
exit 0
fi
Bu scripti crontab’a ekleyin:
chmod +x /usr/local/bin/ssl-chain-check.sh
# /etc/cron.d/ssl-check
0 8 * * * root /usr/local/bin/ssl-chain-check.sh shop.example.com | mail -s "SSL Check" [email protected]
PKCS#12 ve Özel Format Sorunları
Bazı uygulamalar PEM yerine PKCS#12 (.p12 veya .pfx) formatı istiyor. Windows IIS, bazı Java uygulamaları ve load balancer’lar bu formatta sertifika bekliyor.
# PEM'den PKCS#12 oluştur (zincir dahil)
openssl pkcs12 -export
-out certificate.p12
-inkey private.key
-in certificate.crt
-certfile intermediate.crt
-name "example.com"
-passout pass:YourPassword123
# PKCS#12 içeriğini doğrula
openssl pkcs12 -in certificate.p12 -info -noout -passin pass:YourPassword123
# PKCS#12'den PEM'e çevir
openssl pkcs12 -in certificate.p12 -out fullchain.pem -nokeys -passin pass:YourPassword123
openssl pkcs12 -in certificate.p12 -out private.key -nocerts -nodes -passin pass:YourPassword123
HAProxy ve Load Balancer Yapılandırması
HAProxy farklı bir format istiyor; hem sertifika hem key hem de zincir tek dosyada olmalı:
# HAProxy için birleşik PEM dosyası oluştur
# Sıra: private key + leaf cert + intermediate(ler)
cat private.key certificate.crt intermediate.crt > /etc/haproxy/ssl/example.com.pem
# HAProxy config
# /etc/haproxy/haproxy.cfg
# frontend https_front
# bind *:443 ssl crt /etc/haproxy/ssl/example.com.pem
# ...
# Doğrulama
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt
-untrusted intermediate.crt certificate.crt
İç Ağ ve Self-Signed Sertifika Senaryoları
Kurumsal ortamlarda kendi iç CA’nızdan imzalanmış sertifikalar kullanıyorsanız, bu sertifikaların tüm istemcilerin güven deposuna eklenmesi gerekiyor.
# Ubuntu/Debian sistemlere kurumsal CA ekle
sudo cp internal-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates
# RHEL/CentOS/Rocky Linux
sudo cp internal-ca.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust extract
# Eklemenin başarılı olup olmadığını doğrula
openssl verify -verbose internal-server.crt
# Çıktı: internal-server.crt: OK
# curl ile test
curl -v https://internal-service.company.local/health
Active Directory ortamında Group Policy ile tüm Windows makinelere otomatik dağıtım yapılabilir. Linux makineler için Ansible ile toplu güncelleme en temiz çözüm:
# Ansible playbook snippet - kurumsal CA dağıtımı
# - name: Deploy internal CA certificate
# copy:
# src: files/internal-ca.crt
# dest: /usr/local/share/ca-certificates/internal-ca.crt
# notify: update-ca-certificates
Hata Mesajlarını Doğru Okumak
Farklı araçlar aynı sorunu farklı mesajlarla bildiriyor. Bunları bilmek tanı sürecini hızlandırıyor:
- “unable to verify the first certificate”: Intermediate sertifika eksik veya güvenilir değil
- “certificate verify failed”: Genel doğrulama hatası, zincirde kopukluk var
- “self signed certificate in certificate chain”: Zincirde self-signed bir sertifika var veya root CA sistemde tanımlı değil
- “certificate has expired”: Sertifikanın veya zincirdeki bir sertifikanın süresi dolmuş
- “hostname mismatch”: Sertifika farklı bir domain için verilmiş (wildcard veya SAN kontrolü yapın)
- “PKIX path building failed”: Java’ya özgü, güven zinciri oluşturulamıyor, truststore’a sertifika eklenmesi gerekiyor
Sonuç
SSL sertifika zinciri sorunları göz korkutucu görünse de sistematik bir yaklaşımla çoğu sorun 15-20 dakikada çözülüyor. Önce openssl s_client ile zinciri görsel olarak doğrulayın, sonra hangi halkanın eksik veya hatalı olduğunu tespit edin, ardından uygun düzeltmeyi yapın.
En önemli nokta şu: Sertifikayı sunucuya koymak yetmiyor, zincirin tam ve doğru sırayla sunulduğundan emin olmanız gerekiyor. Farklı istemciler (tarayıcı, curl, Java, mobil uygulama) farklı davranıyor ve aynı sorun farklı hata mesajları üretiyor.
Proaktif monitoring kurmak bu sorunların production’da kriz yaratmasını önlüyor. Basit bir cron job bile sizi gece yarısı alertlerinden kurtarabilir. Sertifika sona ermeden en az 30 gün önce uyarı almak, yenilemek için yeterince zamanınız olduğu anlamına geliyor.
Son olarak: Eğer bir ortamda Let’s Encrypt kullanıyorsanız certbot’un otomatik renewal’ını mutlaka aktif edin ve düzenli olarak certbot renew --dry-run ile test edin. Manuel işlem gerektirmeyen bir süreç, insan hatasını minimuma indiriyor.