SSD Nodes Learn
Mga Gabay Matt ConnorNi Matt Connor · Na-update 2026-07-24

Paano gumawa ng self-signed cert sa Ubuntu

Matutong gumamit ng openssl na may SAN para sa Ubuntu 24.04. I-configure ang nginx o Apache at i-trust ang cert para hindi na kailanganin ang curl -k flag.

Ang iyong bubuuin

Isang self-signed TLS certificate na tinatanggap ng mga modernong browser at client — may tamang subjectAltName, maayos na key permissions, at naka-configure sa nginx o Apache. Kasama rin dito ang bahaging madalas nakakaligtaan sa mga guide: ang paggawa para maging trusted ang certificate ng iyong mga client, sa halip na mag-click lang ng mga warning at i-hard-code ang curl -k sa mga script habambuhay. Sa huli, makakakuha ka ng isang five-command private CA para kapag ang isang internal service ay naging anim na.

Una, ang pagpapasya, dahil mas madalang gamitin ang self-signed certificate kaysa sa inaakala. Kung ang service ay accessible mula sa public internet gamit ang totoong DNS name, itigil ang pagbabasa at kumuha na lang ng free Let's Encrypt certificate gamit ang certbot sa nginx o ang Apache equivalent. Wala itong gastos, kusang nagre-renew, at trusted na ito ng lahat ng browser sa mundo. Ang paggamit ng self-signed certificate sa isang public site ay nagtuturo sa iyong mga user na i-click ang mga security warning, na mas masamang habit kaysa sa plain HTTP.

Ang self-signed ay tamang tool kapag walang public internet na kasama: isang admin panel na naka-bind sa isang WireGuard tunnel address sa iyong VPS, isang staging box sa isang 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 ang maglalagay ng private IP o imbento na TLD sa isang certificate. Para sa mga pangalang iyon, ikaw ang CA.

Ang lahat sa ibaba ay tumatakbo sa isang fresh Ubuntu 24.04 box na may OpenSSL 3.0.x (gamitin ang openssl version para mag-confirm). Hindi kailangan ng internet access dito; gumagana ito kahit air-gapped.

Bakit tinatanggihan ng Chrome ang mga lumang one-liner certs

Ganito ang itsura ng command na makikita sa lahat ng tutorial bago ang 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.crt

Nagbibigay ito ng serye ng mga interactive na tanong, inilalagay ang iyong hostname sa Common Name field, at gumagawa ng certificate na walang subjectAltName extension. Hindi gagana ang certificate na iyon. Itinigil na ng Chrome ang pagbabasa ng Common Name sa version 58 noong April 2017 — deprecated na ang CN matching sa ilalim ng RFC 2818 simula pa noong taong 2000 — at ganoon din ang gawi ng Firefox, Safari, curl, at Python. Kinikilala ng certificate ang server nito gamit ang SAN extension; kung wala ito, hindi ito gagana. Ganito ang eksaktong mensahe ng browser:

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 halaga ang pag-aayos sa trust-store para ayusin ang error na ito, dahil walang pangalan na nakalagay sa certificate. Kung nakikita mo ang NET::ERR_CERT_COMMON_NAME_INVALID ngayon, walang SAN ang iyong certificate (o mali ang nakalagay) at kailangan mong gumawa ng bago. Sa kabutihang palad, isang command lang ang solusyon.

Gumawa ng certificate na tinatanggap ng mga browser: isang command

Idinagdag ang -addext flag sa OpenSSL 1.1.1, kaya hindi na kailangan ang komplikadong config-file setup 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"

