SSD Nodes Learn Hosting plans →
Panduan Matt ConnorOleh Matt Connor · Diperbarui 2026-08-07

Cara Membuat Sertifikat Self-Signed di Ubuntu 24.04

Buat sertifikat TLS self-signed yang diterima Chrome di Ubuntu 24.04 dengan SAN, satu perintah openssl, konfigurasi nginx atau Apache, dan trust tanpa curl -k.

Yang Anda buat

Sertifikat TLS yang ditandatangani sendiri dan benar-benar diterima oleh browser serta klien modern, subjectAltName yang benar, izin key yang wajar, dan integrasi dengan nginx atau Apache. Panduan ini juga mencakup bagian yang sering dilewati: membuat klien benar-benar mempercayai sertifikat tersebut, bukan mengabaikan peringatan atau meng-hard-code curl -k ke dalam skrip selamanya. Di bagian akhir, tersedia CA privat dengan lima perintah untuk kasus ketika satu layanan internal berkembang menjadi enam layanan.

Pertama, tentukan kebutuhannya. Sertifikat yang ditandatangani sendiri jauh lebih jarang menjadi pilihan yang tepat daripada penggunaannya. Jika layanan dapat diakses dari Internet publik menggunakan nama DNS yang sebenarnya, berhenti membaca dan gunakan sertifikat Let's Encrypt gratis dengan certbot pada nginx atau padanan untuk Apache sebagai gantinya. Sertifikat ini tidak berbayar, diperbarui secara otomatis, dan sudah dipercaya oleh semua browser. Sertifikat yang ditandatangani sendiri pada situs publik melatih pengguna untuk mengabaikan peringatan keamanan. Kebiasaan ini lebih buruk daripada menggunakan HTTP biasa.

Sertifikat yang ditandatangani sendiri tepat digunakan jika tidak ada akses ke Internet publik: panel admin yang terikat ke alamat tunnel WireGuard pada VPS Anda, server staging pada jaringan privat, trafik antarlayanan antarbackend, perangkat home lab, atau penggantian sertifikat placeholder yang dibuat sendiri oleh Webmin pada port 10000. Let's Encrypt juga 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 berperan sebagai CA.

Semua langkah berikut dijalankan pada sistem Ubuntu 24.04 yang baru diinstal. Sistem ini menyertakan OpenSSL 3.0.x (openssl version untuk mengonfirmasinya). Tidak ada langkah yang memerlukan akses Internet. Semuanya dapat dijalankan dalam lingkungan yang sepenuhnya terisolasi dari jaringan.

Mengapa one-liner lama menghasilkan sertifikat yang ditolak Chrome

Perintah yang diberikan hampir semua tutorial sebelum 2017 terlihat seperti ini:

# 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.crt

Perintah tersebut mengajukan serangkaian pertanyaan interaktif, menempatkan hostname Anda pada field Common Name, dan menghasilkan sertifikat tanpa ekstensi subjectAltName. Sertifikat itu langsung ditolak. Chrome berhenti membaca Common Name pada versi 58, pada April 2017. RFC 2818 bahkan telah menghentikan penggunaan pencocokan CN sejak tahun 2000. Firefox, Safari, curl, dan Python berperilaku sama. Sertifikat mengidentifikasi servernya melalui ekstensi SAN, atau tidak mengidentifikasinya sama sekali. Browser memberi tahu Anda dengan kata-kata 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 trust store tidak akan memperbaiki error tersebut karena sertifikat itu memang tidak mencantumkan nama apa pun. Jika saat ini Anda melihat NET::ERR_CERT_COMMON_NAME_INVALID, sertifikat Anda tidak memiliki SAN (atau SAN-nya salah), dan Anda perlu membuat sertifikat baru. Untungnya, perbaikannya hanya memerlukan satu perintah.

Buat sertifikat yang diterima browser: satu perintah

