エラーの内容
GoのHTTPSリクエストでnet/httpパッケージを使用すると、以下のエラーが発生します:
Get "https://example.com": x509: certificate signed by unknown authority
サーバーのTLS証明書自体は正規かつ有効ですが、Goがその証明書を発行したCAを信頼していません。データのやり取りが始まる前にリクエストが失敗します。
原因
GoはシステムのCA(認証局)ストアに対してTLS証明書を検証します。以下の場合に検証が失敗します:
- サーバーが自己署名証明書を使用している場合 — 開発環境やステージング環境でよく見られます
- OSのトラストストアに登録されていないプライベート/内部CAが署名した証明書の場合
- 企業プロキシやVPNがTLSインスペクションを行い、独自のCAでトラフィックを再署名している場合(Zscaler、Palo Alto NGFW、Cisco Umbrellaなど)
- CA証明書がまったくインストールされていない最小構成のDockerコンテナ(
scratchやalpine)を使用している場合 - システムのCAバンドルが古くなっているか、そもそも存在しない場合
修正方法1:システムトラストストアにCA証明書を追加する
本番環境や共有ホストでは、CA証明書をOSのトラストストアに一度追加するだけで済みます。そのマシン上のすべてのGoバイナリが自動的に読み込みます — コードの変更は不要です。
Linux(Debian/Ubuntu)
# CA証明書(PEM形式)をトラストストアにコピーする
sudo cp your-ca.crt /usr/local/share/ca-certificates/your-ca.crt
sudo update-ca-certificates
Linux(RHEL/CentOS/Fedora)
sudo cp your-ca.crt /etc/pki/ca-trust/source/anchors/your-ca.crt
sudo update-ca-trust
macOS
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain your-ca.crt
証明書を追加したら、Goアプリケーションを再起動してください。システムストアはリクエストごとではなく、起動時に読み込まれます。
修正方法2:CA証明書をプログラムで読み込む
システムのトラストストアを変更できない場合は、GoのコードでCA証明書を直接読み込みます。共有サーバー、読み取り専用コンテナ、OSの変更が許可されていない環境で有効な方法です。
package main
import (
"crypto/tls"
"crypto/x509"
"fmt"
"io"
"net/http"
"os"
)
func main() {
// CA証明書ファイル(PEM形式)を読み込む
caCert, err := os.ReadFile("your-ca.crt")
if err != nil {
panic(err)
}
// システムプールに追加する
caCertPool, err := x509.SystemCertPool()
if err != nil {
// フォールバック:空のプールを作成する
caCertPool = x509.NewCertPool()
}
caCertPool.AppendCertsFromPEM(caCert)
// 拡張プールを使用するHTTPクライアントを構築する
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: caCertPool,
},
},
}
resp, err := client.Get("https://example.com")
if err != nil {
panic(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
CA証明書はバイナリと一緒に配布されます。OSの変更不要、システム全体への影響もありません。
修正方法3:DockerコンテナのCA証明書不足を解消する
最小構成のベースイメージ(scratch、alpine、distroless)にはCA証明書がまったく含まれていません。バイナリは正常にコンパイルされますが、実行時に最初のHTTPS呼び出しでクラッシュします。
オプションA — Alpineにca-certificatesをインストールする
FROM alpine:3.19
RUN apk add --no-cache ca-certificates
COPY your-app /app
ENTRYPOINT ["/app"]
オプションB — ビルダーステージからCA証明書をコピーする(scratchイメージの場合)
FROM golang:1.22 AS builder
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o app .
FROM scratch
# ビルダーからシステムCAバンドルをコピーする
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /src/app /app
ENTRYPOINT ["/app"]
修正方法4:TLS検証をスキップする(開発環境のみ)
この方法も存在します。ローカルテスト専用にしてください。本番環境でInsecureSkipVerify: trueを使用すると、中間者攻撃を行うサーバーを含むあらゆるサーバーが任意の証明書を提示でき、コードは無条件にそれを受け入れてしまいます。
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // ⚠️ 開発環境のみ
},
},
}
本番コードでこれを見つけた場合は、修正方法1または修正方法2で根本原因を解決してください。
修正方法5:企業プロキシ/VPNによるTLSインスペクション
典型的なサインとして、同じネットワーク上の同僚はエラーが発生していないのに自分だけ発生している場合があります。プロキシがTLSトラフィックを傍受し、独自のCAで再署名している可能性が高いです。ITまたはセキュリティチームにプロキシのルートCA証明書を問い合わせ、その証明書で修正方法1または修正方法2を適用してください。
サーバーが実際に提示している証明書を確認するには、次のコマンドを実行します:
openssl s_client -connect example.com:443 < /dev/null 2>&1 | openssl x509 -noout -issuer -subject
発行者にDigiCertやLet's EncryptではなくBoの会社名や*「Zscaler Root CA」*が表示されている場合、それが信頼する必要のあるプロキシ証明書です。
修正の確認
Goプログラムを再実行してください。TLSエラーが表示されなければ修正成功です。手動で確認するには、OpenSSLを直接使用します:
# 詳細出力でプログラムを実行する
go run -v your_main.go
# CAファイルに対して証明書チェーンを検証する
openssl s_client -connect example.com:443 -CAfile your-ca.crt
OpenSSLがVerify return code: 0 (ok)を返せば、チェーンは正常です。Goも受け入れます。
予防策
- 内部CAをバージョン管理に保存する:企業のCA証明書を共有リポジトリや構成管理ツール(Ansible、Chef、Puppet)に配置します。新しいホストはプロビジョニング時に自動的に取得できるため、手動作業が不要になります。
- 外部へのHTTPSリクエストを行うDockerイメージには必ず
ca-certificatesを含める:本番インシデントが発生してから不足に気づくのは避けましょう。 - CIで
InsecureSkipVerifyを禁止する:1行のgrepでマージ前に検出できます — PRパイプラインでgrep -r InsecureSkipVerify ./を実行します。 - 環境変数からCAパスを読み込む:同じバイナリが開発環境(自己署名証明書)と本番環境(パブリックCA)の両方でコードの変更なしに動作するようになります。