Ang function ng bawat flag:

  • Ang -x509 ay direktang gumagawa ng self-signed certificate sa halip na signing request.
  • Ang -newkey rsa:4096 ay gumagawa ng bagong key sa parehong step. Ang RSA 4096 ay compatible sa mga legacy client; kung modern ang lahat ng koneksyon, mas maliit at mas mabilis ang -newkey ec -pkeyopt ec_paramgen_curve:P-256.
  • Ang -noenc ay ang OpenSSL 3.x version ng lumang -nodes: walang passphrase ang key. Parehong gumagana ang dalawang spelling. Ang may passphrase na key ay magiging sanhi ng pag-hang ng nginx habang naghihintay ng input sa bawat boot, kaya kailangan ito para sa server key.
  • -days 730 — dalawang taon; tingnan ang expiry section para sa detalye.
  • Ang -subj ay sumasagot sa mga interactive question nang automatic. Ang CN ay cosmetic na lamang, pero i-set pa rin ito sa primary name; ginagamit ito ng ilang tools.
  • Ang -addext "subjectAltName=..." ang pinaka-importanteng flag. Ilista ang bawat pangalan at bawat IP na itatype ng mga client: DNS: entries para sa mga hostname (pwedeng gumamit ng wildcard gaya ng DNS:*.internal.lan), IP: entries para sa mga address. Kung may magba-browse sa https://10.8.0.1, dapat nandoon ang IP:10.8.0.1 entry — ang DNS-only SAN ay magdudulot muli ng NET::ERR_CERT_COMMON_NAME_INVALID.

I-verify kung nailagay ang SAN bago mag-configure ng iba pang bagay:

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

Tamang output:

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

Kung No extensions in certificate ang lumabas, walang SAN ang certificate at ire-reject ito ng mga browser — mag-regenerate muli sa halip na ituloy ang setup.

I-lock down ang key

Ang private key na nababasa ng lahat ng user sa system ay hindi na maituturing na private key. Sa Ubuntu, ang /etc/ssl/private ay naka-710 root:ssl-cert na, kaya hindi ito agad makikita ng iba, pero dapat i-set ang file permissions nang direkta:

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

Binabasa ng nginx at Apache ang mga certificate bilang root bago i-drop ang privileges, kaya gumagana ang root:root mode 600 para sa kanila. Kung ang key ay para sa isang service na tumatakbo gamit ang sarili nitong user at mismong service ang naglo-load ng key — gaya ng Node app, Gitea, o Python daemon — i-chown ito sa service user na iyon gamit ang mode 600. Ang bawal gawin: mode 644, paglalagay ng kopya sa git repository, o paglalagay ng kopya sa /tmp.

I-integrate 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 nginx

Dapat mag-print ang nginx -t ng syntax is ok at test is successful bago gumana ang reload. Kung SSL_CTX_use_PrivateKey_file() failed ... key values mismatch ang i-print nito, ang certificate at key ay mula sa magkaibang generation runs — tingnan ang failure-modes section.

Wire it into Apache

sudo a2enmod ssl proxy proxy_http

Hindi sapat ang ssl lang dito: ginagamit ng vhost sa ibaba ang ProxyPass, at kung walang mod_proxy at mod_proxy_http, mag-eerror ang config test gamit ang 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 apache2

Dapat sagutin ng configtest ang Syntax OK. Ngayon, i-test ito mula sa isang client machine:

curl -v https://git.internal.lan/

at makakakuha ka ng error:

curl: (60) SSL certificate problem: self-signed certificate

Hindi ito bug. Ito ay normal na TLS behavior: hindi kilala ng curl ang iyong certificate kaya tumatanggi itong makipag-usap sa server na hindi ma-authenticate. Ang susunod na section ang tamang solusyon — at hindi ito ang ginagawa ng kalahati ng internet sa oras na ito.

Gawing trusted ang certificate — at ang mga anti-pattern na dapat iwasan

