certbot certificates sudah menunjukkan tanggal kedaluwarsa yang baru, tapi browser pengunjung tetap memunculkan peringatan sertifikat expired. Reaksi pertama biasanya menyalahkan Certbot atau Let's Encrypt. Padahal dalam banyak kasus file sertifikatnya sudah benar. Yang belum berubah adalah Nginx, karena ia masih menyajikan sertifikat lama yang dimuat ke memori sejak terakhir kali di-start atau di-reload.
Tulisan ini disusun dengan format post-mortem untuk pola masalah yang umum, bukan untuk satu insiden tertentu. Urutannya: dampak awal, jalur investigasi, titik temu, mitigasi sementara, lalu perbaikan permanen supaya masalah ini tidak terulang.
Dampak awal: renewal sukses, pengunjung tetap kena peringatan
NET::ERR_CERT_DATE_INVALID di Chrome. Aplikasi mobile atau integrasi API yang memanggil domain tersebut mulai gagal di tahap handshake TLS, yaitu proses negosiasi awal sebelum data HTTPS dikirim. Monitoring yang mengecek masa berlaku sertifikat juga ikut berbunyi.
Di sisi server, semuanya terlihat normal. Timer systemd atau cron Certbot berjalan, log di /var/log/letsencrypt/letsencrypt.log mencatat renewal sukses, dan tidak ada error di log Nginx. Kombinasi inilah yang membingungkan. Di meja support, kalimat yang sering muncul dari pemilik server adalah 'tapi kan sudah auto-renew?'. Secara teknis itu benar. Yang belum terjadi adalah langkah sesudah renew.
Ada satu hal yang membuat dampaknya terasa mendadak. Certbot biasanya mencoba renew jauh sebelum sertifikat kedaluwarsa, secara default saat sisa masa berlakunya tinggal sekitar 30 hari. Akibatnya sertifikat baru bisa sudah tersimpan di disk selama berminggu-minggu, sementara Nginx tetap menyajikan sertifikat lama sampai tanggal expired-nya lewat. Tidak ada tanda apa pun sampai hari itu tiba.
Jalur investigasi: dari log Certbot ke sertifikat yang benar-benar disajikan
sudo certbot certificates menampilkan setiap lineage beserta tanggal kedaluwarsa dan path file-nya. Lineage adalah sebutan Certbot untuk satu set sertifikat beserta riwayat renewal-nya. Kalau tanggal di sini sudah baru, berarti masalahnya bukan di proses penerbitan.
Langkah kedua lebih penting: cek sertifikat yang dikirim server ke klien, bukan yang tersimpan di disk. Dua hal ini bisa berbeda, dan sumber yang paling jujur adalah openssl s_client. Bandingkan serial number dan tanggal notAfter dari kedua sumber.
BASH
# Sertifikat yang benar-benar disajikan ke klien
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -serial -enddate
# Sertifikat yang ada di disk (lineage Certbot)
sudo openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -noout -serial -enddate
# File sertifikat yang dirujuk konfigurasi Nginx yang sedang berlaku
sudo nginx -T 2>/dev/null | grep -E 'ssl_certificate'echo | di depan membuat koneksi langsung ditutup setelah handshake, jadi s_client tidak menunggu input. Opsi -servername mengirim nama domain saat handshake, sehingga di server yang menampung banyak domain kamu mendapat sertifikat milik domain yang benar. Ganti example.com dengan domain dan nama lineage milikmu.
Kalau domain berada di belakang CDN atau load balancer, s_client ke nama domain akan menunjukkan sertifikat milik edge, bukan origin. Untuk mengecek origin secara langsung, isi -connect dengan IP server dan tetap gunakan -servername dengan nama domain.
TEXT
# contoh format output, bukan data nyata
# dari s_client (yang disajikan ke klien)
serial=04AA11
notAfter=Jan 1 00:00:00 2026 GMT
# dari file di disk
serial=03BB22
notAfter=Apr 1 00:00:00 2026 GMTTitik temu: Nginx memegang sertifikat di memori
ssl_certificate dan ssl_certificate_key hanya saat start atau reload, lalu menyimpannya di memori. Ia tidak memantau perubahan file. Di sisi lain, Certbot tidak menimpa file lama. Ia menulis file baru di /etc/letsencrypt/archive/<nama>/ dengan nomor urut, lalu mengarahkan ulang symlink di /etc/letsencrypt/live/<nama>/ ke file baru tersebut. Path yang ditulis di konfigurasi Nginx tetap sama dan isinya sudah baru, tetapi Nginx tidak tahu ada perubahan sampai diminta memuat ulang.
Pertanyaan berikutnya: kenapa Certbot tidak me-reload Nginx? Jawabannya tergantung cara sertifikat itu dulu diterbitkan. Kalau memakai plugin nginx (certbot --nginx), Certbot biasanya ikut me-reload Nginx setelah renewal. Kalau sertifikat diterbitkan dengan certonly --webroot, Certbot hanya mengambil file dan tidak menyentuh web server sama sekali. Ini pola yang sering ditemui di server yang konfigurasi Nginx-nya ditulis manual.
Cara mengeceknya adalah membuka /etc/letsencrypt/renewal/<nama>.conf dan melihat key authenticator serta installer di bagian [renewalparams]. Kalau tidak ada installer = nginx dan tidak ada hook yang terdaftar, berarti memang tidak ada yang memberi tahu Nginx setelah renewal.
Ada beberapa variasi lain yang gejalanya mirip, dan output nginx -T di atas membantu membedakannya:
- Konfigurasi menunjuk ke salinan file di tempat lain, misalnya di
/etc/ssl/, bukan ke symlink dilive/. Salinan itu tidak pernah ikut diperbarui. - Ada lineage ganda seperti
example.com-0001. Ini bisa muncul kalau sertifikat pernah diterbitkan ulang dengan daftar domain yang berbeda. Akibatnya Nginx masih menunjuk ke lineage lama yang sudah tidak diperpanjang. - TLS diterminasi di load balancer atau CDN. Dalam kasus ini, me-reload origin tidak mengubah apa yang dilihat pengunjung.
Mitigasi sementara: reload, lalu verifikasi ulang
sudo nginx -t && sudo systemctl reload nginx.
Gunakan reload, bukan restart. Saat reload, master process membaca ulang konfigurasi dan sertifikat, menjalankan worker baru, lalu membiarkan worker lama menyelesaikan koneksi yang sedang berjalan. Pengunjung tidak mengalami putus koneksi.
Menjalankan nginx -t lebih dulu juga penting. Kalau ada kesalahan konfigurasi di bagian lain, mungkin dari perubahan lama yang belum pernah di-reload, kamu akan mengetahuinya sekarang. Tanpa langkah ini, reload bisa gagal dan Nginx tetap berjalan dengan konfigurasi serta sertifikat lama.
Setelah reload, jalankan ulang perintah s_client tadi. Serial dari sertifikat yang disajikan harus sama dengan serial file di disk. Kalau masih berbeda, kembali ke tiga variasi di bagian sebelumnya. Jangan langsung menerbitkan ulang sertifikat, karena hal itu tidak menyelesaikan masalah dan menambah jumlah request ke CA.
Perbaikan permanen: deploy hook dan cek dari sisi klien
renewal-hooks/deploy. Semua skrip di direktori ini berlaku untuk semua lineage di server tersebut.
BASH
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh > /dev/null <<'EOF'
#!/bin/sh
# Dijalankan Certbot hanya setelah renewal berhasil
nginx -t && systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shnginx -t di dalam hook sengaja dipertahankan. Kalau konfigurasi sedang rusak, hook akan gagal dan Certbot mencatat kegagalan itu di log-nya. Itu lebih baik daripada reload yang gagal tanpa ada yang tahu.
Alternatifnya, kamu bisa memasang opsi --deploy-hook di perintah Certbot. Untuk server dengan banyak lineage, direktori hook biasanya lebih mudah dirawat. Kalau Certbot dikelola oleh control panel, cek dokumentasi panel tersebut lebih dulu, karena panel bisa punya mekanisme reload sendiri.
Untuk mengujinya, perhatikan satu hal. certbot renew --dry-run secara default tidak menjalankan deploy hook. Di versi Certbot yang lebih baru tersedia opsi --run-deploy-hooks, jadi cek ketersediaannya lewat certbot --help all. Kalau opsi itu tidak ada, jalankan skrip hook secara manual satu kali untuk memastikan skrip bisa dieksekusi dan tidak error.
Lapisan terakhir adalah monitoring. Monitoring yang membaca tanggal dari file di disk akan terus melaporkan semuanya aman, padahal pengunjung melihat sertifikat lama. Monitoring yang berguna adalah yang melakukan handshake dari luar server, persis seperti perintah s_client tadi, lalu memberi peringatan saat sisa masa berlaku sertifikat yang disajikan sudah terlalu pendek. Dengan cara ini, kalau suatu saat hook gagal, kamu masih punya waktu berminggu-minggu untuk memperbaikinya, bukan baru tahu di hari sertifikat kedaluwarsa.
Kalau mau diringkas jadi satu kebiasaan: setiap kali ada laporan sertifikat expired padahal Certbot bilang sukses, bandingkan serial dari s_client dengan serial file di disk sebelum mengubah apa pun. Dua baris perintah itu langsung memberi tahu apakah masalahnya di penerbitan, di Nginx, atau di lapisan depan seperti CDN. Setelah itu, pasang deploy hook sekali saja, supaya renewal berikutnya benar-benar sampai ke pengunjung.