OpenSSL menambahkan flag -addext pada versi 1.1.1. Artinya, Anda tidak lagi memerlukan konfigurasi file yang rumit seperti pada panduan lama untuk menyisipkan 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 setiap flag:

  • -x509 membuat sertifikat yang ditandatangani sendiri secara langsung, bukan permintaan penandatanganan.
  • -newkey rsa:4096 membuat key baru dalam langkah yang sama. RSA 4096 kompatibel dengan client lama. Jika semua koneksi menggunakan client modern, -newkey ec -pkeyopt ec_paramgen_curve:P-256 berukuran lebih kecil dan lebih cepat.
  • -noenc adalah penulisan OpenSSL 3.x untuk -nodes versi lama: key tidak menggunakan passphrase. Kedua penulisan tersebut dapat digunakan. Jika key menggunakan passphrase, nginx akan berhenti dan menunggu input setiap kali boot. Karena itu, gunakan opsi ini untuk key server.
  • -days 730 menetapkan masa berlaku selama dua tahun. Angka tersebut dijelaskan lebih lanjut pada bagian masa berlaku.
  • -subj memberikan jawaban atas pertanyaan interaktif secara langsung. CN kini hanya bersifat kosmetik, tetapi tetap tetapkan ke nama utama. Beberapa tool masih menampilkannya.
  • -addext "subjectAltName=..." adalah flag utama. Cantumkan setiap nama dan setiap IP yang akan diketik client: entri DNS: untuk hostname (wildcard seperti DNS:*.internal.lan dapat digunakan), serta entri IP: untuk alamat IP. Jika ada yang akan mengakses https://10.8.0.1, entri IP:10.8.0.1 harus dicantumkan. SAN yang hanya berisi DNS akan menimbulkan NET::ERR_CERT_COMMON_NAME_INVALID lagi.

Pastikan SAN benar-benar tercantum sebelum menghubungkan konfigurasi apa pun:

openssl x509 -in /etc/ssl/certs/git.internal.crt -noout -ext subjectAltName

Output yang benar:

X509v3 Subject Alternative Name:
    DNS:git.internal.lan, IP Address:10.8.0.1

Jika perintah tersebut justru menampilkan No extensions in certificate, berarti sertifikat tidak memiliki SAN dan akan ditolak oleh browser. Buat ulang sertifikat tersebut, jangan lanjutkan ke langkah berikutnya.

Kunci privat

Kunci privat yang dapat dibaca oleh setiap pengguna di server bukan lagi kunci privat. Di Ubuntu, /etc/ssl/private sudah 710 root:ssl-cert, sehingga pengguna biasa tidak dapat membacanya, tetapi atur izin file secara eksplisit:

sudo chown root:root /etc/ssl/private/git.internal.key
sudo chmod 600 /etc/ssl/private/git.internal.key

nginx dan Apache sama-sama membaca sertifikat sebagai root sebelum menurunkan hak akses, sehingga mode 600 pada root:root dapat digunakan. Jika kunci digunakan oleh service yang berjalan sebagai pengguna sendiri dan memuat kunci tersebut secara langsung, misalnya aplikasi Node, Gitea, atau daemon Python, chown kunci itu kepada pengguna service tersebut, tetap dengan mode 600. Jangan pernah menggunakan mode 644, menyimpan salinan di repositori git, atau menyimpan salinan di /tmp.

Hubungkan 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 nginx

nginx -t harus menampilkan syntax is ok dan test is successful sebelum reload melakukan apa pun. Jika yang ditampilkan adalah SSL_CTX_use_PrivateKey_file() failed ... key values mismatch, sertifikat dan kunci berasal dari dua proses pembuatan yang berbeda. Lihat bagian mode kegagalan.

Konfigurasikan pada Apache

sudo a2enmod ssl proxy proxy_http

ssl saja tidak cukup di sini: vhost di bawah menggunakan ProxyPass, dan tanpa mod_proxy serta mod_proxy_http, pengujian konfigurasi 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 apache2

configtest seharusnya merespons pada 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 certificate

Itu bukan bug. Itu adalah TLS yang bekerja: curl belum mengenal sertifikat Anda dan menolak berkomunikasi dengan server yang tidak dapat diautentikasi. Bagian berikutnya berisi perbaikan yang sebenarnya, dan bukan cara yang dilakukan banyak orang di Internet saat ini.

Buat klien mempercayainya, serta pola antipraktik yang harus dihindari

