TL;DR
証明書に Common Name (CN) フィールドしかなく、Subject Alternative Name (SAN) 拡張がありません。Go 1.15 からSANが必須になりました。SANを含めて証明書を再生成すれば解決します。
エラー内容
x509: certificate relies on legacy Common Name field, use SANs instead
このエラーは、GoアプリがTLSクライアントとして、SAN拡張のない証明書を持つサーバーに接続したときに発生します。Go 1.15からRFC 2818が厳格に適用されるようになり、ホスト名の検証はCNではなくSANを通じて行う必要があります。ブラウザはずっと前にCNのみのサポートを廃止しており、Go 1.15でそれに追随しました。
よく見られるケース:
- 古い
openssl reqワンライナーで生成した自己署名証明書を使う内部サービス -extフラグなしで生成された開発用証明書- SAN要件以前の古いレガシー企業PKI
- CNのみを設定していた2018年以前の古いツール
根本原因
古いTLSクライアントにはフォールバックがありました。証明書にSANがない場合、代わりに Common Name に対してホスト名を照合していました。Go 1.15ではこのフォールバックが完全に削除されました。これ以降、証明書は接続先のホストに一致する Subject Alternative Name のDNSエントリ(またはIPアドレス)を持つ必要があります。
次のコマンドを実行して、証明書にSANがないことを確認します:
# ライブサーバーに対して確認する
openssl s_client -connect yourhost:443 2>/dev/null | openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
# またはローカルの証明書ファイルを検査する
openssl x509 -in server.crt -noout -text | grep -A1 "Subject Alternative Name"
出力が空(DNS: や IP Address: の行がない)であれば問題が確認できます。
修正1 — SANを含めて証明書を再生成する(推奨)
再生成が唯一の根本的な修正方法です。以下に3つの方法を紹介します。
オプションA:設定ファイルを使ったopenssl
san.cnf を作成します:
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req
[dn]
CN = yourhost.internal
[v3_req]
subjectAltName = @alt_names
[alt_names]
DNS.1 = yourhost.internal
DNS.2 = localhost
IP.1 = 127.0.0.1
次に鍵と証明書を生成します:
openssl req -x509 -nodes -days 825 \
-newkey rsa:2048 \
-keyout server.key \
-out server.crt \
-config san.cnf \
-extensions v3_req
オプションB:mkcert(ローカル開発に最速)
mkcert はローカルCAをセットアップし、システムのトラストストアに追加して、適切なSANを組み込みます。設定ファイルは不要です:
brew install mkcert # macOS
# または: apt install mkcert # Debian/Ubuntu
mkcert -install
mkcert yourhost.internal localhost 127.0.0.1
yourhost.internal+2.pem と yourhost.internal+2-key.pem が生成され、OSとブラウザで最初から信頼されます。
オプションC:GoのcryptO/x509でプログラム的に生成する
package main
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"net"
"os"
"time"
)
func main() {
key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
template := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "localhost"},
NotBefore: time.Now(),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
// SANs — Goが要求する項目
DNSNames: []string{"localhost", "yourhost.internal"},
IPAddresses: []net.IP{net.ParseIP("127.0.0.1")},
}
certDER, _ := x509.CreateCertificate(rand.Reader, template, template, &key.PublicKey, key)
certFile, _ := os.Create("server.crt")
pem.Encode(certFile, &pem.Block{Type: "CERTIFICATE", Bytes: certDER})
certFile.Close()
keyDER, _ := x509.MarshalECPrivateKey(key)
keyFile, _ := os.Create("server.key")
pem.Encode(keyFile, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keyDER})
keyFile.Close()
}
修正2 — 一時的な回避策(Go 1.15〜1.16のみ)
証明書の更新を待っている間の緊急対応として、GODEBUG フラグで古いCNフォールバックを再有効化できます。ただし、これは短期的な対応にすぎません:
GODEBUG=x509ignoreCN=0 go run main.go
またはコードで、TLS呼び出しの前に設定します:
import "os"
os.Setenv("GODEBUG", "x509ignoreCN=0")
重要な制限: x509ignoreCN=0 はGo 1.17で削除されました。1.17以降では何も効果がありません。証明書を修正してください。
修正3 — 検証を完全にスキップする(テスト専用)
両端を自分で管理している自動テストや内部ツール向け:
import (
"crypto/tls"
"net/http"
)
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // 本番環境では絶対に使わない
},
},
}
InsecureSkipVerify: true はホスト名・有効期限・信頼チェーンを含むすべての証明書検証を無効化します。ローカルテストでは問題ありませんが、それ以外の環境ではセキュリティホールになります。
修正の確認
再生成後、SANが証明書に正しく含まれているか確認します:
openssl x509 -in server.crt -noout -text | grep -A2 "Subject Alternative Name"
期待される出力:
X509v3 Subject Alternative Name:
DNS:yourhost.internal, DNS:localhost, IP Address:127.0.0.1
Goプログラムを実行してエラーが消えているか確認します。簡単なライブテストには次を使用します:
curl --cacert server.crt https://yourhost.internal:8443/health
200 OK が返ればTLSハンドシェイクが正常に完了したことを意味します。
まとめ
- Go 1.15以降ではSANが必須です。CNのみの証明書は一切受け付けられません。
- 設定ファイルを使った
openssl、mkcert、またはGoのcrypto/x509で再生成してください。 GODEBUG=x509ignoreCN=0はGo 1.15〜1.16のみで一時的に有効です。InsecureSkipVerifyはテスト用であり、本番環境では使用しないでください。

