Cara buat self-signed certificate Ubuntu
Panduan membuat sertifikat TLS self-signed di Ubuntu 24.04 agar diterima Chrome. Gunakan OpenSSL dengan SAN untuk nginx atau Apache tanpa perlu curl -k.
Apa yang Anda bangun
Sertifikat TLS self-signed yang diterima oleh browser dan klien modern — subjectAltName yang benar, izin kunci yang aman, terintegrasi dengan nginx atau Apache — ditambah bagian yang sering dilewatkan oleh hampir semua panduan: membuat klien Anda percaya secara benar, alih-alih mengklik peringatan dan menuliskan curl -k secara permanen ke dalam skrip. Pada akhirnya, Anda akan memiliki CA pribadi dengan lima perintah untuk saat layanan internal Anda bertambah dari satu menjadi enam.
Pertama, tentukan pilihan Anda, karena sertifikat self-signed jauh lebih jarang menjadi alat yang tepat dibandingkan frekuensi penggunaannya. Jika layanan dapat diakses dari internet publik menggunakan nama DNS asli, berhentilah membaca dan gunakan sertifikat Let's Encrypt gratis dengan certbot pada nginx atau padanan Apache sebagai gantinya. Cara ini tidak memerlukan biaya, diperbarui secara otomatis, dan sudah dipercaya oleh semua browser di dunia. Menggunakan sertifikat self-signed pada situs publik melatih pengguna Anda untuk mengabaikan peringatan keamanan, yang merupakan kebiasaan yang lebih buruk daripada menggunakan HTTP biasa.
Self-signed adalah alat yang tepat jika tidak ada keterlibatan internet publik: panel admin yang terikat pada alamat terowongan WireGuard pada VPS Anda, server staging pada jaringan privat, lalu lintas antar-layanan di backend, perangkat home-lab, atau mengganti sertifikat placeholder yang dihasilkan Webmin untuk dirinya sendiri pada port 10000. Let's Encrypt tidak dapat menerbitkan sertifikat untuk 10.8.0.1 atau git.internal.lan — tidak ada CA publik yang akan memasukkan IP privat atau TLD buatan ke dalam sertifikat. Untuk nama-nama tersebut, Anda adalah CA-nya.
Semua langkah di bawah ini dijalankan pada sistem Ubuntu 24.04 baru yang menyertakan OpenSSL 3.0.x (gunakan openssl version untuk konfirmasi). Tidak ada bagian yang memerlukan akses internet; semuanya dapat bekerja secara air-gapped.
Mengapa perintah satu baris lama menghasilkan sertifikat yang ditolak Chrome
Perintah yang diberikan dalam setiap tutorial sebelum tahun 2017 adalah sebagai berikut:
# Do not run this — shown so you recognise it in old guides
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout selfsigned.key -out selfsigned.crtPerintah ini mengajukan serangkaian pertanyaan interaktif, memasukkan hostname Anda ke dalam kolom Common Name, dan menghasilkan sertifikat tanpa ekstensi subjectAltName. Sertifikat tersebut tidak akan valid. Chrome berhenti membaca Common Name pada versi 58 pada April 2017 — RFC 2818 telah menghapus penggunaan CN sejak tahun 2000 — dan Firefox, Safari, curl, serta Python berperilaku sama. Sertifikat mengidentifikasi server melalui ekstensi SAN atau tidak sama sekali, dan browser akan memberikan pesan berikut:
NET::ERR_CERT_COMMON_NAME_INVALID
This server could not prove that it is git.internal.lan; its security
certificate does not specify Subject Alternative Names.Mengubah pengaturan trust-store tidak akan memperbaiki kesalahan tersebut, karena sertifikat tersebut memang tidak mencantumkan identitas apa pun. Jika Anda melihat NET::ERR_CERT_COMMON_NAME_INVALID saat ini, sertifikat Anda tidak memiliki SAN (atau menggunakan SAN yang salah) dan Anda perlu membuat yang baru. Untungnya, solusinya hanya memerlukan satu perintah.
Membuat sertifikat yang diterima browser: satu perintah
OpenSSL menambahkan flag -addext pada versi 1.1.1, sehingga Anda tidak perlu lagi melakukan konfigurasi file yang rumit seperti pada panduan lama untuk menyuntikkan SAN. Pada Ubuntu 24.04:
sudo openssl req -x509 -newkey rsa:4096 -sha256 -days 730 -noenc \
-keyout /etc/ssl/private/git.internal.key \
-out /etc/ssl/certs/git.internal.crt \
-subj "/CN=git.internal.lan" \
-addext "subjectAltName=DNS:git.internal.lan,IP:10.8.0.1"Fungsi dari setiap flag:
-x509mengeluarkan sertifikat self-signed secara langsung, bukan permintaan tanda tangan (CSR).-newkey rsa:4096menghasilkan kunci baru pada langkah yang sama. RSA 4096 kompatibel dengan klien lama; jika semua klien sudah modern,-newkey ec -pkeyopt ec_paramgen_curve:P-256lebih kecil dan lebih cepat.-noencadalah penulisan OpenSSL 3.x untuk-nodesyang lama: tanpa passphrase pada kunci. Kedua penulisan tersebut berfungsi. Kunci dengan passphrase akan menyebabkan nginx tertahan menunggu input setiap kali booting, jadi gunakan opsi ini untuk kunci server.-days 730— dua tahun; penjelasan lebih lanjut mengenai angka tersebut ada di bagian masa berlaku.-subjmenjawab pertanyaan interaktif secara otomatis. CN sekarang hanya bersifat kosmetik, namun tetap atur ke nama utama; beberapa alat menampilkannya.-addext "subjectAltName=..."adalah flag utama. Cantumkan setiap nama dan setiap IP yang akan diketik klien: entriDNS:untuk hostname (wildcard sepertiDNS:*.internal.landiperbolehkan), entriIP:untuk alamat IP. Jika ada yang akan mengakseshttps://10.8.0.1, entriIP:10.8.0.1harus tersedia — SAN yang hanya berisi DNS akan menyebabkanNET::ERR_CERT_COMMON_NAME_INVALIDkembali terjadi.
Pastikan SAN sudah terpasang sebelum melakukan konfigurasi lainnya:
openssl x509 -in /etc/ssl/certs/git.internal.crt -noout -ext subjectAltNameOutput yang benar:
X509v3 Subject Alternative Name:
DNS:git.internal.lan, IP Address:10.8.0.1Jika output yang muncul adalah No extensions in certificate, sertifikat tidak memiliki SAN dan browser akan menolaknya — buat ulang sertifikat daripada melanjutkan.
Amankan kunci
Kunci privat yang dapat dibaca oleh semua pengguna di sistem bukanlah kunci privat. Pada Ubuntu, /etc/ssl/private sudah dalam mode 710 root:ssl-cert, yang mencegah akses tidak sengaja, namun atur izin file secara eksplisit:
sudo chown root:root /etc/ssl/private/git.internal.key
sudo chmod 600 /etc/ssl/private/git.internal.keynginx dan Apache membaca sertifikat sebagai root sebelum menurunkan hak akses, sehingga mode 600 pada root:root sudah memadai. Jika kunci tersebut digunakan oleh layanan yang berjalan dengan pengguna sendiri dan memuat kunci tersebut secara mandiri — seperti aplikasi Node, Gitea, atau daemon Python — ubah kepemilikan ke chown untuk pengguna layanan tersebut, dengan tetap menggunakan mode 600. Hal yang tidak boleh dilakukan: menggunakan mode 644, menyimpan salinan di repositori git, atau menyimpan salinan di /tmp.
Integrasikan ke nginx
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name git.internal.lan;
ssl_certificate /etc/ssl/certs/git.internal.crt;
ssl_certificate_key /etc/ssl/private/git.internal.key;
location / {
proxy_pass http://127.0.0.1:3000;
}
}sudo nginx -t && sudo systemctl reload nginxnginx -t harus mencetak syntax is ok dan test is successful sebelum proses reload dijalankan. Jika yang muncul adalah SSL_CTX_use_PrivateKey_file() failed ... key values mismatch, sertifikat dan key berasal dari dua proses pembuatan yang berbeda — lihat bagian failure-modes.
Hubungkan ke Apache
sudo a2enmod ssl proxy proxy_httpssl saja tidak cukup di sini: vhost di bawah ini menggunakan ProxyPass, dan tanpa mod_proxy serta mod_proxy_http, pengujian konfigurasi akan gagal dengan Invalid command 'ProxyPass', perhaps misspelled or defined by a module not included in the server configuration. Simpan vhost sebagai /etc/apache2/sites-available/git-internal.conf:
<VirtualHost *:443>
ServerName git.internal.lan
SSLEngine on
SSLCertificateFile /etc/ssl/certs/git.internal.crt
SSLCertificateKeyFile /etc/ssl/private/git.internal.key
ProxyPass / http://127.0.0.1:3000/
ProxyPassReverse / http://127.0.0.1:3000/
</VirtualHost>sudo a2ensite git-internal
sudo apache2ctl configtest && sudo systemctl reload apache2configtest seharusnya menjawab Syntax OK. Sekarang lakukan pengujian dari mesin klien:
curl -v https://git.internal.lan/dan Anda akan mendapatkan error:
curl: (60) SSL certificate problem: self-signed certificateItu bukan bug. Itu adalah cara kerja TLS: curl tidak mengenali sertifikat Anda dan menolak berkomunikasi dengan server yang tidak dapat dia autentikasi. Bagian berikutnya adalah solusi sebenarnya — dan ini bukan cara yang dilakukan oleh separuh pengguna internet saat ini.
Membuat klien mempercayainya — dan anti-pattern yang harus ditolak
Solusi yang salah, disebutkan sesuai namanya. curl -k (atau --insecure) di dalam skrip, verify=False dalam Python requests, NODE_TLS_REJECT_UNAUTHORIZED=0 di Node — semua ini tidak membuat sertifikat Anda dipercaya. Metode ini mematikan verifikasi sertifikat, yang berarti klien akan menerima server mana pun yang menyajikan sertifikat apa pun, termasuk sertifikat dari penyerang. Anda tetap menanggung beban kerja TLS tetapi kehilangan fungsi autentikasi yang menjadi tujuannya. Lebih buruk lagi, flag ini dapat menyebar: ditempelkan ke satu cron job, lalu ke skrip deployment, lalu ke kode produksi, hingga tidak ada yang ingat koneksi mana yang seharusnya bersifat sementara. Jika sebuah verify=False bertahan melampaui sesi debugging yang membuatnya, maka desain tersebut salah.
Solusi yang benar adalah menginstruksikan setiap OS klien bahwa sertifikat ini adalah root yang dipercaya. Pada klien Ubuntu dan Debian:
sudo cp git.internal.crt /usr/local/share/ca-certificates/git.internal.crt
sudo update-ca-certificatesBaris yang penting dalam output (diikuti oleh blok Running hooks in /etc/ca-certificates/update.d...):
Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.Ada dua kendala pada baris tersebut. File harus diakhiri dengan .crt — ekstensi .pem akan diabaikan secara diam-diam dan Anda akan mendapatkan 0 added tanpa pesan kesalahan. Dan kontennya harus berupa PEM — file dimulai dengan -----BEGIN CERTIFICATE-----; konversi biner DER terlebih dahulu menggunakan openssl x509 -inform der -in file.der -out file.crt. Menambahkan sertifikat self-signed itu sendiri sebagai root akan berhasil karena sertifikat self-signed adalah root bagi dirinya sendiri.
Setelah itu, curl, wget, git, apt, dan apa pun yang menggunakan OpenSSL terhadap bundle sistem akan mempercayai server tanpa memerlukan flag apa pun. Beberapa klien memiliki penyimpanan kepercayaan (trust store) sendiri dan memerlukan penanganan individual:
- Chrome/Chromium di Linux membaca database NSS, bukan penyimpanan sistem:
sudo apt install libnss3-tools, lalucertutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "git.internal" -i git.internal.crtper pengguna. - Firefox memiliki penyimpanannya sendiri: Settings → Privacy & Security → Certificates → Import, atau ubah
security.enterprise_roots.enabledmenjaditruediabout:configagar ia membaca penyimpanan sistem. - Python requests menyertakan bundle CA sendiri (certifi) dan mengabaikan penyimpanan sistem: gunakan
verify="/usr/local/share/ca-certificates/git.internal.crt"atau eksporREQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt. - Node.js: ekspor
NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/git.internal.crt.
Pada klien Windows, klik dua kali pada .crt dan instal ke Trusted Root Certification Authorities; pada macOS, tambahkan ke System keychain di Keychain Access dan tandai sebagai Always Trust.
Satu root untuk banyak layanan: CA privat skala kecil
Kepercayaan per-sertifikat menghambat skalabilitas: enam layanan dikali empat mesin klien menghasilkan dua puluh empat instalasi kepercayaan, dan setiap layanan baru menambah beban tersebut. Solusinya adalah CA privat — klien hanya mempercayai satu root, dan Anda menandatangani sertifikat setiap layanan menggunakan root tersebut.
Opsi yang mudah adalah mkcert, yang tersedia di repositori Ubuntu 24.04 dan menangani NSS stores (Chrome, Firefox) yang tidak ditangani oleh update-ca-certificates:
sudo apt install -y mkcert libnss3-tools
mkcert -install
mkcert git.internal.lan "*.internal.lan" 10.8.0.1mkcert -install membuat root dan mendaftarkannya ke dalam setiap trust store di mesin tersebut; perintah ketiga menghasilkan git.internal.lan+2.pem dan git.internal.lan+2-key.pem, yang siap digunakan pada cuplikan kode nginx atau Apache di atas. Asumsi desainnya adalah mesin pengembangan — root key tersimpan di mesin mana pun yang menjalankan -install — sehingga sangat cocok untuk laptop pengembang tetapi tidak cocok untuk armada server.
Untuk server, OpenSSL biasa dapat menjalankan seluruh proses CA dalam lima perintah:
openssl genrsa -out lab-ca.key 4096
openssl req -x509 -new -key lab-ca.key -sha256 -days 3650 \
-out lab-ca.crt -subj "/CN=Lab Internal CA" \
-addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
-addext "keyUsage=critical,keyCertSign,cRLSign"
openssl genrsa -out git.key 2048
openssl req -new -key git.key -out git.csr -subj "/CN=git.internal.lan" \
-addext "subjectAltName=DNS:git.internal.lan,IP:10.8.0.1"
openssl x509 -req -in git.csr -CA lab-ca.crt -CAkey lab-ca.key \
-CAcreateserial -days 730 -sha256 -copy_extensions copy -out git.crtKesalahan terjadi pada perintah terakhir: openssl x509 -req menghapus semua ekstensi dari CSR secara default, termasuk SAN yang telah Anda tambahkan. -copy_extensions copy (opsi OpenSSL 3.x, sehingga berfungsi di 24.04) akan menyertakan ekstensi tersebut; jika diabaikan, sertifikat yang ditandatangani tidak memiliki SAN, dan Chrome akan menampilkan NET::ERR_CERT_COMMON_NAME_INVALID kembali. Verifikasi dengan pemeriksaan openssl x509 -noout -ext subjectAltName yang sama seperti sebelumnya.
Distribusikan lab-ca.crt ke klien melalui langkah-langkah trust-store di atas — cukup satu kali per mesin. Lindungi lab-ca.key karena nilainya sangat krusial: gunakan mode 600, idealnya disimpan pada mesin yang bukan merupakan salah satu server yang ia tandatangani, karena siapa pun yang memegangnya dapat membuat sertifikat untuk nama apa pun yang akan dipercayai oleh klien Anda.
Expiry and rotation
Masa berlaku sertifikat CA publik semakin singkat — CA/Browser Forum membatasi sertifikat baru yang dipercaya secara publik maksimal 200 hari pada Maret 2026 (sebelumnya 398 hari), lalu menjadi 100 hari pada 2027, dan 47 hari pada Maret 2029 — namun aturan tersebut hanya mengikat CA yang dipercaya secara publik. CA privat Anda tidak diatur oleh aturan tersebut, dan browser tidak menerapkannya pada root yang diinstal secara manual. Satu batasan dunia nyata tetap berlaku: platform Apple menolak sertifikat server TLS apa pun yang valid lebih dari 825 hari tidak peduli siapa penerbitnya, jadi jika iPhone atau Mac akan terhubung, pastikan sertifikat leaf berdurasi dua tahun atau kurang. -days 730 memenuhi standar tersebut di mana saja; penggunaan root sepuluh tahun dengan leaf dua tahun adalah konfigurasi internal yang aman.
Sertifikat berdurasi panjang gagal dengan satu cara: secara diam-diam, secara serentak, pada tanggal yang tidak diingat oleh siapa pun. Periksa sertifikat Anda:
openssl x509 -in /etc/ssl/certs/git.internal.crt -noout -enddateMasukkan jadwal pembaruan ke dalam kalender, atau gunakan cron untuk memberi peringatan 30 hari sebelumnya — openssl x509 -checkend 2592000 -in cert.crt akan keluar dengan nilai non-zero jika masa kedaluwarsa tersisa dalam jumlah detik tersebut. Jika Anda sudah menggunakan Uptime Kuma untuk pemantauan status, monitor HTTPS miliknya akan menandai kedaluwarsa sertifikat yang mendekat secara gratis.
Rotasi dengan CA privat sangat sederhana: jalankan kembali perintah CSR-and-sign, ganti file, lalu muat ulang web server. Root tidak berubah, sehingga klien tidak akan menyadari perubahan apa pun.
Mode kegagalan, dengan string yang akan Anda lihat
NET::ERR_CERT_AUTHORITY_INVALID — kondisi yang diharapkan sebelum Anda menginstal trust, bukan cacat pada sertifikat. Jika kondisi ini tetap ada setelah Anda menginstal root: pada Linux, Chrome membaca NSS alih-alih sistem store (lihat langkah certutil); atau file yang disalin tidak diakhiri dengan .crt dan update-ca-certificates menyatakan 0 added; atau server menyajikan sertifikat yang berbeda dari yang Anda percayai — bandingkan fingerprint dengan openssl s_client -connect git.internal.lan:443 </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256.
NET::ERR_CERT_COMMON_NAME_INVALID — sertifikat tidak memiliki SAN, atau SAN tidak mencakup nama yang ada di address bar. Kasus klasik: SAN mencantumkan DNS:git.internal.lan tetapi pengguna mengakses https://10.8.0.1. Perubahan trust-store tidak dapat memperbaiki masalah ini; terbitkan ulang dengan entri yang hilang.
curl: (60) SSL certificate problem: self-signed certificate — curl tidak mempercayai sertifikat tersebut. Varian self-signed certificate in certificate chain memiliki arti yang sama untuk sertifikat yang ditandatangani oleh CA privat Anda. Solusi sementara: curl --cacert lab-ca.crt https://...; solusi permanen: trust store. Bukan -k.
unable to load certificate ... Expecting: TRUSTED CERTIFICATE (atau Expecting: CERTIFICATE REQUEST, atau no start line) — kebingungan PEM. Anda memberikan jenis file yang salah ke OpenSSL: sebuah key atau CSR saat yang dibutuhkan adalah sertifikat, atau binary DER saat yang dibutuhkan adalah PEM. head -1 filename memberi tahu Anda file apa yang sebenarnya Anda miliki — sebuah sertifikat dimulai dengan -----BEGIN CERTIFICATE-----. Untuk DER, konversi menggunakan openssl x509 -inform der -in file.der -out file.crt.
nginx: [emerg] SSL_CTX_use_PrivateKey_file(...) failed (SSL: error ... key values mismatch) — sertifikat dan key tidak berpasangan, biasanya karena perintah pembuatan dijalankan dua kali dan file tercampur. Konfirmasi dengan openssl x509 -in git.internal.crt -noout -pubkey | sha256sum versus openssl pkey -in git.internal.key -pubout | sha256sum; hash yang cocok berarti pasangan yang cocok. Jika berbeda, buat ulang keduanya secara bersamaan.
FAQ
Mengapa Chrome tetap menampilkan "Not secure" setelah saya membuat sertifikat self-signed?
Jika kesalahan adalah NET::ERR_CERT_AUTHORITY_INVALID, sertifikat sudah benar — Chrome belum memiliki alasan untuk mempercayainya. Instal sertifikat tersebut (atau root CA privat Anda) ke dalam trust store klien. Perlu diingat bahwa pada Linux, Chrome menggunakan database NSS melalui certutil, bukan trust store sistem. Jika kesalahan adalah NET::ERR_CERT_COMMON_NAME_INVALID, sertifikat tidak memiliki Subject Alternative Name yang sesuai dengan URL dan harus diterbitkan ulang dengan -addext "subjectAltName=...".
Bagaimana cara membuat curl mempercayai sertifikat self-signed tanpa flag -k?
Salin sertifikat (format PEM, ekstensi .crt) ke dalam /usr/local/share/ca-certificates/ dan jalankan sudo update-ca-certificates — output harus menunjukkan 1 added. Setelah itu, curl akan memverifikasi sertifikat tersebut seperti sertifikat publik lainnya. Untuk permintaan satu kali tanpa mengubah sistem, gunakan curl --cacert /path/to/cert.crt untuk verifikasi hanya pada file tersebut; -k menonaktifkan verifikasi sepenuhnya dan tidak boleh digunakan dalam skrip apa pun.
Berapa lama masa berlaku sertifikat self-signed?
Secara teknis selama yang Anda inginkan — batasan CA/Browser Forum (saat ini 200 hari, menjadi 47 hari pada 2029) hanya mengikat CA yang dipercaya publik, bukan trust privat. Dalam praktiknya, batasi sertifikat server maksimal 825 hari, karena perangkat Apple menolak sertifikat yang lebih lama terlepas dari penerbitnya. Root privat berdurasi sepuluh tahun dengan sertifikat leaf dua tahun (-days 730) adalah pengaturan default yang aman; pastikan untuk menjadwalkan pembaruan, karena sertifikat internal yang kedaluwarsa akan mematikan seluruh layanan secara diam-diam pada tanggal yang tidak diingat siapa pun.
Haruskah saya menggunakan sertifikat self-signed atau Let's Encrypt?
Jika layanan memiliki nama DNS publik dan dapat diakses dari internet, selalu gunakan Let's Encrypt — gratis, otomatis, dan sudah dipercaya oleh semua klien. Self-signed (atau CA privat) digunakan untuk hal yang tidak dapat diterbitkan oleh Let's Encrypt: IP privat, hostname internal seperti .lan, jaringan air-gapped, dan layanan yang sengaja disembunyikan di balik VPN. Keputusan ini bergantung pada keterjangkauan dan penamaan, bukan kekuatan keamanan — kriptografinya identik.
Mengapa sertifikat saya ditolak meskipun sudah ditambahkan ke /usr/local/share/ca-certificates?
Periksa tiga hal. File harus diakhiri dengan .crt — ekstensi .pem akan dilewati secara diam-diam dan update-ca-certificates akan melaporkan 0 added. Konten harus berupa teks PEM yang dimulai dengan -----BEGIN CERTIFICATE-----, bukan biner DER. Dan aplikasi tersebut harus benar-benar menggunakan trust store sistem — Chrome di Linux, Firefox, Python requests, Node.js, dan Java masing-masing memiliki trust store privat dan memerlukan sertifikat ditambahkan secara terpisah.