Между корнем и сайтом бывает посредник — промежуточный CA.
Корневой ключ CA берегут и подписывают им редко — в основном сертификаты промежуточных CA. Промежуточный CA подписан корневым, а уже он подписывает сертификаты сайтов. Выходит цепочка: app.lab.test ← Lab Issuing CA ← Lab Root CA.
Клиент проходит её звено за звеном: issuer сертификата сайта — Lab Issuing CA, issuer Lab Issuing CA — Lab Root CA, а Lab Root CA лежит в хранилище. В хранилище — только корни. Промежуточные сертификаты клиент получает от сервера, в том же начале TLS, что и сертификат сайта.
openssl s_client из прошлого урока печатает и цепочку, которую прислал сервер: строка s: — subject сертификата, i: — его issuer, номер 0 — сертификат сайта. grep -A1 ' s:' оставляет из вывода эти пары строк.
Посмотри, что прислал сервер
Посмотри, какие сертификаты app.lab.test присылает в начале TLS.
nginx прислал один сертификат — тот, что лежит в файле из ssl_certificate. Сертификата Lab Issuing CA у curl нет ни в хранилище, ни в ответе сервера. Подпись сайта ему проверить нечем — отсюда unable to get local issuer certificate.
Браузер на твоём ноутбуке такой сайт иногда открывает: он помнит промежуточные сертификаты с других сайтов или скачивает недостающий сам. curl, скрипты и другие серверы так не умеют, поэтому цепочку после правки проверяют curl.