Bali, Indonesia
Insights

Cara Cek Rantai Sertifikat SSL Lengkap Setelah Pindah dari Apache ke Nginx

October 28, 2026
0:00
Situs baru dipindah dari Apache ke Nginx. Dibuka di Chrome desktop semuanya normal dan gembok muncul. Tapi sebagian pengguna HP melihat peringatan sertifikat tidak tepercaya, dan curl dari server lain gagal dengan pesan SSL certificate problem: unable to get local issuer certificate. Penyebab yang paling sering adalah Nginx hanya menyajikan sertifikat domain tanpa sertifikat intermediate. Sertifikatnya sendiri tidak salah. Yang salah adalah cara file sertifikat dipasang saat migrasi.
Sertifikat SSL bekerja sebagai rantai. Sertifikat domain (sering disebut leaf) diterbitkan oleh sertifikat intermediate. Intermediate itu ditandatangani oleh root CA yang sudah tersimpan di trust store perangkat, yaitu daftar CA yang dipercaya oleh OS atau aplikasi. Supaya klien bisa menyambungkan leaf ke root, server wajib mengirim leaf dan intermediate. Root tidak perlu dikirim karena klien sudah punya. Kalau server lupa mengirim intermediate, sebagian browser desktop bisa menambal sendiri. Caranya bisa dengan mengunduh intermediate dari alamat yang tercantum di dalam sertifikat (mekanisme AIA, Authority Information Access), atau dengan memakai intermediate yang tersimpan dari kunjungan ke situs lain. curl, OpenSSL, banyak library HTTP di aplikasi, dan sebagian perangkat mobile biasanya tidak melakukan itu, jadi mereka langsung menolak. Detail perbedaan perilaku ini sudah dibahas di artikel Why curl Rejects an SSL Certificate That Works in Your Browser. Dampaknya di meja support cukup jelas. Laporan pemilik situs bahwa di laptopnya aman tidak bisa dijadikan patokan. Pengecekan harus dilakukan dengan tool yang tidak menambal rantai, dan openssl s_client adalah pilihan paling jujur.
Di Apache dengan mod_ssl, konfigurasi lama biasanya memakai tiga directive. SSLCertificateFile untuk sertifikat domain, SSLCertificateKeyFile untuk private key, dan SSLCertificateChainFile untuk intermediate. Sejak Apache 2.4.8, SSLCertificateChainFile sudah deprecated dan SSLCertificateFile boleh berisi leaf beserta intermediate. Meski begitu, banyak konfigurasi lama masih memakai tiga file terpisah. Masalah muncul saat migrasi. Orang memetakan SSLCertificateFile ke ssl_certificate dan SSLCertificateKeyFile ke ssl_certificate_key, lalu mencari padanan untuk chain file dan tidak menemukannya. Nginx memang tidak punya directive terpisah untuk intermediate yang dikirim ke pengunjung. File yang ditunjuk ssl_certificate harus berisi leaf lebih dulu, lalu diikuti intermediate, dalam satu file PEM. Ada dua jebakan lain di sini. Pertama, urutan isi file itu penting. Nginx mencocokkan private key dengan sertifikat pertama di dalam file. Kalau intermediate ditaruh paling atas, uji konfigurasi biasanya gagal dengan error ketidakcocokan key, misalnya key values mismatch. Kedua, ssl_trusted_certificate bukan solusinya. Directive itu dipakai untuk memverifikasi OCSP stapling dan sertifikat klien. Isinya tidak dikirim ke pengunjung. Rantai tetap bolong walaupun intermediate sudah ditaruh di situ. Kalau sertifikatnya dari Let's Encrypt via Certbot, folder /etc/letsencrypt/live/<domain>/ berisi cert.pem (leaf saja), chain.pem (intermediate saja), fullchain.pem (leaf dan intermediate), dan privkey.pem. Kesalahan klasiknya adalah mengarahkan ssl_certificate ke cert.pem karena namanya mirip dengan konfigurasi Apache yang lama.
Mulai dari apa yang benar-benar dikirim server, bukan dari isi file konfigurasi. Jalankan ini dari mesin mana saja yang punya OpenSSL dan curl. Semua perintah di bawah hanya membaca, tidak mengubah apa pun.
BASH
# Lihat rantai yang benar-benar dikirim server (ganti example.com)
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null

