企业内网或开发测试环境往往需要HTTPS加密,但直接使用OpenSSL生成的自签名证书会在浏览器中显示不可信任警告。更专业的做法是创建一套私有CA,由该CA给Nginx等服务器签发证书,客户端只需信任这一张根证书即可。cfssl是Cloudflare开源的PKI/TLS工具,通过JSON配置文件定义证书请求和签名策略,操作比OpenSSL命令更清晰,适合快速搭建内部证书体系。

一、安装cfssl并初始化自建CA
cfssl包含三个常用二进制:cfssl、cfssljson和cfssl-certinfo。可以从GitHub Releases获取对应平台版本,也可以使用包管理器安装。下载后赋予执行权限并放入/usr/local/bin目录。
# 下载二进制文件 wget https://github.com/cloudflare/cfssl/releases/download/v1.6.4/cfssl_1.6.4_linux_amd64 -O /usr/local/bin/cfssl wget https://github.com/cloudflare/cfssl/releases/download/v1.6.4/cfssljson_1.6.4_linux_amd64 -O /usr/local/bin/cfssljson chmod +x /usr/local/bin/cfssl /usr/local/bin/cfssljson
安装完成后创建一个工作目录,例如/etc/cfssl,并在其中准备CA请求文件ca-csr.json。该文件定义根证书的CN、组织、加密算法和有效期。
{
"CN": "Internal Root CA",
"key": {
"algo": "rsa",
"size": 2048
},
"names": [
{
"O": "Example Corp",
"OU": "Security"
}
],
"ca": {
"expiry": "87600h"
}
}
其中ca.expiry为87600小时,约10年。执行cfssl gencert -initca命令生成自签名根证书。
cfssl gencert -initca ca-csr.json | cfssljson -bare ca
该命令输出ca.pem和ca-key.pem。ca.pem是根证书公钥,需要分发给客户端;ca-key.pem是私钥,必须严格保护。建议将私钥权限设置为600,并备份到离线介质。
生成CA后还应创建签名配置文件ca-config.json,它规定了证书用途和有效期策略。cfssl在签发服务器证书时会读取该文件中的profiles。
{
"signing": {
"default": {
"expiry": "8760h"
},
"profiles": {
"server": {
"expiry": "8760h",
"usages": ["signing", "key encipherment", "server auth"]
},
"client": {
"expiry": "8760h",
"usages": ["signing", "key encipherment", "client auth"]
}
}
}
}
server profile用于服务器证书,client profile用于客户端证书。usages中的server auth和client auth是扩展密钥用途,Nginx使用server auth即可。
二、为Nginx签发服务器证书
有了CA后,需要为Nginx域名准备CSR配置文件。假设内网域名是nginx.internal.ippipp.com,IP地址为192.168.0.1,可以在hosts字段中同时写入DNS和IP,确保证书在不同访问方式下均有效。
{
"CN": "nginx.internal.ippipp.com",
"key": {
"algo": "rsa",
"size": 2048
},
"hosts": [
"nginx.internal.ippipp.com",
"192.168.0.1",
"127.0.0.1"
]
}
使用cfssl和ca-config.json中的server profile签发证书,命令如下。
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -profile=server server-csr.json | cfssljson -bare nginx
执行后会得到nginx.pem和nginx-key.pem。前者包含服务器证书链,后者是私钥。可以通过cfssl-certinfo验证证书信息。
cfssl-certinfo -cert nginx.pem
输出中会显示Subject、Issuer、SAN、有效期等。确认Issuer为自建CA的CN,即Internal Root CA,并且DNS和IP都在SAN列表里。
接下来配置Nginx。将nginx.pem和nginx-key.pem复制到/etc/nginx/ssl目录,然后修改server块。注意ssl_certificate指向包含证书链的文件,ssl_certificate_key指向私钥。
server {
listen 443 ssl;
server_name nginx.internal.ippipp.com;
ssl_certificate /etc/nginx/ssl/nginx.pem;
ssl_certificate_key /etc/nginx/ssl/nginx-key.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
}
}
检查配置并重载Nginx。
nginx -t systemctl reload nginx
此时如果客户端尚未信任根证书,访问https://nginx.internal.ippipp.com会看到不安全提示,但证书本身已经由私有CA签发。下一步要在客户端导入ca.pem。
三、客户端信任与证书生命周期管理
要让浏览器不再报警,需要把自建CA的根证书添加到客户端的信任存储中。Linux系统可以把ca.pem复制到/usr/local/share/ca-certificates/,然后执行update-ca-certificates。Windows可以双击ca.pem导入受信任的根证书颁发机构,macOS使用钥匙串访问导入并设置为始终信任。
cp ca.pem /usr/local/share/ca-certificates/internal-root-ca.crt update-ca-certificates
完成导入后重新打开浏览器,访问Nginx服务应当显示正常的锁形图标。如果仍提示证书无效,需要检查证书的SAN是否包含访问用的域名或IP,以及系统时间是否在证书有效期内。
私有CA的证书到期后需要续期。cfssl没有自动续期机制,通常做法是重新生成证书并替换。可以编写脚本调用cfssl gencert,把生成的新证书推送到Nginx并自动reload。根证书到期则需要重新生成CA并重新分发,因此建议把根证书有效期设长,同时将私钥加密存储。
多域名或通配符证书可以通过修改hosts字段实现。例如添加*.internal.ippipp.com可以覆盖所有一级子域名,但要注意通配符不能匹配多级子域名。如果服务数量很多,还可以签发中间CA,由根CA离线保存,中间CA负责日常签发,降低根私钥暴露风险。
四、常见错误与排查思路
证书签发后Nginx启动失败,常见原因是私钥与证书不匹配。可以用openssl x509 -noout -modulus -in nginx.pem和openssl rsa -noout -modulus -in nginx-key.pem对比md5值,二者必须一致。另一个原因是ssl_certificate路径错误或权限不可读,Nginx worker进程需要读取私钥文件,建议将所属用户设置为root并给予600权限。
openssl x509 -noout -modulus -in nginx.pem | openssl md5 openssl rsa -noout -modulus -in nginx-key.pem | openssl md5
如果客户端报告证书颁发者未知,说明客户端未正确信任根证书,或者服务器只发送了叶子证书而没有中间证书。cfssl直接签发的服务器证书中通常已包含CA证书链,但如果使用中间CA则必须拼接完整链。可以使用cat nginx.pem ca.pem > chain.pem命令合并后再配置。
证书SAN不匹配时浏览器会提示存在风险,此时应回到server-csr.json补充全部域名和IP,重新签发。值得注意的是,CN字段已逐渐被浏览器忽略,SAN才是主要校验对象。
最后,自建CA虽然灵活,但私钥管理责任完全在自身。如果根私钥泄露,攻击者可以签发任意证书,必须立即吊销并更换CA。因此线上环境应结合硬件安全模块或加密文件系统存储私钥,并限制服务器物理和网络访问。