Ang mga maling fix, na tinutukoy kung ano ang mga ito. Ang pag-bake ng curl -k (o --insecure) sa isang script, verify=False sa Python requests, o NODE_TLS_REJECT_UNAUTHORIZED=0 sa Node — hindi nito gagawing trusted ang iyong certificate. Pinapatay nito ang certificate verification, kaya makikipag-usap ang client sa kahit anong server na nagpapakita ng kahit anong certificate, kasama na ang certificate mula sa isang attacker. Mananatili ang overhead ng TLS pero mawawala ang authentication na siyang layunin nito. Mas malala, kumakalat ang mga flag na ito: ididikit sa isang cron job, pagkatapos ay sa isang deploy script, hanggang sa production code, hanggang sa wala nang nakakaalam kung aling mga connection ang dapat sana ay temporary lang. Kung ang isang verify=False ay mananatili lampas sa debugging session kung saan ito ginamit, mali ang design.

Ang tamang fix ay turuan ang bawat client OS na ang certificate na ito ay isang trusted root. Para sa Ubuntu at Debian clients:

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

Ang mahalagang linya sa output (may kasunod na Running hooks in /etc/ca-certificates/update.d... block):

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

May dalawang dapat bantayan sa mga linyang ito. Ang file ay dapat magtapos sa .crt — ang .pem extension ay hindi babasahin at makakakuha ka ng 0 added nang walang error message. At ang contents ay dapat PEM — 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 ng self-signed certificate bilang root dahil ang isang self-signed certificate ay sarili nitong root.

Pagkatapos nito, ang curl, wget, git, apt, at anumang gumagamit ng OpenSSL laban sa system bundle ay magtitiwala sa server nang walang anumang flag. May ilang clients na may sariling trust stores at nangangailangan ng indibidwal na setup:

  • Chrome/Chromium sa Linux ay nagbabasa ng NSS database, hindi ang system store: sudo apt install libnss3-tools, pagkatapos ay certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "git.internal" -i git.internal.crt bawat user.
  • Firefox ay may sariling store: Settings → Privacy & Security → Certificates → Import, o i-set ang security.enterprise_roots.enabled sa true sa about:config para magbasa ito mula sa system store.
  • Python requests ay may kasamang sariling CA bundle (certifi) at hindi binabasa ang system store: gamitin ang verify="/usr/local/share/ca-certificates/git.internal.crt" o i-export ang REQUESTS_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 sa Keychain Access at i-marka bilang Always Trust.

Isang root para sa maraming services: isang maliit na private CA

Hindi scalable ang per-certificate trust: ang anim na services sa apat na client machines ay nangangailangan ng dalawampu't apat na trust installs, at nadaragdagan ito sa bawat bagong service. Ang solusyon ay isang private CA — magtitiwala ang mga client sa isang root, at gagamitin mo ito para i-sign ang certificate ng bawat service.

Ang madaling opsyon ay ang mkcert. Matatagpuan ito sa Ubuntu 24.04 repos at kayang i-handle ang mga NSS stores (Chrome, Firefox) na hindi kayang gawin ng update-ca-certificates:

sudo apt install -y mkcert libnss3-tools
mkcert -install
mkcert git.internal.lan "*.internal.lan" 10.8.0.1

Gumagawa ang mkcert -install ng root at nire-register ito sa lahat ng 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 gamitin sa mga nginx o Apache snippets sa itaas. Ang design assumption nito ay para sa development machine — ang root key ay nasa machine kung saan pinatakbo ang -install — kaya perpekto ito para sa dev laptop pero hindi angkop para sa server fleet.

Para sa mga server, kayang gawin ng OpenSSL ang buong CA gamit ang 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.crt

Ang problema ay nasa huling command: openssl x509 -req tinatanggal ang lahat ng extensions mula sa CSR by default, kasama na ang SAN na maingat mong idinagdag. Ang -copy_extensions copy (isang OpenSSL 3.x option, kaya gumagana ito sa 24.04) ang nagpapanatili sa mga ito; kung hindi ito gagamitin, walang SAN ang signed certificate, at muling lalabas ang NET::ERR_CERT_COMMON_NAME_INVALID sa Chrome. I-verify gamit ang parehong openssl x509 -noout -ext subjectAltName check gaya ng dati.