# Pembanding dari klien yang tidak menambal rantai: curl tanpa -k
curl -sSv -o /dev/null https://example.com/ 2>&1 | grep -iE 'SSL certificate|subject|issuer'
Opsi -servername mengirim SNI, yaitu nama domain yang diminta saat handshake. Opsi ini wajib kalau satu IP melayani banyak domain. Tanpa SNI, kamu bisa saja melihat sertifikat dari server block default dan menarik kesimpulan yang salah. Opsi -showcerts menampilkan semua sertifikat yang dikirim server, bukan hanya leaf. Untuk mengetes server baru sebelum DNS dipindah, ganti bagian host di -connect dengan IP server baru dan tetap isi -servername dengan nama domain. Di output, cari blok Certificate chain. Setiap sertifikat diberi nomor kedalaman. Baris yang diawali 0 s: adalah subject leaf, dan baris i: di bawahnya adalah issuer atau penerbitnya. Rantai yang sehat setidaknya punya entri 1 s: yang subject-nya sama dengan issuer milik entri 0. Kalau yang muncul hanya entri 0, berarti server cuma mengirim leaf. Bagian bawah output juga memberi petunjuk. Verify return code: 0 (ok) berarti rantai lengkap sampai ke root yang dipercaya. Verify return code: 21 (unable to verify the first certificate) adalah tanda khas intermediate tidak dikirim. Format output ini sedikit berbeda antar versi OpenSSL, jadi kalau ada baris tambahan di blok chain, cukup fokus ke baris s: dan i:.
Setelah yakin rantainya bolong, cari tahu file mana yang dipakai Nginx dan apa isinya. Jangan menebak dari satu file konfigurasi saja. Ada kemungkinan server block lain, file include, atau konfigurasi buatan panel yang menimpanya.
BASH
# File sertifikat yang benar-benar dimuat Nginx, per server_name (butuh root)
sudo nginx -T 2>/dev/null | grep -E 'server_name|ssl_certificate'

# Hitung isi file: fullchain minimal berisi 2 sertifikat
grep -c 'BEGIN CERTIFICATE' /etc/letsencrypt/live/example.com/fullchain.pem

# Subject dan issuer setiap sertifikat di file, sesuai urutannya
openssl crl2pkcs7 -nocrl -certfile /etc/letsencrypt/live/example.com/fullchain.pem | openssl pkcs7 -print_certs -noout

# Cari dua sertifikat yang menempel di satu baris akibat file digabung tanpa newline
grep -n 'END CERTIFICATE-----.*BEGIN' /etc/letsencrypt/live/example.com/fullchain.pem

# Verifikasi lokal: leaf dicek dengan bantuan intermediate
openssl verify -untrusted /etc/letsencrypt/live/example.com/chain.pem /etc/letsencrypt/live/example.com/cert.pem
Baca hasil perintah pkcs7 -print_certs dari atas ke bawah. Sertifikat pertama harus leaf domainmu. Issuer-nya harus sama dengan subject sertifikat kedua. Kalau urutannya terbalik atau isinya cuma satu sertifikat, ketemulah sumber masalahnya. Pengecekan sambungan satu baris sering terlewat. Kalau file sertifikat dari CA tidak diakhiri baris baru lalu digabung begitu saja, penanda akhir sertifikat pertama dan penanda awal sertifikat kedua bisa menempel di satu baris. Hasilnya, file gagal dimuat atau sertifikat kedua tidak terbaca. Kalau grep itu menemukan sesuatu, perbaiki file sumbernya dulu. Sesuaikan path di atas dengan setup kamu. Panel kontrol dan ACME client selain Certbot sering menyimpan sertifikat di lokasi dan nama file yang berbeda. Path yang benar ada di output nginx -T.
Perbaikannya kecil. Yang perlu dijaga hanya urutannya dan uji sebelum reload.
BASH
# Certbot: arahkan ssl_certificate ke fullchain.pem, bukan cert.pem
#   ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
#   ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

# CA komersial: gabungkan leaf dulu, baru intermediate.
# Hati-hati, tanda > menimpa file tujuan. Pakai nama file baru.
cat example.com.crt intermediate-bundle.crt > example.com.fullchain.crt

# Uji konfigurasi, reload hanya kalau lolos
sudo nginx -t && sudo systemctl reload nginx

# Ulangi cek dari sisi klien, tampilkan hanya baris yang penting
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null | grep -E '^ *[0-9]+ s:|Verify return code'
Perintah cat di atas tidak menyentuh file asli, tapi akan menimpa example.com.fullchain.crt kalau file itu sudah ada. Setelah digabung, jalankan lagi pengecekan menempel dan hitung jumlah sertifikat dari bagian sebelumnya pada file baru, baru arahkan ssl_certificate ke file itu. Setelah reload, hasil akhirnya harus menampilkan entri 0 s: dan 1 s:, ditambah Verify return code: 0 (ok). Ulangi juga perintah curl dari bagian pertama tanpa opsi -k. Kalau sudah tidak ada pesan SSL certificate problem, klien yang ketat pun seharusnya ikut menerima. Cek semua varian domain, bukan hanya domain utama. Server block untuk www, subdomain lain, atau blok listen IPv6 yang terpisah bisa saja masih menunjuk ke file lama. Output nginx -T tadi membantu memastikan tidak ada yang tertinggal. Untuk jangka panjang, mengarah ke fullchain.pem di folder live milik Certbot itu aman. Saat renewal, file itu diperbarui beserta intermediate-nya. Untuk sertifikat dari CA komersial, bundle intermediate bisa saja berubah saat perpanjangan, jadi gabungkan ulang dengan bundle terbaru setiap kali sertifikat diganti. Kebiasaan yang paling berguna setelah migrasi web server adalah menjadikan openssl s_client dengan -servername dan -showcerts sebagai langkah wajib sebelum menyatakan SSL beres. Tampilan gembok di browser desktop bukan bukti. Rantai yang lengkap berarti server mengirim leaf dan intermediate dengan urutan yang benar, dan klien yang tidak memaafkan kesalahan seperti curl ikut menerimanya.