Self-Signed TLS Certificate sa Ubuntu 24.04
Gumawa ng self-signed TLS cert na tinatanggap ng Chrome sa Ubuntu 24.04, may SAN, nginx o Apache setup, at tamang trust nang walang curl -k.
Ang ginagawa mo
Isang self-signed TLS certificate na talagang tinatanggap ng mga modern browser at client, tamang subjectAltName, wastong permissions ng key, naka-configure para sa nginx o Apache, at ang bahaging halos hindi tinatalakay ng mga guide: ang tamang pagtiwala rito ng mga client, sa halip na basta i-click ang mga warning at permanenteng i-hard-code ang curl -k sa mga script. Sa huli, may five-command private CA para sa sitwasyong ang isang internal service ay naging anim.
Una, ang desisyon, dahil mas madalang na tamang tool ang self-signed certificate kaysa sa paggamit dito. Kung naaabot ang service mula sa public internet gamit ang tunay na DNS name, huwag nang magpatuloy at kumuha ng libreng Let's Encrypt certificate gamit ang certbot sa nginx o katumbas nito para sa Apache. Wala itong bayad, awtomatikong nagre-renew, at pinagkakatiwalaan na ito ng bawat browser. Kapag gumamit ng self-signed certificate sa isang public site, nasasanay ang mga user na i-click ang mga security warning, na mas masamang gawi kaysa sa plain HTTP.
Self-signed ang tamang tool kapag walang kinalaman ang public internet: isang admin panel na naka-bind sa address ng WireGuard tunnel sa iyong VPS, isang staging box sa private network, service-to-service traffic sa pagitan ng mga backend, isang home-lab appliance, o pagpapalit sa placeholder certificate na ginagawa ng Webmin para sa sarili nito sa port 10000. Hindi rin makakapag-issue ang Let's Encrypt para sa 10.8.0.1 o git.internal.lan; walang public CA na maglalagay ng private IP o gawa-gawang TLD sa isang certificate. Para sa mga pangalang iyon, ikaw ang CA.
Ang lahat ng nasa ibaba ay tumatakbo sa isang bagong Ubuntu 24.04 box, na may kasamang OpenSSL 3.0.x (openssl version para makumpirma). Walang nangangailangan ng internet access dito; gumagana ang lahat kahit air-gapped.
Bakit gumagawa ang lumang one-liner ng mga certificate na nire-reject ng Chrome
Ganito ang command na ibinibigay ng bawat tutorial bago mag-2017:
# 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.crtNagtatanong ito nang interactive, inilalagay ang hostname mo sa Common Name field, at gumagawa ng certificate na walang subjectAltName extension. Hindi magagamit ang certificate na iyon. Tumigil ang Chrome sa pagbasa ng Common Name sa version 58 noong Abril 2017. Na-deprecate na ng RFC 2818 ang CN matching noong 2000. Pareho rin ang behavior ng Firefox, Safari, curl, at Python. Tinutukoy ng certificate ang server nito sa pamamagitan ng SAN extension, o hindi nito ito matutukoy. Sinasabi ito ng browser sa eksaktong mga salitang ito:
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.Walang silbi ang anumang pagbabago sa trust store para ayusin ang error na iyon, dahil wala talagang server name na tinutukoy ang certificate. Kung nakikita mo ngayon ang NET::ERR_CERT_COMMON_NAME_INVALID, walang SAN ang certificate mo, o mali ang SAN nito, kaya kailangan mong gumawa ng bago. Mabuti na lang, isang command lang ang kailangan para ayusin ito.
Mag-mint ng certificate na tatanggapin ng mga browser: isang command
Nagdagdag ang OpenSSL ng -addext flag sa 1.1.1. Dahil dito, hindi mo na kailangang gamitin ang config-file gymnastics ng mga lumang guide para mag-inject ng SAN. Sa 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"Narito ang ginagawa ng bawat flag:
- Direktang naglalabas ang
-x509ng self-signed certificate sa halip na signing request. - Gumagawa ang
-newkey rsa:4096ng bagong key sa parehong hakbang. Hindi magiging problema sa mga lumang client ang RSA 4096; kung modern lahat ng kumokonekta, mas maliit at mas mabilis ang-newkey ec -pkeyopt ec_paramgen_curve:P-256. - Ang
-noencang spelling sa OpenSSL 3.x ng dating-nodes: walang passphrase ang key. Parehong gumagana ang dalawang spelling. Kapag may passphrase ang key, maghihintay ang nginx ng input sa bawat boot. Kaya para sa server key, ito ang dapat gamitin. - Dalawang taon ang
-days 730; tatalakayin pa ang numerong iyon sa expiry section. - Sinasagot ng
-subjang interactive questions inline. Cosmetic na lamang ngayon ang CN, pero itakda pa rin ito sa primary name; ipinapakita pa rin ito ng ilang tool. - Ang
-addext "subjectAltName=..."ang pangunahing flag. Ilagay ang bawat pangalan at bawat IP na ita-type ng mga client:DNS:entries para sa mga hostname (puwedeng gumamit ng wildcard gaya ngDNS:*.internal.lan), atIP:entries para sa mga address. Kung may magba-browse sahttps://10.8.0.1, dapat naroon angIP:10.8.0.1entry. Kung DNS-only SAN ang gagamitin, makukuha nila muli angNET::ERR_CERT_COMMON_NAME_INVALID.
Kumpirmahing aktuwal na naisama ang SAN bago mag-configure ng iba:
openssl x509 -in /etc/ssl/certs/git.internal.crt -noout -ext subjectAltNameTamang output:
X509v3 Subject Alternative Name:
DNS:git.internal.lan, IP Address:10.8.0.1Kung No extensions in certificate ang lumabas sa halip, walang SAN ang certificate at tatanggihan ito ng mga browser. Mag-regenerate ng certificate sa halip na ituloy ang configuration.
I-lock down ang key
Ang private key na nababasa ng bawat user sa server ay hindi na maituturing na private key. Sa Ubuntu, naka-/etc/ssl/private na ang 710 root:ssl-cert, kaya hindi ito basta-basta makikita ng ibang user. Gayunman, itakda nang hayagan ang permissions ng file:
sudo chown root:root /etc/ssl/private/git.internal.key
sudo chmod 600 /etc/ssl/private/git.internal.keyParehong binabasa ng Nginx at Apache ang certificates bilang root bago ibaba ang kanilang privileges, kaya ang mode 600 na root:root ay gumagana para sa mga ito. Kung ang key ay para sa service na tumatakbo gamit ang sarili nitong user at direktang naglo-load ng key, gaya ng Node app, Gitea, o Python daemon, chown ito sa user ng service na iyon, at panatilihing mode 600. Huwag na huwag gagamit ng mode 644, maglalagay ng kopya sa git repository, o maglalagay ng kopya sa /tmp.
Iugnay ito sa 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 nginxDapat i-print ng nginx -t ang syntax is ok at test is successful bago gumawa ng anuman ang reload. Kung sa halip ay i-print nito ang SSL_CTX_use_PrivateKey_file() failed ... key values mismatch, mula sa magkaibang generation run ang certificate at key; tingnan ang seksyong failure modes.
I-configure ito sa Apache
sudo a2enmod ssl proxy proxy_httpHindi sapat dito ang ssl: gumagamit ang vhost sa ibaba ng ProxyPass, at kung wala ang mod_proxy at mod_proxy_http, mabibigo ang config test dahil sa Invalid command 'ProxyPass', perhaps misspelled or defined by a module not included in the server configuration. I-save ang vhost bilang /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 apache2Dapat tumugon ang configtest sa Syntax OK. Ngayon, mag-test mula sa isang client machine:
curl -v https://git.internal.lan/at makakakuha ka ng error:
curl: (60) SSL certificate problem: self-signed certificateHindi ito bug. Gumagana ang TLS: hindi pa nakikilala ng curl ang certificate mo, kaya tumatanggi itong makipag-ugnayan sa server na hindi nito ma-authenticate. Ang susunod na seksyon ang aktuwal na solusyon, at hindi ito ang karaniwang ginagawa ng maraming tao sa internet sa mismong sandaling ito.
Ipa-trust ito ng mga client, at ang mga anti-pattern na dapat tanggihan
Unahin ang mga maling fix, ayon sa tamang tawag sa mga ito. Ang curl -k (o --insecure) na inilagay sa script, verify=False sa Python requests, at NODE_TLS_REJECT_UNAUTHORIZED=0 sa Node ay hindi nagiging trusted ang certificate. Ino-off ng mga ito ang certificate verification, kaya makikipag-ugnayan ang client sa anumang server na nagpe-present ng anumang certificate, kabilang ang certificate na inilagay ng attacker sa path. Napapanatili mo ang overhead ng TLS ngunit nawawala ang authentication na siyang dahilan kung bakit ito ginagamit. Mas masama, kumakalat ang mga flag na ito: naikakabit sa isang cron job, pagkatapos sa deploy script, at kalaunan sa production code, hanggang sa wala nang nakakaalala kung aling mga connection ang pansamantala lamang dapat. Kung mananatili ang isang verify=False pagkalipas ng debugging session na pinagmulan nito, mali ang design.
Ang tamang fix ay turuan ang OS ng bawat client na trusted root ang certificate na ito. Sa mga Ubuntu at Debian client:
sudo cp git.internal.crt /usr/local/share/ca-certificates/git.internal.crt
sudo update-ca-certificatesAng mahalagang linya sa output (susundan ito ng Running hooks in /etc/ca-certificates/update.d... block):
Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.May dalawang problemang nakatago sa mga linyang iyon. Kailangang magtapos ang file sa .crt. Tahimik na ini-ignore ang .pem extension, at makakakuha ka ng 0 added nang walang error message. Dapat ding PEM ang laman ng file; nagsisimula ang file sa -----BEGIN CERTIFICATE-----. I-convert muna ang DER binary gamit ang openssl x509 -inform der -in file.der -out file.crt. Gumagana ang pagdagdag mismo sa self-signed certificate bilang root dahil ang self-signed certificate ay sarili nitong root.
Pagkatapos nito, pinagkakatiwalaan na ng curl, wget, git, apt, at ng iba pang gumagamit ng OpenSSL laban sa system bundle ang server nang walang anumang flag. May ilang client na may sarili nilang trust store at nangangailangan ng indibidwal na configuration:
- Chrome/Chromium sa Linux ay nagbabasa ng NSS database, hindi ng system store:
sudo apt install libnss3-tools, pagkatapos aycertutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "git.internal" -i git.internal.crtpara sa bawat user. - Firefox ay may sarili nitong store: Settings → Privacy & Security → Certificates → Import, o baguhin ang
security.enterprise_roots.enabledsatruesaabout:configupang gamitin nito ang system store. - Python requests ay may sariling CA bundle (certifi) at ini-ignore ang system store: ipasa ang
verify="/usr/local/share/ca-certificates/git.internal.crt"o i-export angREQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt. - Node.js: i-export ang
NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/git.internal.crt.
Sa mga Windows client, i-double-click ang .crt at i-install ito sa Trusted Root Certification Authorities. Sa macOS, idagdag ito sa System keychain gamit ang Keychain Access at markahan bilang Always Trust.
Isang root para sa maraming serbisyo: maliit na private CA
Hindi agad nagso-scale ang trust na hiwalay para sa bawat certificate: anim na serbisyo na may tig-apat na client machine ay katumbas ng dalawampu't apat na trust install, at bawat bagong serbisyo ay nagdaragdag pa nito. Ang solusyon ay private CA. Isang root lang ang pinagkakatiwalaan ng mga client, at gamit ito, sina-sign mo ang certificate ng bawat serbisyo.
Ang mas madaling opsyon ay mkcert, na nasa Ubuntu 24.04 repos at nagha-handle ng NSS stores (Chrome, Firefox) na hindi nasusuportahan ng update-ca-certificates:
sudo apt install -y mkcert libnss3-tools
mkcert -install
mkcert git.internal.lan "*.internal.lan" 10.8.0.1Gumagawa ang mkcert -install ng root at nirerehistro ito sa bawat trust store sa machine na iyon. Ang ikatlong command ay naglalabas ng git.internal.lan+2.pem at git.internal.lan+2-key.pem, na handa nang ilagay sa mga nginx o Apache snippet sa itaas. Ipinapalagay ng disenyo nito na development machine ang gamit. Nananatili ang root key sa anumang box na nagpatakbo ng -install, kaya perpekto ito para sa dev laptop ngunit hindi angkop sa server fleet.
Para sa mga server, kayang gawin ng plain OpenSSL ang buong CA sa limang command:
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.crtAng dapat bantayan ay ang huling command: bilang default, inaalis ng openssl x509 -req ang lahat ng extension mula sa CSR, kabilang ang SAN na maingat mong idinagdag. Inililipat ng -copy_extensions copy ang mga ito sa signed certificate. OpenSSL 3.x option ito, kaya gumagana ito sa 24.04. Kapag inalis mo ito, walang SAN ang signed certificate at ipapakita ulit sa iyo ng Chrome ang NET::ERR_CERT_COMMON_NAME_INVALID. I-verify ito gamit ang parehong openssl x509 -noout -ext subjectAltName check tulad ng dati.
I-distribute ang lab-ca.crt sa mga client gamit ang mga trust-store step sa itaas, isang beses lang sa bawat machine. Protektahan ang lab-ca.key na parang napakahalagang secret: itakda ang mode na 600 at, kung maaari, itago ito sa isang box na hindi kabilang sa mga server na sina-sign nito. Kapag hawak ito ng isang tao, makakagawa siya ng certificate para sa anumang pangalan na pagkakatiwalaan ng iyong mga client.
Pag-expire at rotation
Pababa nang pababa ang validity period ng mga certificate mula sa public CA. Nilimitahan ng CA/Browser Forum ang mga bagong publicly trusted certificate sa 200 days noong March 2026, mula sa dating 398 days. Babalik ito sa 100 days sa 2027 at 47 days pagsapit ng March 2029. Gayunman, ang mga panuntunang ito ay para lamang sa publicly trusted CA. Hindi saklaw ng mga ito ang private CA mo, at hindi ipinapatupad ng mga browser ang mga ito sa mga manually installed root. May isang aktuwal na limitasyon: tinatanggihan ng Apple platforms ang TLS server certificate na valid nang higit sa 825 days, kahit sino pa ang nag-issue nito. Kaya kung kokonekta ang mga iPhone o Mac, panatilihing dalawang taon o mas maikli ang validity ng leaf certificate. Pasado sa limitasyong iyon ang -days 730 sa lahat ng environment. Komportable para sa internal setup ang ten-year root na may two-year leaves.
Isang paraan lang pumapalya ang mga long-lived certificate: tahimik at sabay-sabay, sa petsang walang nakakaalala kung bakit iyon ang napili. Suriin kung ano ang mayroon ka:
openssl x509 -in /etc/ssl/certs/git.internal.crt -noout -enddateIlagay ang renewal sa aktuwal na calendar, o ipa-remind ito sa cron 30 days bago ang expiry. Mag-e-exit ang openssl x509 -checkend 2592000 -in cert.crt nang non-zero kapag ang nalalabing validity ay pasok na sa itinakdang bilang ng seconds. Kung gumagamit ka na ng Uptime Kuma para sa status monitoring, awtomatikong nagfa-flag ang HTTPS monitors nito kapag papalapit na ang certificate expiry, nang walang dagdag na bayad.
Madali lang ang rotation gamit ang private CA: patakbuhin muli ang mga CSR-and-sign command, palitan ang mga file, at i-reload ang web server. Hindi nagbago ang root, kaya walang mapapansin ang mga client.
Mga failure mode at mga string na makikita mo
NET::ERR_CERT_AUTHORITY_INVALID, ito ang inaasahang estado bago mo i-install ang trust, hindi depekto sa certificate. Kung nagpapatuloy ito pagkatapos mong i-install ang root: sa Linux, NSS ang binabasa ng Chrome sa halip na system store (tingnan ang hakbang na certutil); o hindi nagtatapos sa .crt ang nakopyang file at sinabi ng update-ca-certificates na 0 added; o ibang certificate ang ipinapakita ng server kaysa sa pinagkatiwalaan mo—ikumpara ang fingerprints gamit ang openssl s_client -connect git.internal.lan:443 </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256.
NET::ERR_CERT_COMMON_NAME_INVALID, walang SAN ang certificate, o hindi saklaw ng SAN ang pangalan sa address bar. Karaniwang kaso ito: nakalista sa SAN ang DNS:git.internal.lan pero nag-browse ang user sa https://10.8.0.1. Hindi ito maaayos ng mga pagbabago sa trust store; mag-issue ulit gamit ang nawawalang entry.
curl: (60) SSL certificate problem: self-signed certificate, hindi pinagkakatiwalaan ng curl ang certificate. Ganito rin ang ibig sabihin ng variant na self-signed certificate in certificate chain para sa certificate na nilagdaan ng sarili mong private CA. Pansamantalang fix: curl --cacert lab-ca.crt https://...; permanenteng fix: ang trust store. Hindi -k.
unable to load certificate ... Expecting: TRUSTED CERTIFICATE (o Expecting: CERTIFICATE REQUEST, o no start line), may kalituhan sa PEM. Maling uri ng file ang ibinigay mo sa OpenSSL: key o CSR kahit certificate ang hinihingi nito, o DER binary kahit PEM ang hinihingi nito. Ipinapakita ng head -1 filename kung ano talaga ang mayroon ka; nagsisimula ang certificate sa -----BEGIN CERTIFICATE-----. Para sa DER, mag-convert gamit ang openssl x509 -inform der -in file.der -out file.crt.
nginx: [emerg] SSL_CTX_use_PrivateKey_file(...) failed (SSL: error ... key values mismatch), hindi magkapareha ang certificate at key, karaniwan dahil dalawang beses pinatakbo ang generation command at napaghalo ang mga file. Kumpirmahin gamit ang openssl x509 -in git.internal.crt -noout -pubkey | sha256sum kumpara sa openssl pkey -in git.internal.key -pubout | sha256sum; nangangahulugan ang magkaparehong hash na magkapareha ang mga ito. Kung magkaiba, i-generate muli ang dalawa nang magkasama.
FAQ
Bakit sinasabi pa rin ng Chrome na "Not secure" matapos akong gumawa ng self-signed certificate?
Kung ang error ay NET::ERR_CERT_AUTHORITY_INVALID, maayos ang certificate; wala pa itong mapagkakatiwalaang pinagmulan para sa Chrome. I-install ito, o ang private CA root nito, sa trust store ng client. Tandaan na sa Linux, ginagamit ng Chrome ang NSS database sa pamamagitan ng certutil, hindi ang system store. Kung ang error ay NET::ERR_CERT_COMMON_NAME_INVALID, walang Subject Alternative Name ang certificate na tumutugma sa URL. Kailangan itong i-reissue gamit ang -addext "subjectAltName=...".
Paano mapagkakatiwalaan ng curl ang isang self-signed certificate nang hindi gumagamit ng -k?
Kopyahin ang certificate, na nasa PEM format at may .crt extension, sa /usr/local/share/ca-certificates/. Pagkatapos, patakbuhin ang sudo update-ca-certificates. Dapat sabihin ng output na 1 added. Mula noon, ibe-verify ito ng curl gaya ng isang public certificate. Para sa isang request lamang at hindi binabago ang system, ibe-verify ng curl --cacert /path/to/cert.crt ang certificate laban sa file na iyon lang. Ganap namang dini-disable ng -k ang verification, kaya hindi ito dapat gamitin sa anumang script.
Gaano katagal maaaring maging valid ang isang self-signed certificate?
Sa teknikal na paraan, maaari itong maging valid sa anumang haba ng panahon. Ang mga limitasyon ng CA/Browser Forum, na 200 days ngayon at magiging 47 pagsapit ng 2029, ay para sa mga CA na pinagkakatiwalaan ng publiko, hindi para sa private trust. Sa aktuwal na paggamit, limitahan sa 825 days ang server certificates, dahil nire-reject ng Apple devices ang mas mahaba rito anuman ang issuer. Makatuwirang default ang ten-year private root na may two-year (-days 730) leaf certificates. I-schedule lamang ang renewal, dahil maaaring tahimik na pabagsakin ng expired internal certificate ang lahat sa petsang walang nakakaalala.
Dapat ba akong gumamit ng self-signed certificate o Let's Encrypt?
Kung may public DNS name ang service at naaabot ito mula sa internet, palaging gamitin ang Let's Encrypt. Libre ito, automated, at pinagkakatiwalaan na ng lahat ng client. Ang self-signed certificate, o private CA, ay para sa mga certificate na hindi maibibigay ng Let's Encrypt: private IPs, internal-only hostnames gaya ng .lan, air-gapped networks, at mga service na sadyang nakatago sa likod ng VPN. Ang desisyon ay nakabatay sa reachability at naming, hindi sa lakas ng seguridad. Magkapareho ang cryptography.
Bakit nire-reject ang certificate ko kahit idinagdag ko na ito sa /usr/local/share/ca-certificates?
Suriin ang tatlong bagay. Dapat magtapos ang file sa .crt. Tahimik na nilalaktawan ang .pem extension, at iniuulat ng update-ca-certificates ang 0 added. Dapat PEM text ang laman na nagsisimula sa -----BEGIN CERTIFICATE-----, hindi DER binary. Dapat ding aktuwal na gamitin ng application ang system store. Ang Chrome sa Linux, Firefox, Python requests, Node.js, at Java ay may kanya-kanyang private trust store, kaya kailangang idagdag nang hiwalay ang certificate sa bawat isa.