Docker容器内SSL证书生成与信任问题排查
问题描述
我在Docker容器中部署了一个监听8090端口的HTTPS Web应用。创建自签名证书并添加到容器内信任根后,容器内通过curl验证证书有效,但外部浏览器及curl访问时始终显示证书不受信任。推测是容器内可解析CA根证书,但外部无法解析。以下是相关文件及验证日志:
Dockerfile内容
FROM ubuntu EXPOSE 8090 WORKDIR /app #install tools RUN apt-get -y update; apt-get -y install sudo curl wget libicu-dev apt-transport-https ca-certificates # create SSL certificate RUN openssl req -x509 -noenc -newkey rsa:4096 -sha256 -keyout /app/Certificates/test.key -out /app/Certificates/test.crt -passin "pass:password" -subj "/O=Test/CN=localhost" -addext "subjectAltName=DNS:localhost,DNS:*.localhost,IP:127.0.0.1" -days 3650 # export certificate RUN openssl pkcs12 -export -password "pass:password" -inkey /app/Certificates/test.key -in /app/Certificates/test.crt -out /app/Certificates/test.pfx # trust certificate RUN cp /app/Certificates/test.crt /usr/local/share/ca-certificates/test.crt && update-ca-certificates RUN useradd app USER app ENTRYPOINT ["./WebApplication"]
容器内验证证书(正常)
$ curl -vv https://localhost:8090 * Host localhost:8090 was resolved. * IPv6: ::1 * IPv4: 127.0.0.1 * Trying [::1]:8090... * connect to ::1 port 8090 from ::1 port 59964 failed: Connection refused * Trying 127.0.0.1:8090... * Connected to localhost (127.0.0.1) port 8090 * ALPN: curl offers h2,http/1.1 * TLSv1.3 (OUT), TLS handshake, Client hello (1): * CAfile: /etc/ssl/certs/ca-certificates.crt * CApath: /etc/ssl/certs * TLSv1.3 (IN), TLS handshake, Server hello (2): * TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8): * TLSv1.3 (IN), TLS handshake, Certificate (11): * TLSv1.3 (IN), TLS handshake, CERT verify (15): * TLSv1.3 (IN), TLS handshake, Finished (20): * TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1): * TLSv1.3 (OUT), TLS handshake, Finished (20): * SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519 / RSASSA-PSS * ALPN: server accepted h2 * Server certificate: * subject: O=Test; CN=localhost * start date: Mar 17 01:37:17 2025 GMT * expire date: Mar 15 01:37:17 2035 GMT * subjectAltName: host "localhost" matched cert's "localhost" * issuer: O=Test; CN=localhost * SSL certificate verify ok. * Certificate level 0: Public key type RSA (4096/152 Bits/secBits), signed using sha256WithRSAEncryption * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * old SSL session ID is stale, removing * using HTTP/2 * [HTTP/2] [1] OPENED stream for https://localhost:8090/ * [HTTP/2] [1] [:method: GET] * [HTTP/2] [1] [:scheme: https] * [HTTP/2] [1] [:authority: localhost:8090] * [HTTP/2] [1] [:path: /] * [HTTP/2] [1] [user-agent: curl/8.5.0] * [HTTP/2] [1] [accept: */*] > GET / HTTP/2 > Host: localhost:8090 > User-Agent: curl/8.5.0 > Accept: */* > < HTTP/2 200 < content-type: text/html < date: Mon, 17 Mar 2025 01:45:32 GMT < server: Kestrel < accept-ranges: bytes < etag: "1db9629b316d3e9" < last-modified: Sun, 16 Mar 2025 04:12:58 GMT < content-length: 4841 < x-version: 0.0.0 < <!DOCTYPE html> <html lang="en"> <!-- ... HTML CONTENTS ... --> </html> * Connection #0 to host localhost left intact
容器外验证证书(失败)
$ curl -vv https://localhost:8090 * Host localhost:8090 was resolved. * IPv6: ::1 * IPv4: 127.0.0.1 * Trying [::1]:8090... * Connected to localhost (::1) port 8090 * schannel: disabled automatic use of client certificate * ALPN: curl offers http/1.1 * schannel: SEC_E_UNTRUSTED_ROOT (0x80090325) - The certificate chain was issued by an authority that is not trusted. * closing connection #0 curl: (60) schannel: SEC_E_UNTRUSTED_ROOT (0x80090325) - The certificate chain was issued by an authority that is not trusted. More details here: https://curl.se/docs/sslcerts.html curl failed to verify the legitimacy of the server and therefore could not establish a secure connection to it. To learn more about this situation and how to fix it, please visit the webpage mentioned above.
OpenSSL证书链信息
$ openssl s_client -servername localhost -connect localhost:8090 -CApath /app/Certificates CONNECTED(00000003) depth=0 O = Test, CN = localhost verify error:num=18:self-signed certificate verify return:1 depth=0 O = Binner, CN = localhost verify return:1 --- Certificate chain 0 s:O = Test, CN = localhost i:O = Test, CN = localhost a:PKEY: rsaEncryption, 4096 (bit); sigalg: RSA-SHA256 v:NotBefore: Mar 16 15:39:02 2025 GMT; NotAfter: Mar 14 15:39:02 2035 GMT --- Server certificate -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- subject=O = Test, CN = localhost issuer=O = Test, CN = localhost --- No client certificate CA names sent Peer signing digest: SHA256 Peer signature type: RSA-PSS Server Temp Key: X25519, 253 bits --- SSL handshake has read 2194 bytes and written 391 bytes Verification error: self-signed certificate --- New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384 Server public key is 4096 bit Secure Renegotiation IS NOT supported Compression: NONE Expansion: NONE No ALPN negotiated Early data was not sent Verify return code: 18 (self-signed certificate) --- --- Post-Handshake New Session Ticket arrived: SSL-Session: Protocol : TLSv1.3 Cipher : TLS_AES_256_GCM_SHA384 Session-ID: D35F37B1E33D77323D56FA4BF418459FF7C4B752F4318F5DE0CFA15BB53FEC37 Session-ID-ctx: Resumption PSK: ... PSK identity: None PSK identity hint: None SRP username: None TLS session ticket lifetime hint: 7200 (seconds) TLS session ticket: ... Start Time: 1742174452 Timeout : 7200 (sec) Verify return code: 18 (self-signed certificate) Extended master secret: no Max Early Data: 0 ---
问题原因与解决方案
核心原因
- 容器与外部主机的信任根完全独立:你在容器内执行
update-ca-certificates只是把自签名证书添加到容器的CA信任库中,外部主机(本地curl、浏览器)的CA信任链和容器没有任何关联,根本不识别这个自签名证书。 - 证书本身是自签名的:从OpenSSL日志可以看到,证书的颁发者(issuer)和主体(subject)完全一致,属于自签名证书,不在任何公共或本地受信任的CA链中,外部必须手动信任该证书才能通过验证。
解决方案
方案1:将自签名证书导入外部主机的信任根
这是最直接的解决方法,让外部主机信任这个证书:
- 第一步:从容器中导出证书到本地主机
docker cp <你的容器ID>:/app/Certificates/test.crt ./local-test.crt - 第二步:根据操作系统添加信任:
- Windows:双击导出的
local-test.crt,选择「安装证书」→「本地计算机」→「受信任的根证书颁发机构」,完成导入。 - macOS:打开「钥匙串访问」,导入证书,找到该证书后右键选择「显示简介」,在「信任」选项卡中设置「使用此证书时」为「始终信任」。
- Linux:将证书复制到系统CA目录,更新信任链:
sudo cp local-test.crt /usr/local/share/ca-certificates/ sudo update-ca-certificates
- Windows:双击导出的
方案2:创建私有CA签发服务器证书(更规范)
如果后续有多个服务需要使用自签名证书,建议先创建自己的私有CA,再用CA签发服务器证书:
- 生成CA密钥和证书:
openssl req -x509 -newkey rsa:4096 -sha256 -days 3650 -keyout ca.key -out ca.crt -subj "/O=Test CA/CN=Test Root CA" -noenc - 生成服务器证书签名请求(CSR):
openssl req -newkey rsa:4096 -sha256 -keyout test.key -out test.csr -subj "/O=Test/CN=localhost" -addext "subjectAltName=DNS:localhost,DNS:*.localhost,IP:127.0.0.1" -noenc - 用CA签发服务器证书:
openssl x509 -req -in test.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out test.crt -days 3650 -sha256 -extfile <(printf "subjectAltName=DNS:localhost,DNS:*.localhost,IP:127.0.0.1") - 只需要把
ca.crt导入外部主机的信任根,所有由该CA签发的证书都会被信任,容器内也只需要信任ca.crt即可。
方案3:临时跳过验证(仅用于测试)
如果只是临时测试,不需要长期信任,可以用curl跳过证书验证:
curl -k https://localhost:8090
注意:浏览器无法直接跳过验证,只能临时添加安全例外,但不推荐用于生产环境。
内容的提问来源于stack exchange,提问作者Michael Brown
相关产品推荐
相关产品推荐