I-distribute ang lab-ca.crt sa mga client gamit ang mga trust-store steps sa itaas — isang beses lang bawat machine. Protektahan ang lab-ca.key dahil napakahalaga nito: gamitin ang mode 600, at mas mabuting itago ito sa machine na hindi kasama sa mga server na sineserbisyuhan nito, dahil ang sinumang may hawak nito ay kayang gumawa ng certificate para sa anumang pangalan na paniniwalaan ng iyong mga client.

Expiry at rotation

Paikli na ang lifetime ng mga Public CA certificate — nilimitahan ng CA/Browser Forum ang mga bagong-isyung publicly trusted certificates sa 200 days noong March 2026 (mula sa 398), na bababa sa 100 days sa 2027 at 47 days pagdating ng March 2029 — ngunit ang mga panuntunang ito ay para lamang sa mga publicly trusted CA. Hindi sakop ng mga ito ang iyong private CA, at hindi rin ipinapatupad ng mga browser ang mga ito sa mga manually installed roots. May isang real-world restriction na dapat tandaan: tinatanggihan ng mga Apple platform ang anumang TLS server certificate na valid nang higit sa 825 days anuman ang nag-issue nito. Kung gagamit ng iPhone o Mac, panatilihin ang leaf certificates sa loob ng dalawang taon o mas maikli pa. Ang -days 730 ay pasok sa limitasyong iyon sa lahat ng platform; ang isang ten-year root na may dalawang-taong leaves ay mainam na configuration para sa internal use.

Ang mga long-lived certificate ay pumapalya sa iisang paraan: tahimik at sabay-sabay, sa petsang hindi naman naisulat ng sinuman. Suriin ang iyong mga kasalukuyang certificate:

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

Ilagay ang renewal sa isang calendar, o gumamit ng cron para mag-alert 30 days bago ang expiry — naglalabas ng non-zero exit code ang openssl x509 -checkend 2592000 -in cert.crt kapag ang expiry ay nasa loob na ng bilang na iyon ng mga segundo. Kung gumagamit ka na ng Uptime Kuma para sa status monitoring, libreng nagbibigay ng flag ang mga HTTPS monitor nito kapag malapit na ang certificate expiry.

Ang rotation gamit ang private CA ay napakasimple: i-run 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.

Failure modes, with the strings you will see

NET::ERR_CERT_AUTHORITY_INVALID — ang expected na state bago i-install ang trust, hindi ito defect sa certificate. Kung mananatili ito pagkatapos i-install ang root: sa Linux, binabasa ng Chrome ang NSS sa halip na ang system store (tingnan ang certutil step); o ang kinopya na file ay walang .crt at sinabi ng update-ca-certificates na 0 added; o ang server ay nagpapakita ng ibang certificate kaysa sa iyong ni-trust — i-compare ang mga fingerprint 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 sakop ng SAN ang pangalan sa address bar. Karaniwang case: nakalista ang DNS:git.internal.lan sa SAN pero ang binisita ng user ay https://10.8.0.1. Hindi kayang ayusin ng trust-store changes ito; i-reissue ang certificate kasama ang kulang na entry.

curl: (60) SSL certificate problem: self-signed certificate — hindi ni-ti-trust ng curl ang certificate. Ang variant na self-signed certificate in certificate chain ay pareho lang ang ibig sabihin para sa cert na signed ng iyong private CA. One-off fix: curl --cacert lab-ca.crt https://...; permanent fix: ang trust store. Hindi ang -k.

