Go 语言 ACME 客户端库 — 自动化 TLS 证书管理
CertMagic 是 Go 语言中最成熟、最健壮的 ACME(自动化证书管理环境)客户端集成库。通过 CertMagic,只需一行代码即可为 Go 应用启用 HTTPS 服务,无需手动管理证书。
不像这样:
// 明文 HTTP
http.ListenAndServe(":80", mux)
使用 CertMagic:
// 加密 HTTPS,自动 HTTP->HTTPS 重定向
certmagic.HTTPS([]string{"example.com"}, mux)
这一行代码即可通过 HTTPS 提供 HTTP 路由服务,自动获取和续期证书、staple OCSP 响应。只要域名指向服务器,CertMagic 就会保持连接安全。
UseFirstIssuer 和 UseFirstRandomIssuer重要:使用本库前,你的域名必须已指向服务器(A/AAAA 记录),除非使用 DNS 挑战!
go get github.com/caddyserver/certmagic
err := certmagic.HTTPS([]string{"example.com", "www.example.com"}, mux)
if err != nil {
// 处理错误
}
ln, err := certmagic.Listen([]string{"example.com"})
if err != nil {
// 处理错误
}
tlsConfig, err := certmagic.TLS([]string{"example.com"})
if err != nil {
// 处理错误
}
// 如需自定义 NextProtos,例如支持 h2:
tlsConfig.NextProtos = append([]string{"h2", "http/1.1"}, tlsConfig.NextProtos...)
// 推荐:设置默认 ACME 配置
certmagic.DefaultACME.Agreed = true // 同意 CA 服务条款
certmagic.DefaultACME.Email = "you@yours.com" // 提供邮箱
certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA // 使用 staging 环境(开发时)
// 生产环境使用:
// certmagic.DefaultACME.CA = certmagic.LetsEncryptProductionCA
certmagic.Config 是配置证书管理器的核心结构。空配置无效,需使用 New() 或 NewDefault() 获取有效配置。
// 使用默认配置
cfg := certmagic.NewDefault()
// 自定义配置
cache := certmagic.NewCache(certmagic.CacheOptions{
GetConfigForCert: func(cert certmagic.Certificate) (*certmagic.Config, error) {
return certmagic.New(cache, certmagic.Config{
// 自定义配置
}), nil
},
})
magic := certmagic.New(cache, certmagic.Config{
// 自定义配置
})
Issuer 是证书颁发机构的抽象,支持多种实现:
// 创建 ACME Issuer
myACME := certmagic.NewACMEIssuer(magic, certmagic.ACMEIssuer{
CA: certmagic.LetsEncryptStagingCA,
Email: "you@yours.com",
Agreed: true,
})
magic.Issuers = []certmagic.Issuer{myACME}
Cache 是内存中的证书缓存,索引证书以快速访问。
cache := certmagic.NewCache(certmagic.CacheOptions{ GetConfigForCert: myConfigGetter, })
Storage 是持久化存储接口,默认使用文件系统($HOME/.local/share/certmagic)。
certmagic.Default.Storage = &myCustomStorage{} // 实现 certmagic.Storage 接口
需要 80 端口。通过在特殊端点提供特定 HTTP 响应来验证域名控制权。
// 使用标准库
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
fmt.Fprintf(w, "我的 HTTPS 网站!")
})
http.ListenAndServe(":80", myACME.HTTPChallengeHandler(mux))
需要 443 端口。使用 TLS 扩展(ALPN)提供特殊值进行验证。
// 使用 magic.TLSConfig() 配置 TLS 监听器
tlsConfig := magic.TLSConfig()
最灵活的挑战方式,无需服务器公网访问,也是 Let's Encrypt 签发通配符证书的唯一方式。
import "github.com/libdns/cloudflare"
certmagic.DefaultACME.DNS01Solver = &certmagic.DNS01Solver{
DNSManager: certmagic.DNSManager{
DNSProvider: &cloudflare.Provider{
APIToken: "topsecret",
},
},
}
不知道所有域名或不想预先管理所有证书时,On-Demand TLS 非常有用。 CertMagic 会在 TLS 握手时读取 SNI,按策略:
生产环境建议始终显式配置策略,避免任意 SNI 触发证书申请。
certmagic.Default.OnDemand = certmagic.OnDemandPolicy(nil)
如需同时接入外部 Manager,可直接作为变参传入:
certmagic.Default.OnDemand = certmagic.OnDemandPolicy(nil, myManager)
certmagic.Default.OnDemand = certmagic.OnDemandPolicy(
func(ctx context.Context, serverName string) (certmagic.DomainPolicy, error) {
if serverName != "example.com" {
return certmagic.DomainPolicy{}, fmt.Errorf("not allowed")
}
return certmagic.DomainPolicy{}, nil
},
)
默认 DomainPolicy{} 的行为是:
api.example.com*.example.comapi.example.comcertmagic.Default.OnDemand = certmagic.OnDemandPolicy(
func(ctx context.Context, serverName string) (certmagic.DomainPolicy, error) {
tenant, err := domainStore.LookupEnabledTenant(ctx, serverName)
if err != nil {
return certmagic.DomainPolicy{}, err
}
if tenant == nil {
return certmagic.DomainPolicy{}, fmt.Errorf("domain not allowed")
}
return certmagic.DomainPolicy{
BaseDomain: tenant.BaseDomain,
PreferWildcard: tenant.WildcardEnabled,
ObtainWildcard: tenant.WildcardEnabled && tenant.DNS01Ready,
DisableExactObtain: tenant.WildcardEnabled && tenant.DNS01Ready,
}, nil
},
)
这样上层只需返回一份 DomainPolicy,库内会自动完成:
DomainPolicy 支持以下字段:
| 字段 | 类型 | 说明 |
|---|---|---|
ExactSubject | string | 覆盖默认 exact 主题名;留空时使用归一化后的请求 SNI |
BaseDomain | string | 指定 wildcard 的基础域名,例如 "tenant.example.com";留空时自动推导单层 wildcard |
WildcardSubject | string | 显式指定 wildcard 主题名;优先级高于 BaseDomain |
PreferWildcard | bool | 控制加载顺序是否优先尝试 wildcard |
DisableWildcardLoad | bool | 禁止在加载阶段尝试 wildcard 证书 |
ObtainWildcard | bool | 未命中现有证书时申请 wildcard 证书;启用前应确保 issuer 已具备 DNS-01 能力 |
DisableExactObtain | bool | 禁止申请 exact 证书;若同时未启用 ObtainWildcard,则该策略仅允许加载已有证书 |
零值策略等价于:
默认策略只会尝试单层 wildcard:
api.example.com → *.example.comfoo.bar.example.com → *.bar.example.com不会默认尝试:
*.*.example.com*.example.com 覆盖 example.com*.example.com 覆盖 foo.bar.example.com如果你要现场申请 wildcard,必须同时满足:
否则建议仅复用现有 wildcard,申请时仍走 exact 证书。
CertMagic 支持管理客户端证书用于 mTLS:
chains, err := cfg.ClientCredentials(ctx, []string{"example.com"})
if err != nil {
// 处理错误
}
CertMagic 在重要事件发生时发出事件。设置 Config.OnEvent 来订阅:
cfg.OnEvent = func(ctx context.Context, event string, data map[string]any) error {
switch event {
case "cert_obtained":
// 证书获取成功
case "cert_failed":
// 证书获取失败
case "tls_get_certificate":
// TLS 握手获取证书阶段
}
return nil
}
事件类型:
cached_unmanaged_cert:缓存了非托管证书cert_obtaining:即将获取证书cert_obtained:成功获取证书cert_failed:获取证书失败tls_get_certificate:TLS 握手 GetCertificate 阶段cert_ocsp_revoked:证书 OCSP 状态为已撤销CertMagic 内置 Logger 接口,支持自定义日志实现:
// 使用标准库 slog
logger := certmagic.NewLogger(slog.New(slog.NewJSONHandler(os.Stdout, nil)))
certmagic.SetLogger(logger)
// 使用默认文本日志
certmagic.SetLogger(certmagic.NewTextLogger(os.Stdout, slog.LevelInfo))
// 丢弃所有日志
certmagic.SetLogger(certmagic.NopLogger())
默认存储到 $HOME/.local/share/certmagic。
实现 certmagic.Storage 接口:
type Storage interface {
Locker
Store(ctx context.Context, key string, value []byte) error
Load(ctx context.Context, key string) ([]byte, error)
Delete(ctx context.Context, key string) error
Exists(ctx context.Context, key string) bool
List(ctx context.Context, prefix string, recursive bool) ([]string, error)
Stat(ctx context.Context, key string) (KeyInfo, error)
}
CertMagic 非常适合在负载均衡器后或集群环境中运行。确保所有实例使用相同的 Storage。
// 所有实例使用相同存储配置
certmagic.Default.Storage = mySharedStorage
Let's Encrypt 生产环境有严格的速率限制。开发时使用 staging 环境:
certmagic.DefaultACME.CA = certmagic.LetsEncryptStagingCA
可以。调用以下方法将证书添加到缓存:
CacheUnmanagedCertificatePEMBytes()CacheUnmanagedCertificatePEMFile()CacheUnmanagedTLSCertificate()注意:非托管证书不会自动续期。
Linux 上可使用 setcap 授予绑定低位端口的权限:
sudo setcap cap_net_bind_service=+ep /path/to/your/binary
支持。CertMagic 支持 ACME IP 证书(RFC 8738),但需 CA 支持。当前 Let's Encrypt 和 Google Trust Services 支持。
CertMagic 是 Caddy 核心 TLS 自动化代码提取出的库。底层 ACME 客户端实现基于 ACMEz。
CertMagic 代码最初是 Caddy 的一部分,早在 2015 年 Let's Encrypt 公开测试前就已存在。
多年来,Caddy 的 TLS 自动化技术已被广泛采用,在生产环境中经过测试,保护了数百万网站和数万亿连接。
欢迎贡献!请参阅贡献指南。
CertMagic 由 Matthew Holt 开发,采用 Apache 2.0 许可证。