Mulai dari perbaikan yang salah, dengan menyebutkan apa adanya. curl -k (atau --insecure) yang ditanam dalam skrip, verify=False dalam Python requests, dan NODE_TLS_REJECT_UNAUTHORIZED=0 dalam Node tidak membuat sertifikat Anda tepercaya. Semua itu menonaktifkan verifikasi sertifikat, sehingga klien akan berkomunikasi dengan server apa pun yang menyajikan sertifikat apa pun, termasuk sertifikat yang dipasang penyerang di jalur koneksi. Anda tetap menanggung overhead TLS, tetapi kehilangan autentikasi yang menjadi tujuan TLS. Lebih buruk lagi, flag tersebut menyebar: ditempelkan ke satu cron job, lalu ke skrip deployment, kemudian ke kode produksi, sampai tidak ada yang ingat koneksi mana yang seharusnya hanya bersifat sementara. Jika sebuah verify=False masih ada setelah sesi debugging yang memicunya selesai, desain tersebut salah.

Perbaikan yang benar adalah mengajarkan kepada setiap OS klien bahwa sertifikat ini merupakan root tepercaya. Pada klien Ubuntu dan Debian:

sudo cp git.internal.crt /usr/local/share/ca-certificates/git.internal.crt
sudo update-ca-certificates

Baris penting dalam output (setelahnya terdapat blok Running hooks in /etc/ca-certificates/update.d...):

Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.

Ada dua hal yang sering terlewat pada baris tersebut. Nama file harus diakhiri dengan .crt. Ekstensi .pem akan diabaikan tanpa pesan, dan Anda akan mendapatkan 0 added. Isinya juga harus berupa PEM; file diawali dengan -----BEGIN CERTIFICATE-----. Konversikan terlebih dahulu biner DER menggunakan openssl x509 -inform der -in file.der -out file.crt. Menambahkan sertifikat yang ditandatangani sendiri sebagai root dapat dilakukan karena sertifikat yang ditandatangani sendiri memang merupakan root-nya sendiri.

Setelah itu, curl, wget, git, apt, dan apa pun yang menggunakan OpenSSL terhadap bundle sistem akan mempercayai server tanpa flag apa pun. Beberapa klien memiliki trust store sendiri dan memerlukan penanganan khusus:

  • Chrome/Chromium di Linux membaca database NSS, bukan penyimpanan sistem: sudo apt install libnss3-tools, lalu certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "git.internal" -i git.internal.crt untuk setiap pengguna.
  • Firefox memiliki penyimpanan sendiri: Settings → Privacy & Security → Certificates → Import, atau ubah security.enterprise_roots.enabled menjadi true dalam about:config agar Firefox membaca penyimpanan sistem.
  • Python requests menyertakan CA bundle sendiri (certifi) dan mengabaikan penyimpanan sistem: teruskan verify="/usr/local/share/ca-certificates/git.internal.crt" atau ekspor REQUESTS_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 .crt dan instal ke Trusted Root Certification Authorities. Pada macOS, tambahkan sertifikat tersebut ke System keychain melalui Keychain Access, lalu tandai sebagai Always Trust.

Satu root untuk banyak layanan: private CA sederhana

Kepercayaan per sertifikat langsung tidak lagi praktis: enam layanan dikalikan empat mesin klien berarti dua puluh empat instalasi trust, dan setiap layanan baru menambah jumlah tersebut. Solusinya adalah private CA. Klien cukup mempercayai satu root, lalu Anda menandatangani sertifikat setiap layanan dengan root tersebut.