unable to load certificate ... Expecting: TRUSTED CERTIFICATE (o Expecting: CERTIFICATE REQUEST, o no start line) — PEM confusion. Nagbigay ka sa OpenSSL ng maling uri ng file: key o CSR sa halip na certificate, o DER binary sa halip na PEM. Sasabihin ng head -1 filename kung ano ang hawak mo — ang certificate ay nagsisimula sa -----BEGIN CERTIFICATE-----. Para sa DER, i-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 magkapares ang certificate at key, kadalasan dahil na-run nang dalawang beses ang generation command at naghalo ang mga file. I-confirm gamit ang openssl x509 -in git.internal.crt -noout -pubkey | sha256sum laban sa openssl pkey -in git.internal.key -pubout | sha256sum; kung magkapareho ang mga hash, magkapares ang mga ito. Kung magkaiba, i-regenerate ang parehong file nang sabay.

FAQ

Bakit "Not secure" pa rin ang Chrome kahit gumawa na ako ng self-signed certificate?

Kung NET::ERR_CERT_AUTHORITY_INVALID ang error, maayos ang certificate — wala lang talagang dahilan ang Chrome para pagkatiwalaan ito. I-install ang certificate (o ang iyong private CA root) sa trust store ng client. Tandaan na sa Linux, ginagamit ng Chrome ang NSS database via certutil sa halip na ang system store. Kung NET::ERR_CERT_COMMON_NAME_INVALID ang error, kulang ang certificate sa Subject Alternative Name na tumutugma sa URL; kailangan itong i-reissue gamit ang -addext "subjectAltName=...".

Paano gagawing trusted ng curl ang self-signed certificate nang hindi gumagamit ng -k?

I-copy ang certificate (PEM format, .crt extension) sa /usr/local/share/ca-certificates/ at i-run ang sudo update-ca-certificates — dapat ang output ay 1 added. Pagkatapos nito, i-veverify na ng curl ito gaya ng anumang public certificate. Para sa isang request na hindi binabago ang system, ginagamit ang curl --cacert /path/to/cert.crt para mag-verify gamit ang file na iyon lamang; ang -k ay nag-o-off ng verification nang buo at hindi dapat gamitin sa anumang script.

Gaano katagal pwedeng maging valid ang isang self-signed certificate?

Technically, kahit gaano mo gustuhin — ang mga limitasyon ng CA/Browser Forum (200 days ngayon, 47 sa 2029) ay para lamang sa mga publicly trusted CA, hindi sa private trust. Sa praktikal na aspeto, limitahan ang server certificates sa 825 days, dahil tinatanggihan ng mga Apple device ang anumang mas mahaba rito anuman ang issuer. Ang isang ten-year private root na may two-year (-days 730) leaf certificates ay isang maayos na default; i-schedule lang ang renewal, dahil ang expired na internal cert ay magpapabagsak sa lahat ng serbisyo nang tahimik sa petsang hindi matandaan ng sinuman.

Dapat ko bang gamitin ang self-signed certificate o Let's Encrypt?

Kung ang service ay may public DNS name at accessible mula sa internet, laging Let's Encrypt ang gamitin — libre, automated, at trusted na ng lahat ng client. Ang self-signed (o private CA) ay para sa mga bagay na hindi kayang i-issue ng Let's Encrypt: mga private IP, internal-only hostnames gaya ng .lan, air-gapped networks, at mga serbisyong sadyang nakatago sa likod ng VPN. Ang desisyon ay nakabase sa reachability at naming, hindi sa security strength — pareho lang ang cryptography nito.

Bakit rejected ang certificate ko kahit na-add ko na ito sa /usr/local/share/ca-certificates?

I-check ang tatlong bagay. Dapat ang file ay nagtatapos sa .crt — ang .pem extension ay hindi binabasa at mag-uulat ang update-ca-certificates ng 0 added. Ang contents ay dapat PEM text na nagsisimula sa -----BEGIN CERTIFICATE-----, hindi DER binary. At dapat ay ginagamit ng application ang system store — ang Chrome sa Linux, Firefox, Python requests, Node.js, at Java ay may kani-kaniyang private trust store at kailangang i-add ang certificate nang hiwalay.

#openssl#tls#self-signed#ubuntu#security