TL;DR
Chứng chỉ của bạn chỉ có trường Common Name (CN) — không có extension Subject Alternative Name (SAN). Go 1.15 đã bắt buộc phải có SAN. Hãy tạo lại chứng chỉ với SAN là xong.
Lỗi gặp phải
x509: certificate relies on legacy Common Name field, use SANs instead
Lỗi này xảy ra khi ứng dụng Go của bạn — đóng vai trò TLS client — kết nối đến một server có chứng chỉ không có extension SAN. Go 1.15 bắt đầu thực thi RFC 2818 một cách nghiêm ngặt: việc xác minh hostname phải thông qua SAN, không phải CN. Các trình duyệt đã bỏ hỗ trợ chứng chỉ chỉ có CN từ nhiều năm trước; Go bắt kịp trong phiên bản 1.15.
Những trường hợp thường gặp lỗi này:
- Các dịch vụ nội bộ dùng self-signed cert tạo từ lệnh
openssl reqmột dòng kiểu cũ - Chứng chỉ dev được tạo mà không có flag
-ext - PKI nội bộ doanh nghiệp cũ, tạo trước khi có yêu cầu SAN
- Bất kỳ công cụ nào có từ trước ~2018 mà chỉ điền CN
Nguyên nhân gốc rễ
Các TLS client cũ có cơ chế fallback: nếu chứng chỉ không có SAN, sẽ khớp hostname với Common Name. Go 1.15 đã loại bỏ hoàn toàn cơ chế fallback đó. Giờ đây chứng chỉ phải có entry DNS Subject Alternative Name (hoặc địa chỉ IP) khớp với host bạn đang kết nối đến.
Chạy lệnh sau để xác nhận chứng chỉ bị thiếu SAN:
# Kiểm tra với server đang chạy
openssl s_client -connect yourhost:443 2>/dev/null | openssl x509 -noout -text | grep -A1 "Subject Alternative Name"
# Hoặc kiểm tra file cert ở local
openssl x509 -in server.crt -noout -text | grep -A1 "Subject Alternative Name"
Nếu không có output — không có dòng DNS: hay IP Address: — thì đã xác nhận được vấn đề.
Cách sửa 1 — Tạo lại chứng chỉ với SAN (khuyến nghị)
Tạo lại chứng chỉ là cách sửa duy nhất thực sự hiệu quả. Dưới đây là ba cách để làm điều đó.
Cách A: openssl với file cấu hình
Tạo file 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
Sau đó tạo key và cert:
openssl req -x509 -nodes -days 825 \
-newkey rsa:2048 \
-keyout server.key \
-out server.crt \
-config san.cnf \
-extensions v3_req
Cách B: mkcert (nhanh nhất cho môi trường dev local)
mkcert thiết lập một CA local, tự động inject vào trust store của hệ thống, và tích hợp sẵn các SAN đúng — không cần file cấu hình:
brew install mkcert # macOS
# hoặc: apt install mkcert # Debian/Ubuntu
mkcert -install
mkcert yourhost.internal localhost 127.0.0.1
Bạn sẽ nhận được yourhost.internal+2.pem và yourhost.internal+2-key.pem, được hệ điều hành và trình duyệt tin tưởng ngay mà không cần cấu hình thêm.
Cách C: Tạo bằng code với Go's 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 — đây là điều Go yêu cầu
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()
}
Cách sửa 2 — Giải pháp tạm thời (chỉ dùng cho Go 1.15–1.16)
Đang phải chờ gia hạn chứng chỉ? Có một lối thoát tạm thời — nhưng chỉ dùng được trong thời gian ngắn. Đặt flag GODEBUG để bật lại cơ chế fallback CN cũ:
GODEBUG=x509ignoreCN=0 go run main.go
Hoặc trong code, trước bất kỳ lệnh gọi TLS nào:
import "os"
os.Setenv("GODEBUG", "x509ignoreCN=0")
Giới hạn cứng: x509ignoreCN=0 đã bị loại bỏ trong Go 1.17. Không có tác dụng gì trên phiên bản 1.17 trở lên. Hãy sửa chứng chỉ đi.
Cách sửa 3 — Bỏ qua xác minh hoàn toàn (chỉ dùng cho test)
Dành cho các automated test hoặc công cụ nội bộ mà bạn kiểm soát cả hai đầu:
import (
"crypto/tls"
"net/http"
)
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // không bao giờ dùng trên production
},
},
}
InsecureSkipVerify: true bỏ qua toàn bộ quá trình xác thực chứng chỉ — hostname, hạn sử dụng, chuỗi tin cậy. Chấp nhận được cho test ở local. Nhưng là lỗ hổng bảo mật ở mọi nơi khác.
Xác minh sau khi sửa
Sau khi tạo lại chứng chỉ, kiểm tra xem SAN đã thực sự có trong cert chưa:
openssl x509 -in server.crt -noout -text | grep -A2 "Subject Alternative Name"
Kết quả mong đợi:
X509v3 Subject Alternative Name:
DNS:yourhost.internal, DNS:localhost, IP Address:127.0.0.1
Chạy lại chương trình Go — lỗi sẽ biến mất. Để test nhanh trực tiếp:
curl --cacert server.crt https://yourhost.internal:8443/health
Nhận được 200 OK nghĩa là TLS handshake đã hoàn tất thành công.
Tóm tắt
- Go 1.15 trở lên yêu cầu SAN — chứng chỉ chỉ có CN sẽ bị từ chối hoàn toàn.
- Tạo lại chứng chỉ bằng
opensslvới file cấu hình,mkcert, hoặccrypto/x509của Go. GODEBUG=x509ignoreCN=0chỉ là giải pháp tạm thời cho Go 1.15–1.16.InsecureSkipVerifychỉ dùng trong test, không dùng trên production.