Pilihan yang praktis adalah mkcert, yang tersedia di repositori Ubuntu 24.04 dan menangani penyimpanan NSS (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.1

mkcert -install membuat root dan mendaftarkannya di setiap trust store pada mesin tersebut. Perintah ketiga menghasilkan git.internal.lan+2.pem dan git.internal.lan+2-key.pem, yang siap ditempatkan ke dalam potongan konfigurasi nginx atau Apache di atas. Desainnya mengasumsikan mesin pengembangan. Root key berada di mesin yang menjalankan -install. Karena itu, solusi ini sangat sesuai untuk laptop pengembangan, tetapi tidak sesuai untuk fleet server.

Untuk server, OpenSSL standar dapat menangani seluruh 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.crt

Masalahnya terdapat pada perintah terakhir: secara default, openssl x509 -req menghapus semua ekstensi dari CSR, termasuk SAN yang telah Anda tambahkan dengan cermat. -copy_extensions copy (opsi OpenSSL 3.x, sehingga berfungsi pada 24.04) mempertahankan ekstensi tersebut. Jika opsi ini dihilangkan, sertifikat yang ditandatangani tidak memiliki SAN, dan Chrome kembali menampilkan NET::ERR_CERT_COMMON_NAME_INVALID. Verifikasi menggunakan pemeriksaan openssl x509 -noout -ext subjectAltName yang sama seperti sebelumnya.

Distribusikan lab-ca.crt ke klien melalui langkah trust-store di atas, satu kali untuk setiap mesin. Lindungi lab-ca.key seperti aset yang sangat penting: gunakan mode 600, dan idealnya simpan pada mesin yang bukan salah satu server yang ditandatanganinya. Siapa pun yang memegangnya dapat membuat sertifikat untuk nama apa pun yang dipercaya oleh klien Anda.

Masa berlaku dan rotasi

Masa berlaku sertifikat CA publik terus dipersingkat. CA/Browser Forum membatasi sertifikat tepercaya publik yang baru diterbitkan hingga 200 hari pada Maret 2026, turun dari 398 hari, lalu menjadi 100 hari pada 2027 dan 47 hari pada Maret 2029. Namun, aturan tersebut hanya berlaku bagi CA tepercaya publik. CA privat Anda tidak diatur oleh aturan tersebut, dan browser tidak memberlakukannya terhadap root yang diinstal secara manual. Ada satu batasan nyata yang berlaku: platform Apple menolak sertifikat server TLS dengan masa berlaku lebih dari 825 hari, siapa pun penerbitnya. Jadi, jika iPhone atau Mac akan terhubung, gunakan sertifikat leaf dengan masa berlaku paling lama dua tahun. -days 730 memenuhi batas tersebut di semua platform. Root dengan masa berlaku sepuluh tahun dan sertifikat leaf dengan masa berlaku dua tahun merupakan konfigurasi internal yang praktis.

Sertifikat dengan masa berlaku panjang hanya gagal dengan satu cara: kedaluwarsa secara diam-diam dan serentak pada tanggal yang tidak diingat siapa pun. Periksa sertifikat yang ada:

openssl x509 -in /etc/ssl/certs/git.internal.crt -noout -enddate

Masukkan pembaruan ke kalender yang sebenarnya, atau gunakan cron untuk mengingatkan Anda 30 hari sebelumnya. openssl x509 -checkend 2592000 -in cert.crt keluar dengan kode nonzero setelah masa berlaku tersisa dalam jumlah detik tersebut. Jika Anda sudah menjalankan Uptime Kuma untuk pemantauan status, monitor HTTPS-nya akan menandai sertifikat yang masa berlakunya akan segera berakhir tanpa biaya tambahan.

Rotasi dengan CA privat cukup sederhana: jalankan kembali perintah CSR dan penandatanganan, ganti file, lalu reload web server. Root tidak berubah, sehingga tidak ada klien yang menyadarinya.

Mode kegagalan, beserta string yang akan Anda lihat

NET::ERR_CERT_AUTHORITY_INVALID, ini adalah kondisi yang diharapkan sebelum Anda memasang trust, bukan cacat pada sertifikat. Jika tetap muncul setelah Anda memasang root: di Linux, Chrome membaca NSS, bukan trust store sistem (lihat langkah certutil); atau file yang disalin tidak diakhiri dengan .crt dan update-ca-certificates menghasilkan 0 added; atau server menyajikan sertifikat yang berbeda dari sertifikat 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 pada bilah alamat. Kasus yang umum: SAN mencantumkan DNS:git.internal.lan, tetapi pengguna membuka https://10.8.0.1. Perubahan pada trust store tidak dapat memperbaiki masalah ini; terbitkan ulang sertifikat dengan entri yang hilang.

curl: (60) SSL certificate problem: self-signed certificate, curl tidak memercayai sertifikat tersebut. Varian self-signed certificate in certificate chain memiliki arti yang sama untuk sertifikat yang ditandatangani oleh CA privat Anda. Perbaikan sekali pakai: curl --cacert lab-ca.crt https://...; perbaikan permanen: trust store. Jangan gunakan -k.

unable to load certificate ... Expecting: TRUSTED CERTIFICATE (atau Expecting: CERTIFICATE REQUEST, atau no start line), terjadi kebingungan PEM. Anda memberikan jenis file yang salah kepada OpenSSL: key atau CSR saat OpenSSL mengharapkan sertifikat, atau file biner DER saat OpenSSL mengharapkan PEM. head -1 filename menunjukkan jenis file yang sebenarnya Anda miliki. Sertifikat diawali dengan -----BEGIN CERTIFICATE-----. Untuk DER, konversikan dengan 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 cocok, biasanya karena perintah pembuatan dijalankan dua kali sehingga file tertukar. Konfirmasikan dengan membandingkan openssl x509 -in git.internal.crt -noout -pubkey | sha256sum dan openssl pkey -in git.internal.key -pubout | sha256sum; hash yang sama berarti keduanya merupakan pasangan yang cocok. Jika berbeda, buat ulang keduanya secara bersamaan.

FAQ

Mengapa Chrome masih menampilkan "Not secure" setelah saya membuat sertifikat yang ditandatangani sendiri?

Jika error-nya adalah NET::ERR_CERT_AUTHORITY_INVALID, sertifikat tersebut valid. Chrome hanya belum memiliki alasan untuk mempercayainya. Instal sertifikat tersebut, atau root CA privat Anda, ke trust store klien. Ingat bahwa di Linux, Chrome menggunakan database NSS melalui certutil, bukan system store. Jika error-nya adalah NET::ERR_CERT_COMMON_NAME_INVALID, sertifikat tidak memiliki Subject Alternative Name yang cocok dengan URL dan harus diterbitkan ulang menggunakan -addext "subjectAltName=...".

Bagaimana cara membuat curl mempercayai sertifikat yang ditandatangani sendiri tanpa menggunakan -k?

Salin sertifikat dalam format PEM, dengan ekstensi .crt, ke /usr/local/share/ca-certificates/, lalu jalankan sudo update-ca-certificates. Output harus menampilkan 1 added. Setelah itu, curl memverifikasinya seperti sertifikat publik lainnya. Untuk satu permintaan tanpa mengubah sistem, curl --cacert /path/to/cert.crt memverifikasi hanya berdasarkan file tersebut. -k menonaktifkan verifikasi sepenuhnya dan tidak boleh digunakan dalam skrip apa pun.

Berapa lama sertifikat yang ditandatangani sendiri dapat berlaku?

Secara teknis, selama yang Anda inginkan. Batas CA/Browser Forum, yaitu 200 hari saat ini dan 47 hari pada 2029, berlaku untuk CA yang dipercaya secara publik, bukan untuk trust privat. Dalam praktiknya, batasi masa berlaku sertifikat server hingga 825 hari karena perangkat Apple menolak sertifikat yang lebih lama, apa pun penerbitnya. Root privat dengan masa berlaku sepuluh tahun dan sertifikat leaf dua tahun (-days 730) merupakan pilihan bawaan yang wajar. Jadwalkan pembaruannya karena sertifikat internal yang kedaluwarsa dapat menghentikan seluruh layanan tanpa pesan yang terlihat, pada tanggal yang tidak diingat siapa pun.

Apakah sebaiknya saya menggunakan sertifikat yang ditandatangani sendiri atau Let's Encrypt?

Jika layanan memiliki nama DNS publik dan dapat dijangkau dari Internet, selalu gunakan Let's Encrypt. Layanan ini gratis, otomatis, dan sudah dipercaya oleh semua klien. Sertifikat yang ditandatangani sendiri, atau CA privat, digunakan untuk hal-hal yang tidak dapat diterbitkan oleh Let's Encrypt: IP privat, hostname internal seperti .lan, jaringan yang terisolasi dari Internet, dan layanan yang sengaja disembunyikan di balik VPN. Keputusannya bergantung pada keterjangkauan dan penamaan, bukan kekuatan keamanan. Kriptografinya sama.

Mengapa sertifikat saya ditolak meskipun sudah ditambahkan ke /usr/local/share/ca-certificates?

Periksa tiga hal. Nama file harus diakhiri dengan .crt. File dengan ekstensi .pem akan dilewati secara diam-diam, dan update-ca-certificates akan melaporkan 0 added. Isinya harus berupa teks PEM yang diawali -----BEGIN CERTIFICATE-----, bukan biner DER. Aplikasi juga harus benar-benar menggunakan system store. Chrome di Linux, Firefox, Python requests, Node.js, dan Java masing-masing memiliki trust store sendiri, sehingga sertifikat harus ditambahkan secara terpisah.