Ubuntu CA Sertifikası Ekleme ve Güven Deposu Ayarları
Ubuntu sisteminde kendi CA sertifikanızı /usr/local/share/ca-certificates dizinine ekleyerek HTTPS hatalarını gidermek için update-ca-certificates komutunu kullanın.
Kendi CA sertifikanızı Ubuntu güven deposuna ekleme
Kendi CA sertifikanızı Ubuntu güven deposuna eklemek için kök sertifikayı /usr/local/share/ca-certificates/ dizinine, ismi .crt ile bitecek şekilde kopyalayın ve ardından sudo update-ca-certificates komutunu çalıştırın. CA (sertifika yetkilisi), sertifikası diğer sertifikaları imzalamaya yetkili olan bir anahtar çiftidir. Makine kök sertifikanıza güvendiğinde, bu kök tarafından imzalanan her sertifika kabul edilir; böylece kendi servisleriniz arasındaki HTTPS bağlantılarında doğrulama hataları sona erer.
Bu kılavuz, tüm zinciri openssl kullanarak çevrimdışı oluşturur. Bir kök anahtar ve kök sertifika oluşturacak, bir sunucu için yaprak sertifika düzenleyecek, ardından kök sertifikayı yükleyerek aynı doğrulama komutunun verdiği yanıtın değişimini gözlemleyeceksiniz. Bu sıralamanın amacı şudur: Yükleme öncesinde ve sonrasında doğrulama yapmak, sonucun yükleme işlemiyle değiştiğini görmenizi sağlar.
Ubuntu 24.04, varsayılan imajında OpenSSL 3 ve ca-certificates paketini içerir; bu nedenle önceden herhangi bir kurulum yapılması gerekmez (Ağustos 2026 itibarıyla doğrulanmıştır).
Kendi CA'nızı ne zaman çalıştırmalısınız?
Let's Encrypt gibi halka açık bir CA, genel DNS üzerinde bir isme ve erişebileceği bir sunucuya ihtiyaç duyar. Dahili isimler bu şartı sağlamaz. Özel bir ağdaki veritabanı veya bir tünele bağlı yönetim paneli genel bir sertifika alamaz; ayrıca sırf sertifika almak için bu servislerin internete açılması da doğru değildir.
Ubuntu üzerinde kendinden imzalı sertifika, yalnızca tek bir ana bilgisayar için çözüm sunar. Her istemcinin bu tek sertifikaya güvenmesi gerekir ve bir sonraki ana bilgisayarda aynı işlemler baştan yapılır. Özel bir CA, bu karar mekanizmasını bir üst seviyeye taşır. İstemciler kök sertifikaya bir kez güvenir; ardından kök sertifikanın imzaladığı her sertifika, henüz var olmayan ana bilgisayarlar için olanlar dahil, otomatik olarak güvenilir kabul edilir.
Bunun maliyeti gerçektir. Kök anahtar, kısıtlamaların izin verdiği her şeyi imzalayabilir; bu nedenle ca.key dosyasını okuyan herkes, makinelerinizin kabul edeceği sertifikalar düzenleyebilir. Bu anahtarı, SSH anahtar yönetimi içindeki özel bir anahtarı koruduğunuz gibi koruyun. Eğer bir servisin genel bir DNS ismi varsa, tüm bu süreçleri atlayın ve halka açık bir CA kullanın: Nginx ve Let's Encrypt ile Certbot kullanımı daha az iş yükü gerektirir ve istemci tarafında herhangi bir kurulum yapılmasına ihtiyaç duymaz.
CA anahtarını ve kök sertifikayı oluşturma
Yalnızca kullanıcınızın erişebildiği bir dizinde çalışın. Kök anahtar bu dizinden asla dışarı çıkarılmamalıdır.
install -d -m 700 ~/ca
cd ~/ca
openssl genrsa -aes256 -out ca.key 4096
chmod 600 ca.key-aes256, anahtarı belirlediğiniz bir parola ile şifreler; bu anahtarla imzalama yapan sonraki her komut bu parolayı soracaktır. -aes256 bayrağını kullanmazsanız anahtar diskte açık metin olarak saklanır; bu durumda bir yedek veya ikinci bir yönetici hesabı, makinelerinizin güvendiği sertifikaları düzenleme yetkisini başkasına vermek için yeterli olur.
Şimdi, CA anahtarının kendisini imzaladığı kök sertifikayı oluşturalım.
openssl req -x509 -new -key ca.key -sha256 -days 3650 \
-subj "/O=Example Internal/CN=Example Internal Root CA" \
-addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
-addext "keyUsage=critical,keyCertSign,cRLSign" \
-addext "subjectKeyIdentifier=hash" \
-addext "nameConstraints=critical,permitted;DNS:internal.example" \
-out ca.crtinternal.example ifadesini kullandığınız isim sonekiyle değiştirin ve son uzantıyı korumadan önce bir sonraki bölümü okuyun.
Her uzantı tek bir işlev görür.
basicConstraintsveCA:TRUEkullanımı, bunun bir CA sertifikası olmasını sağlar. Bu olmadan, imza doğru olsa bile bir istemci, bu anahtarın imzaladığı hiçbir sertifikayı kabul etmez.pathlen:0, CA'nın uç sertifikaları imzalayabileceğini ancak altında başka bir CA oluşturamayacağını belirtir.keyUsage, anahtarı yalnızca sertifika ve iptal listelerini imzalama ile sınırlar; böylece aynı anahtar yanlışlıkla bir TLS sunucu anahtarı olarak kullanılamaz.subjectKeyIdentifier, kök sertifikaya, uç sertifikaların referans aldığı bir tanımlayıcı verir; istemciler, yüzlerce sertifikanın bulunduğu bir depoda doğru düzenleyiciyi bu sayede bulur.nameConstraints, bu CA'nın adına kefil olabileceği isimleri sınırlar.
Komutun istediğiniz işlemi yaptığını varsaymak yerine, oluşturduğunuz dosyayı kontrol edin.
openssl x509 -noout -subject -issuer -serial -dates -in ca.crt
openssl x509 -noout -text -in ca.crtKök sertifika kendini imzaladığı için konu (subject) ve düzenleyici (issuer) aynı dizgiyi yazdırır. Seri numarası ve iki tarih bilgisi az önce oluşturduğunuz dosyadan gelir; bu nedenle bunları herhangi bir rehberden değil, doğrudan bu çıktıdan alın.
CA'nizin imzalama yetkisini sınırlandırın
Sistem deposundaki bir kök sertifika, aksi belirtilmediği sürece internetteki her alan adı için güvenilir kabul edilir. Bu, tek bir sunucudaki tek bir dosya için oldukça büyük bir yetki alanıdır. nameConstraints bu yetkiyi azaltır. Kök sertifikada permitted;DNS:internal.example bulunduğunda, bu CA tarafından imzalanmış olsa bile internal.example dışındaki bir alan adına ait zincir, imza geçerli olsa dahi reddedilir.
Bunu güvenmek yerine test edin.
openssl req -new -newkey rsa:2048 -nodes -keyout /tmp/outside.key -out /tmp/outside.csr \
-subj "/CN=www.example.com"
openssl x509 -req -in /tmp/outside.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-days 30 -sha256 \
-extfile <(printf 'subjectAltName=DNS:www.example.com\n') -out /tmp/outside.crt
openssl verify -CAfile ca.crt /tmp/outside.crt
echo $?Sertifika düzenlenir, çünkü CA'niz talep ettiğiniz her şeyi imzalar. Doğrulama aşamasında ise süreç başarısız olur: çıkış durumu sıfır değildir ve OpenSSL karşılaşılan kısıtlamayı belirtir. Uzantının değeri budur. Çalınmış bir CA anahtarı bile, alt ağaç dışındaki bir isim için çalışan bir sertifika üretemez. İşiniz bittiğinde rm /tmp/outside.* ile artıkları silin.
Bir kısıtlamayı uygulamadan önce bilmeniz gereken dört husus vardır. Bu kısıtlama critical olarak işaretlenmiştir; bu nedenle uzantıyı anlamayan bir istemci, zinciri görmezden gelmek yerine reddetmelidir. Bu güvenli bir yaklaşımdır ancak eski bir TLS kütüphanesini şaşırtabilir. DNS isimleri için izin verilen bir alt ağaç, IP adresi SAN'larını kısıtlamaz; çünkü listelenmemiş bir isim türü kısıtlanmamış kalır. Bu nedenle, sertifikalarınız IP adresleri içeriyorsa aynı uzantıya permitted;IP:10.0.0.0/255.255.0.0 ekleyin. Alt ağaç, kısa ana bilgisayar adları da dahil olmak üzere düzenleyeceğiniz tüm isimleri kapsamalıdır; dolayısıyla app gibi yalın bir isim için düzenlenen sertifika, yukarıdaki örnekte başarısız olur. Kısıtlama kök sertifikaya işlendiği için, fikrinizi değiştirmeniz yeni bir kök sertifika ve her istemcide temiz bir kurulum anlamına gelir.
CA tarafından imzalanmış bir yaprak sertifika oluşturma
Yaprak sertifika, bir sunucunun istemcilere sunduğu sertifikadır. İşleme, kendi anahtarı ve talep edenin özel anahtara sahip olduğunu kanıtlamak için yaprak anahtarı ile imzalanmış, ortak anahtarı ve talep edilen ismi taşıyan bir CSR (sertifika imzalama talebi) ile başlayın.
openssl req -new -newkey rsa:2048 -nodes \
-keyout app.key -out app.csr \
-subj "/CN=app.internal.example"
chmod 600 app.keyÖnemli olan isimler CSR içinde değil, bir uzantı dosyasında yer almalıdır. İstemciler ana makine adını subjectAltName (SAN) ile eşleştirir ve common name (ortak isim) alanını tamamen görmezden gelir; bu nedenle, CN alanı dolu olsa bile SAN alanı olmayan bir sertifika, CN ne olursa olsun güncel tüm istemcilerde ana makine adı doğrulamasından geçemez.
basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:app.internal.example, DNS:api.internal.example
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:alwaysBunu app.ext olarak kaydedin ve ardından talebi CA ile imzalayın.
openssl x509 -req -in app.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-days 397 -sha256 -extfile app.ext -out app.crt-CAcreateserial, bir sonraki seri numarasını tutan ca.srl dosyasını CA'nın yanına yazar; böylece bu CA tarafından verilen hiçbir sertifika aynı seri numarasını paylaşmaz. Bu dosyayı CA dizininde tutun. -days 397 bir tercih meselesidir, aracın bir sınırlaması değildir. Özel bir CA'nın iptal altyapısı (CRL veya OCSP yanıtlayıcısı oluşturmadığınız sürece) bulunmadığından, burada kısa ömürlü sertifikalar kullanmak, genel bir CA kullanmaktan daha kritiktir; sızdırılmış bir yaprak anahtar, sertifika süresi dolana kadar kullanılabilir kalır.
Güven deposuna (trust store) yaklaşmadan önce sonucu kontrol edin.
openssl x509 -noout -subject -issuer -serial -dates -in app.crt
openssl x509 -noout -ext subjectAltName -in app.crtIssuer (yayınlayan) satırı artık yaprağın kendisi yerine CA'yı belirtir. SAN satırı, bu sertifikanın geçerli olduğu isimleri listeler; istemci, eşleştirmeyi yalnızca bu liste üzerinden yapar.
Herhangi bir kurulum yapmadan önce -CAfile ile doğrulama yapın
openssl verify -CAfile ca.crt app.crt
echo $?Bu komut tek bir dar kapsamlı soru sorar: app.crt, ca.crt içindeki sertifika ile zincirleniyor mu? Bu makinenin neye güvendiği hakkında hiçbir şey söylemez, çünkü OpenSSL'e kök sertifikayı komut satırından verdiniz. Burada alınan bir hata, sertifikaların kendisiyle ilgili bir sorundur; bu yüzden devam etmeden önce bunu düzeltin.
Şimdi makineye sorun.
openssl verify app.crt
echo $?-CAfile parametresi olmadığında, OpenSSL yerleşik sertifika dizinine geri döner. openssl version -d, derlemenizin kullandığı temel dizini yazdırır ve Ubuntu üzerinde bunun altındaki certs dizini /etc/ssl/certs yoluna çözümlenir. Kök sertifikanız henüz orada olmadığı için doğrulama başarısız olur: zincir, deponun sahip olmadığı bir vericiye ulaşır ve bakılacak başka yer kalmaz. Çıkış durumuna dikkat edin. İki adım sonra değişecek olan şey budur.
Gerçek bir istemci, ana bilgisayar adını ve zinciri de kontrol ettiği için openssl verify aracından daha iyi bir test yapar. Sertifikayı sunun ve çekin.
openssl s_server -accept 8443 -cert app.crt -key app.key -www &
curl --resolve app.internal.example:8443:127.0.0.1 https://app.internal.example:8443/--resolve, bağlantıyı 127.0.0.1 adresine gönderirken hala app.internal.example talebinde bulunur, böylece SAN eşleşir ve geriye kalan tek soru güven konusudur. curl başarısız olur ve zinciri neden doğrulayamadığının nedenini yazdırır. Daha fazla ayrıntı için -v ekleyin. Test sunucusunu çalışır durumda bırakın.
Root sertifikasını /usr/local/share/ca-certificates dizinine yükleyin
sudo cp ca.crt /usr/local/share/ca-certificates/example-internal-root.crt
sudo chmod 644 /usr/local/share/ca-certificates/example-internal-root.crt
sudo update-ca-certificatesBu işlemin başarılı olup olmayacağını belirleyen ayrıntılar şunlardır:
- Dosya adı
.crtile bitmelidir.update-ca-certificateskılavuz sayfası,/usr/local/share/ca-certificatesaltında bulunan ve.crtuzantısına sahip sertifikaların dahil edildiğini ve örtük olarak güvenildiğini belirtir.root.pemveyaroot.cerolarak adlandırılan bir dosya, hiçbir uyarı verilmeden atlanır. - İçerik,
BEGIN CERTIFICATEveEND CERTIFICATEsatırları arasına sarılmış base64 bloğu olan PEM formatında olmalıdır..crtolarak yeniden adlandırılan bir DER dosyası ikili (binary) yapısını korur ve okunmaz. Bu dosyayıopenssl x509 -inform DER -in ca.der -out ca.crtile dönüştürün. - Buraya yalnızca root sertifikası eklenmelidir. CA özel anahtarı ve yaprak sertifikasının güven deposunda yeri yoktur.
update-ca-certificates, kaç sertifikanın eklendiğini ve kaldırıldığını yazdırır. Eğer hiçbir sertifika eklenmediyse, sorun uzantı veya dosya formatından kaynaklanıyordur.
Değişikliği, ekrandaki mesajdan ziyade sistem tarafında doğrulayın.
ls -l /etc/ssl/certs/$(openssl x509 -noout -subject_hash -in /usr/local/share/ca-certificates/example-internal-root.crt).0
grep -c 'BEGIN CERTIFICATE' /etc/ssl/certs/ca-certificates.crtİlk komut, sertifikanızın kendi konu özetinden (subject hash) bir dosya adı oluşturur ve bunu listeler. update-ca-certificates bu sembolik bağı oluşturmuştur ve bu bağ, yüklediğiniz dosyayı işaret eder. İkinci komut ise tek dosyalık paketteki sertifikaları sayar. Bu komutu yükleme öncesinde de çalıştırırsanız, sayının bir arttığını görebilirsiniz.
Bu root sertifikasını başka makinelere kopyaladığınızda, yüklemeden önce kopyanın bozulmadan ulaştığından emin olun. Bir root sertifikası, sistemde hatalı olması en tehlikeli dosya türüdür; bu nedenle onu kullanmadan önce sağlama toplamı ile doğrulayacağınız herhangi bir indirme işlemi gibi değerlendirin.
Sistem deposuna karşı tekrar doğrulama yapın
openssl verify app.crt
echo $?
curl --resolve app.internal.example:8443:127.0.0.1 https://app.internal.example:8443/Aynı komutlar, aynı sertifika dosyaları, farklı yanıt. app.crt ile ilgili hiçbir şey değişmedi ve sunucu, daha önce başlattığınız sunucudur. Tek fark, root sertifikanın artık istemcilerin okuduğu depoda bulunması ve bu sayede zincirin tamamlanmasıdır. Hatırlanması gereken mekanizma şudur: doğrulama, istemcinin halihazırda güvendiği bir vericiyi arama işlemidir ve bir CA yüklemek, vericinin arama yaptığı yere yerleşmesini sağlar.
Test sunucusunu kill %1 ile durdurun.
Neden /etc/ssl/certs dizinine dosya eklenmemelidir
/etc/ssl/certs üretilmiş bir çıktıdır. update-ca-certificates, bu dizini gerçek sertifika dosyalarına işaret eden sembolik bağlantılarla doldurur ve /etc/ssl/certs/ca-certificates.crt isimli birleştirilmiş paketi bunların yanına yazar.
Elle bu dizine kopyaladığınız bir sertifika hiçbir araç tarafından bulunamaz. OpenSSL'in dizin arama işlevi yalnızca sertifikanın konu özetine (subject hash) göre isimlendirilmiş dosyaları açar; bu nedenle myca.crt gibi bir dosya OpenSSL için görünmezdir. Ubuntu üzerindeki curl, paket dosyasını okur ve bu paket kayıtlı kaynaklardan yeniden oluşturulur; dolayısıyla sizin kopyaladığınız dosya bu yolda yer almaz. update-ca-certificates --fresh komutunu çalıştırdığınızda dizindeki sembolik bağlantılar silinip yeniden oluşturulur, bu işlem sırasında elle eklediğiniz tüm bağlantılar da silinir.
Bu yapının diğer yarısı, ca-certificates paketine ait olan ve /etc/ca-certificates.conf içerisinde listelenen /usr/share/ca-certificates dosyasıdır. Paket güncellemeleri bu dosyayı yeniden yazar. /usr/local/share/ca-certificates, yerel yöneticiye ayrılmış dizindir; bu sayede CA sertifikanız, dizinin geri kalanını yöneten paketin her yükseltmesinden sonra varlığını korur.
Hangi programlar sistem güven deposunu yok sayar
Kök sertifikanın yüklenmesi, OpenSSL kullanan veya /etc/ssl/certs dosyasını okuyan tüm programları düzeltir. Bu; curl, wget, git, Python'ın standart ssl modülü ve Linux üzerinde sistem dosyalarını okuyan Go programlarını kapsar. Kendi sertifika listesini beraberinde getiren çalışma zamanları (runtime) bundan etkilenmez; başarılı bir kurulumdan sonra yaşanan kafa karışıklığının çoğu da bu durumdan kaynaklanır.
- Node.js, derleme içine gömülü bir liste kullanır. Node değişkeni başlangıçta bir kez okuduğu için, süreci başlatmadan önce ortam değişkeni olarak
NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/example-internal-root.crtayarını yaparak onu kök sertifikanıza yönlendirin. Güncel Node sürümlerinde sistem deposunu okuma seçeneği de bulunur; sürümünüzün bu desteğe sahip olup olmadığını görmek içinnode --help | grep -i system-cakomutunu çalıştırın. - Python'ın
requestskütüphanesicertifipaketini kullanır. Bu süreç içinREQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crtdeğişkenini ayarlayın veya çağrıyaverify="/etc/ssl/certs/ca-certificates.crt"parametresini geçin.pipde aynı nedenle--certparametresini alır. - Java bir anahtar deposu (keystore) okur. Ubuntu üzerinde
ca-certificates-javapaketi,/etc/ca-certificates/update.d/altında bir kanca (hook) kurar; bu nedenle ilgili paket mevcut olduğundaupdate-ca-certificateskomutu Java anahtar deposunu da yeniler. Paket yoksa, kök sertifikayıkeytool -importcertile içe aktarın. - Firefox kendi deposunu tutar ve
/etc/ssl/certsdosyasına asla bakmaz. İçe aktarma işlemini sertifika ayarları üzerinden yapın. Linux üzerindeki Chromium,libnss3-toolspaketinden gelencertutilile düzenlediğiniz kullanıcıya özel bir NSS veritabanı okur. - Konteynerlerin kendi dosya sistemleri vardır, bu nedenle ana makinenin deposunun içeride bir anlamı yoktur. Kök sertifikayı imajın içine kopyalayın ve derleme sırasında
update-ca-certificateskomutunu çalıştırın. Servisleriniz Docker Compose on a VPS üzerinde çalışıyorsa bunu planınıza dahil edin.
Temiz bir kurulumdan sonra bir program sertifikayı hala reddediyorsa, başka bir değişiklik yapmadan önce hangi dosyaları açtığını tespit edin. strace -f -e trace=openat <command> 2>&1 | grep -i cert doğrudan sonuç verir ve soruyu tek bir çalıştırmada yanıtlar.
CA'in zaman içinde kullanılabilir durumda tutulması
Bir leaf sertifikasının yeniden oluşturulması, aynı app.ext dosyası kullanılarak CSR ve imzalama adımlarının tekrarlanmasından ibarettir. İstemcilerin herhangi bir işlem yapması gerekmez; çünkü güvendikleri kök sertifika değişmemiştir. Bir sonraki sertifika oluşturma işleminin hafızadan yeniden kurgulanması yerine daha önce çalışan bir komutun tekrarı olabilmesi için ca.srl dosyasını ve her bir .ext dosyasını CA dizininde tutun.
ca.key ve ca.crt dosyalarını, şifreli halde kalacak şekilde makine dışında bir yere yedekleyin. Anahtarı kaybederseniz yeni bir sertifika oluşturamazsınız; bu durumda ikinci bir CA kurmanız ve ilk CA'in yüklü olduğu her yere yeni kök sertifikayı yüklemeniz gerekir. Kök sertifikanın yüklü olduğu tüm makinelerin ve uygulama depolarının yazılı bir listesini tutun; çünkü rotasyon ve kaldırma işlemleri ancak bu liste sayesinde mümkün olur.
Kök sertifikanın geçerlilik süresi dolmaya yaklaştığında, yenisini erkenden oluşturun ve her iki kök sertifikayı yan yana yükleyin. Depoda iki kök sertifikanın bulunması sorun yaratmaz; istemci her ikisini de kabul eder. Leaf sertifikalarını yeni kök sertifikaya göre yeniden oluşturun ve ardından hiçbir şeyin eski kök sertifikaya bağımlı kalmadığından emin olduğunuzda onu kaldırın.
Bir CA'yı güven deposundan kaldırma
sudo rm /usr/local/share/ca-certificates/example-internal-root.crt
sudo update-ca-certificates --fresh--fresh, /etc/ssl/certs içindeki sembolik bağlantıları kaldırır ve bunları hala mevcut olan kaynaklardan yeniden oluşturur; böylece silinen kök sertifika hem dizinden hem de paketten çıkarılmış olur. Kaldırma işlemini, kurulumu doğruladığınız yöntemle doğrulayın.
openssl verify app.crt
echo $?
grep -c 'BEGIN CERTIFICATE' /etc/ssl/certs/ca-certificates.crt
ls -l /etc/ssl/certs/$(openssl x509 -noout -subject_hash -in ~/ca/ca.crt).0Doğrulama tekrar başarısız olur, sertifika sayısı başlangıçtaki değerine döner ve hash sembolik bağlantısı silinmiş olur.
Bu komut yalnızca sistem deposuna müdahale eder, başka hiçbir yere dokunmaz. Diğer konumlardaki kurulumu manuel olarak geri alın: NODE_EXTRA_CA_CERTS içeriğini temizleyin, herhangi bir Java anahtar deposundan (keystore) takma adı silin, kök sertifikayı her tarayıcı profilinden kaldırın ve içine gömülü olan tüm container imajlarını yeniden oluşturun. Kök sertifikayı kaldırmak, imzaladığı sertifikaları geçersiz kılmaz. Bu sertifikalar, kök sertifikaya güvenmeye devam eden her makinede geçerli kalır; özel bir CA'nın kök sertifikanın nerelere dağıtıldığının yazılı bir listesine ihtiyaç duymasının pratik nedeni budur. Tamamen geri çekemediğiniz bir CA kalıcı bir güvenlik açığıdır; bu nedenle, liste henüz kısayken kurulumu yaptığınız gün bir makine üzerinde kaldırma işlemini test edin.
FAQ
Where do I put a CA certificate on Ubuntu?
In /usr/local/share/ca-certificates/, with a filename ending in .crt and PEM content, then run sudo update-ca-certificates. That directory is reserved for the local administrator, so package upgrades leave it alone. /usr/share/ca-certificates belongs to the ca-certificates package, and /etc/ssl/certs is generated from both, so a file placed in either of those is overwritten or ignored.
Why does curl still reject the certificate after update-ca-certificates?
Work through the causes in order. The file may not end in .crt, or may be DER rather than PEM, in which case update-ca-certificates skipped it and added nothing. The certificate may have no subjectAltName matching the hostname, which is a hostname failure rather than a trust failure; check with openssl x509 -noout -ext subjectAltName -in app.crt. The server may be sending only the leaf when an intermediate is also needed. curl may be pointed at a different bundle by CURL_CA_BUNDLE or --cacert. And a long-running service needs a restart, because most programs read the trust store once when they start.
Does the system trust store cover Firefox, Chrome, Node and Java?
No. curl, wget, git, Python's standard ssl module and Go programs read the system files, so those work as soon as update-ca-certificates runs. Firefox keeps its own store. Chromium on Linux uses a per-user NSS database, edited with certutil from the libnss3-tools package. Node.js needs NODE_EXTRA_CA_CERTS pointing at your root file. Java reads a keystore, which update-ca-certificates refreshes only when the ca-certificates-java package is installed. Python's requests uses certifi and needs REQUESTS_CA_BUNDLE.
How do I remove a CA from Ubuntu's trust store?
Delete the file from /usr/local/share/ca-certificates/ and run sudo update-ca-certificates --fresh. The --fresh option clears the symlinks in /etc/ssl/certs and rebuilds them, so the certificate leaves the hash symlinks and the ca-certificates.crt bundle at the same time. Confirm by running openssl verify against a certificate that CA signed and reading the exit status. Then repeat the removal in every other store you added it to, because that command does not touch any of them.
Can I use a private CA instead of Let's Encrypt for a public site?
No. A visitor's browser has never seen your root, so it shows a full-page warning, and you cannot install your root on machines you do not control. A private CA is for names that only your own machines resolve and for clients you administer. For anything a stranger visits, get the certificate from a public CA.