Go 可用 crypto/x509 和 crypto/rsa 直接生成自签名证书,关键需正确设置 SerialNumber(big.Int)、NotBefore/NotAfter、DNSNames/IPAddresses,私钥建议2048位以上,并分别以 PEM 格式编码证书("CERTIFICATE")和 RSA 私钥("RSA PRIVATE KEY"),避免复用密钥或忽略 SAN。

用 x509.CreateCertificate 生成自签名证书,不是调用 openssl 命令
Go 自带的 crypto/x509 和 crypto/rsa 足够生成完整 PEM 格式的自签名证书和私钥,无需依赖外部工具。很多人卡在第一步:以为必须跑 openssl req -x509,其实 Go 完全能自己搞定,且更可控。
关键点在于:证书需要一对密钥(rsa.PrivateKey)、一个符合 x509.Certificate 结构的模板(含 SerialNumber、NotBefore、NotAfter、Subject、IPAddresses 或 DNSNames),以及用私钥对证书签名——注意:自签名时,签名者和被签者是同一把私钥。
-
SerialNumber必须是大整数(big.Int),不能直接写1,否则 panic:panic: x509: serial number is negative -
NotBefore建议设为time.Now().Add(-10 * time.Minute),避免因系统时间微小偏差导致浏览器报 “CERT_HAS_EXPIRED” -
DNSNames至少填[]string{"localhost"};若要支持https://127.0.0.1,必须同时加IPAddresses: []net.IP{net.ParseIP("127.0.0.1")} - 私钥长度建议 2048 位以上;1024 位在现代 TLS 握手中会被 Chrome/Firefox 拒绝
生成的证书要能被 http.Server 正确加载,PEM 编码顺序不能错
Go 的 http.Server.TLSConfig 要求 Certificates 字段是 tls.Certificate 类型,而它只接受 PEM 编码的 cert + key 二进制数据。常见错误是把证书和私钥混在一起写、或顺序颠倒、或漏了 -----BEGIN RSA PRIVATE KEY----- 头尾。
正确做法是分别编码:先用 pem.Encode 将 *x509.Certificate 写入 cert PEM,再将 *rsa.PrivateKey 写入 key PEM(注意用 rsa.PrivateKey,不是 interface{});二者不能合并成一个文件传给 tls.LoadX509KeyPair —— 它内部会按固定分隔符解析,但行为不稳定,尤其私钥是 PKCS#8 格式时容易失败。
立即学习“go语言免费学习笔记(深入)”;
- 证书 PEM 块类型必须是
"CERTIFICATE",不是"X509 CERTIFICATE"或其他变体 - 私钥 PEM 块类型推荐
"RSA PRIVATE KEY"(PKCS#1),比"PRIVATE KEY"(PKCS#8)兼容性更好,尤其在旧版 Go 或某些嵌入式 TLS 库中 - 写入文件后,可用
openssl x509 -in cert.pem -text -noout验证是否可读;若报错unable to load certificate,八成是 PEM 头尾缺失或换行符损坏(Windows CRLF 可能干扰)
开发中用 crypto/tls 启动 HTTPS 服务,绕过证书校验要明确作用域
测试时经常需要让客户端(比如 http.Client)跳过证书验证,但这不是全局开关,也不是改某一个配置项就能生效。核心是替换 http.Client.Transport.TLSClientConfig 的 InsecureSkipVerify 字段。
重点:这个字段只影响该 client 实例发出的请求;如果复用默认 client(http.DefaultClient),改它的 Transport 会影响所有后续 HTTP 请求,包括你可能没意识到的第三方库调用 —— 这是隐蔽的副作用。
- 不要直接修改
http.DefaultTransport.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true,这会污染全局状态 - 每次测试应新建
&http.Client{Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}}} - 更安全的做法是只对特定域名跳过验证,用
tls.Config.VerifyPeerCertificate自定义逻辑,但开发阶段没必要过度设计 - 若用
curl -k测试服务端,确认服务已启动且端口未被占用;常见错误是listen tcp :443: bind: permission denied(非 root 绑定特权端口),改用:8443即可
证书有效期、SAN 和密钥重用是本地测试最容易忽略的三个点
开发中反复生成证书时,常假设“反正只跑 localhost,随便填”,结果遇到奇怪问题:浏览器突然不信任、iOS 模拟器拒绝连接、或 gRPC 客户端报 transport: authentication handshake failed。根因往往就在这三处。
- 证书有效期别设太长(如 10 年):某些平台(如 Android 7+)会拒绝超过 39 个月的证书;建议设为 365 天,够用又安全
-
Subject.CommonName已被主流浏览器弃用,仅靠它无法通过验证;必须显式设置DNSNames或IPAddresses,且内容需与实际访问地址完全一致(localhost≠127.0.0.1) - 不要复用同一个私钥生成多张证书:虽然技术上可行,但一旦私钥泄露,所有历史证书都失效;开发脚本里应每次生成新密钥对,哪怕只是
go run gen.go临时用
这些点不难,但组合起来就容易漏掉一两个,然后花半小时查为什么 curl 显示证书 OK,浏览器却标红锁图标。


















