1. 为什么要在内网自建一套CA
我最早接触openEuler下的CA部署,是被一个很现实的场景逼出来的:公司内部一套Web管理系统,部署了几年,浏览器每次访问都弹“您的连接不是私密连接”,用户那边天天打电话过来问是不是网站被黑了。仔细一看,问题出在系统自带的CA证书版本太旧,系统又没有持续更新,导致系统CA补丁也不更新,新装的浏览器和设备根本没把这个链路上的根证书放进信任库里。
与其到处找免费web服务器证书凑合,不如自己在内网搭建一套CA根证书服务器,把证书签发、分发、续期都握在自己手里。这套思路解决的不只是“浏览器报错”这一个表面问题,而是把企业内网对“谁是可信的、证书从哪里来、到期怎么续”这三个核心问题,一次性规范化了。
自建CA适合三类人:一是内网应用比较多、不想每台设备单独导证书的管理员;二是对证书私钥和控制权有要求的等保或合规场景;三就是像我这样,被浏览器报错烦到忍无可忍,决定一劳永逸解决的人。整个过程不复杂,但涉及的概念和细节不少,这篇就按我实际落地的顺序,从规划、搭建、签发到排错一步步讲清楚。
2. 动手之前的规划和设计
2.1 系统版本与网络架构确认
我用的环境是openEuler 22.03 LTS,这个版本在国内服务器圈子用得挺多,稳定性没什么问题。但这套流程不挑版本,openEuler 20.03、22.03、甚至openEuler 24.03 LTS都能跑,核心依赖的OpenSSL版本差异也不大。建议动手前先用一条命令确认环境:
cat /etc/openEuler-release openssl version我见过不少人在这一步翻车——系统是openEuler没错,但OpenSSL是1.1.1的老版本,后面生成私钥时有些参数不支持。如果openssl version显示的是1.1.1系列,建议先用dnf升级一下:dnf update openssl。实测下来,OpenSSL 3.0以上的版本用起来最顺手,因为部分旧参数被新语法替代了,照着新版本的写法写配置文件,一次成功的概率更高。
网络架构上要提前想清楚:CA服务器不一定要给外网访问,但它至少要能接收到Web服务器提交过来的证书请求。我的建议是CA服务器放内网一个固定IP,关闭所有不必要的对外端口,只留SSH和必要的文件传输通道。毕竟是整个内网信任体系的根,相当于公司大门的总钥匙,暴露面越小越好。
2.2 参数规划:有效期、目录结构与文件命名
开始执行前,先把规划表做出来。CA这套体系最忌讳“装到一半才发现有效期设短了”或者“签发完才发现密钥长度不够”,返工成本比一次性规划到位高得多。
| 规划项 | 建议值 | 说明 |
|---|---|---|
| 根CA私钥算法 | RSA 4096 | 兼容性最好,强度足够 |
| 根CA证书有效期 | 3650天(10年) | Web证书续约时,根证书不需要频繁换 |
| Web服务器证书算法 | RSA 2048 | 性能与安全平衡 |
| Web服务器证书有效期 | 825天左右 | 浏览器对超过13个月的证书有兼容性问题 |
| 私钥权限 | 600,属主root | 私钥泄露等于信任体系崩塌 |
| 数据库目录 | /root/ca | 根CA的完整工作目录 |
这里要解释一个关键选择:为什么根CA证书有效期设10年,而Web证书只设825天左右。根证书一旦签发,所有依赖它的设备都要信任它。如果根证书有效期太短,比如两三年,你会发现Web证书还没到期,根证书先到期了,整个信任链断裂,所有客户端的弹窗问题卷土重来。而Web证书恰好相反,Apple和Google对超过398天(约13个月)的证书已经开始不信任,为了最高兼容性,825天是保守且稳妥的选择。
目录结构直接用OpenSSL官方文档推荐的经典方案:
mkdir -p /root/ca/{certs,crl,newcerts,private} touch /root/ca/index.txt echo 1000 > /root/ca/serial echo 1000 > /root/ca/crlnumber chmod 700 /root/ca/private其中index.txt是CA的证书签发数据库,每签发一张证书就会追加一条记录;serial文件是证书序列号来源,每次签发自动递增;crlnumber用于吊销列表的编号管理。这四个文件是整个CA的“账本”,缺一不可。我把它们统一放在/root/ca下,后面所有操作都在这个目录里,维护起来一目了然。
3. 根CA的初始化搭建(核心实操)
3.1 配置OpenSSL主配置文件
OpenSSL默认的配置文件在/etc/pki/tls/openssl.cnf,但我不建议直接改系统文件,而是复制一份到/root/ca/openssl.cnf,只改我们需要的部分。理由很简单:系统文件是全局配置,动了它会影响这台机器上其他所有用到SSL的地方;而CA专用的配置独立出来,后面要调整、备份、迁移都方便。
配置文件的[ ca ]和[ CA_default ]段是核心,我直接给出实测可用的配置片段:
[ ca ] default_ca = CA_default [ CA_default ] dir = /root/ca database = $dir/index.txt new_certs_dir = $dir/newcerts certificate = $dir/cacert.pem serial = $dir/serial crlnumber = $dir/crlnumber crl = $dir/crl.pem private_key = $dir/private/cakey.pem RANDFILE = $dir/private/.rand default_md = sha256 default_days = 825 default_crl_days= 365 policy = policy_loose unique_subject = no [ policy_loose ] countryName = optional stateOrProvinceName = optional localityName = optional organizationName = optional organizationalUnitName = optional commonName = supplied emailAddress = optional这段配置里有个容易踩坑的点:unique_subject = no。默认值是yes,意味着如果两张证书的DN信息完全相同,CA会拒绝签发。实际场景中,同一个域名过了一年后再次申请证书,DN信息必然一模一样,不关掉这个选项,第二次签发会直接报错“File exists”,那时候再回头找原因就很浪费时间。
策略段用policy_loose而不是OpenSSL默认的policy_match,也是基于实操经验。policy_match严格要求申请证书的DN必须和根CA的DN完全匹配,这在内网场景下太苛刻了。内网Web服务器五花八门,有的单位信息随便填,用宽松策略只是要求commonName必填,其他字段允许为空,签发成功率大幅提升,安全性上也够用。
3.2 生成根CA私钥与自签名根证书
配置就位后,开始生成根CA自身的密钥和证书。命令分两步走:
# 第一步:生成根CA私钥 openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 \ -aes-256-cbc -out /root/ca/private/cakey.pem # 第二步:生成自签名根证书 openssl req -x509 -new -sha256 -days 3650 \ -key /root/ca/private/cakey.pem \ -out /root/ca/cacert.pem \ -subj "/C=CN/O=YourCompany/CN=YourCompany Internal Root CA"第一次执行时,openssl会要求输入私钥的口令(passphrase),这个口令相当于CA的“开机密码”,每次用私钥签发证书都要输入,务必记牢并妥善保管。我见过有人图省事不加-aes-256-cbc参数,直接生成无口令私钥,这等于把保险柜的门敞开着,极其危险,内网环境也不要这么干。
-subj参数里的CN(Common Name)是根证书的标识,建议用“公司名 + Internal Root CA”这种格式。为什么这个命名很重要?因为后面客户端安装根证书时,用户会看到这个名称,一个清晰规范的名称能减少“这是哪来的证书”的疑问,也方便在证书库里检索和管理。
3.3 验证根证书与准备分发副本
生成完不能急着用,先自己体检一遍。我每次都要跑这条命令确认证书的基本信息:
openssl x509 -in /root/ca/cacert.pem -noout -text | grep -E "Subject:|Issuer:|Not Before|Not After"重点看三处:Subject和Issuer是否一致(自签名证书两者相同,否则就是生成过程出了问题);有效期起止是否和预期相符;签名算法是否为sha256WithRSAEncryption。这一步发现问题越早,返工成本越低,等全部部署完才发现根证书有效期算错了,所有客户端都得重新导一遍,那才是真的欲哭无泪。
根证书验证无误后,准备分发用的副本,这一步有个实用性极强的细节:把PEM格式的根证书转换为CRT格式并改名,让Windows、Linux、macOS都能直接识别安装:
cp /root/ca/cacert.pem /root/ca/cacert.crt然后把这个cacert.crt通过内网文件共享或者U盘分发给所有需要信任的终端。Linux客户端安装根证书的方式我推荐用系统级信任库,而不是把PEM文件手动丢到浏览器的“受信任证书”里。系统级安装能保证命令行工具(比如curl)和浏览器同时生效,一条命令就搞定:
cp cacert.crt /etc/pki/ca-trust/source/anchors/ update-ca-trustWindows上就简单了,双击crt文件选择“安装证书”,存储位置选“本地计算机”,然后放到“受信任的根证书颁发机构”即可。macOS则是双击后用“钥匙串访问”导入,再把信任策略改为“始终信任”。
4. web服务器证书申请全流程
4.1 在Web服务器上生成私钥与CSR
根CA搭好,整个CA体系的“总闸”就有了,接下来就是Web服务器这个“分闸”的接入动作。这一步的目标是在Web服务器上生成一对密钥,同时产出一个证书签发请求文件(CSR),把CSR交给CA签名,拿回来后就能给Nginx或Apache用上。
# 在web服务器上执行,生成私钥 openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \ -out /etc/nginx/ssl/web_server.key # 生成证书签发请求(CSR) openssl req -new -sha256 -key /etc/nginx/ssl/web_server.key \ -out /root/web_server.csr \ -subj "/C=CN/O=YourCompany/CN=web.example.internal"这里有个关键概念要说清楚:CA签发的所有证书,最终安全性的根子其实在Web服务器自己的私钥上。这个私钥理论上只应该存在Web服务器本机上,永远不要拷贝、不要传到CA服务器上。CA那边拿到的只是CSR,它里面只有公钥和域名信息,没有私钥,这样才能保证即使CA服务器被攻破,也不至于直接带走所有Web服务器的私钥。这一点一定要在流程层面强制,我见过有团队图省事把私钥和CSR一起打包传给CA,这属于把钥匙和锁一起交给外人。
CSR文件里的CN字段,实测下来最稳妥的方案就是填域名本身(如web.example.internal),不添加任何环境标记。之前有人在CN里写“web-test-server”,结果证书签发出来浏览器不认,因为浏览器验证证书时,比对的是网址的域名和证书CN,两边如果不完全一致,证书链校验直接失败。
4.2 CA端签发证书
CSR准备好之后,把它从Web服务器拷贝到CA服务器(用scp或者内网文件共享均可),然后在CA服务器上执行签发命令:
cd /root/ca openssl ca -config /root/ca/openssl.cnf \ -in /root/web_server.csr \ -out /root/ca/certs/web_server.pem \ -days 825 \ -notext执行时OpenSSL会提示确认细节,输入y确认,并输入CA私钥的口令。整个过程十几秒就完成了。签发成功的标志是/root/ca/index.txt中新增了一条记录,同时/root/ca/newcerts/目录下多了一个以序列号命名的PEM文件。
命令里的-days 825是对单个证书的覆盖式设置,哪怕配置文件里default_days已经写好了,这里再显式指定一遍更稳,防止改配置时手滑改错。825天这个值在主流浏览器信任范围内,既给运维留足了续期缓冲,又不会因为时间太长被浏览器视为异常证书。
签发完成后,CA端私有的数据库记录会保留一张证书的完整生命周期,包括签发时间、到期时间、吊销状态。这一点是自建CA相比第三方证书服务最核心的优势:企业对自己的证书资产有完整的掌控和审计能力。第三方CA只给你一张证书,不会给你“证书注册台账”。
4.3 证书文件打包与Nginx部署
CA签发完成后,需要把CA返回的证书文件和CA根证书一并拷回Web服务器。除了刚才生成的那个web_server.pem,还要把cacert.pem一起带上。这一步很多人忽略,导致后面配置Nginx时缺少证书链文件,客户端校验时链条不完整,依然报错。
我的习惯是把文件整理成标准命名和目录:
mkdir -p /etc/nginx/ssl # 从CA服务器上拷贝到web服务器后: cp web_server.pem /etc/nginx/ssl/web_server.crt cp cacert.pem /etc/nginx/ssl/ca_chain.crt chmod 600 /etc/nginx/ssl/web_server.key然后修改Nginx配置,在server块里加上证书相关配置:
server { listen 443 ssl; server_name web.example.internal; ssl_certificate /etc/nginx/ssl/web_server.crt; ssl_certificate_key /etc/nginx/ssl/web_server.key; ssl_trusted_certificate /etc/nginx/ssl/ca_chain.crt; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; }这里三个配置项对应不同的证书内容:ssl_certificate是Web服务器本身的证书,ssl_certificate_key是配对的私钥,而ssl_trusted_certificate配的是根CA证书。前两个如果缺失或错误,浏览器会报“证书无效”;第三个如果缺失,如果你的客户端没有提前安装根证书,Nginx就没办法在握手阶段把完整的证书链发给对方,从而被判定为“不受信任”。
配置改完,记得先检测再重载:
nginx -t systemctl reload nginx如果nginx -t报错,不用慌,八成是证书路径写错或者证书和私钥不匹配。快速验证两者是否匹配的命令:
# 对比证书与私钥的modulus是否一致 openssl x509 -in /etc/nginx/ssl/web_server.crt -noout -modulus | openssl md5 openssl rsa -in /etc/nginx/ssl/web_server.key -noout -modulus | openssl md5两条命令输出的哈希值如果一致,说明证书和私钥是一对;如果不同,那就是拷文件时张冠李戴了。
4.4 证书续期与生命周期管理
Web证书到期是必然的,最关键的是不要让它在毫无准备的情况下过期。建议把“签发记录”和“到期提醒”制度化,而不是每次临时发现报错才去处理。我个人的做法是在CA服务器上写一个简单的提醒脚本,把证书到期前30天、7天各扫一遍:
openssl x509 -in /etc/nginx/ssl/web_server.crt -noout -dates这个命令能看到证书的notBefore和notAfter时间。配合crontab和邮件通知,证书到期前自动提醒。实际上证书续期的流程和首次签发完全一致,只是Web服务器可以复用原来的私钥,重新生成CSR再走一遍CA签发流程即可。复用私钥是允许的,也不会影响安全性,但要注意如果怀疑私钥已经泄露,续期时顺手换一把新的更保险。
还有一个日常容易被忽略的点:证书吊销。如果Web服务器私钥泄露或者域名下线,需要让CA服务器把对应的证书吊销掉,并生成CRL(证书吊销列表)。吊销命令非常简单:
cd /root/ca openssl ca -config /root/ca/openssl.cnf \ -revoke /root/ca/newcerts/1001.pem openssl ca -config /root/ca/openssl.cnf \ -gencrl -out /root/ca/crl.pem吊销不是删记录,而是给证书状态打一个“已吊销”的标记。这样客户端如果配置了CRL检查,就能识别出这张证书已经被撤回了。等保测评时,这套有签发、有续期、有吊销的闭环流程,是加分项。
5. 踩坑实录:旧版CA证书兼容性问题排查
5.1 客户端提示“证书不受信任”的排查思路
做内网CA的人,大概率都遇到过这个场景:明明根证书已经导出并安装了,可curl访问还是报“self-signed certificate in certificate chain”,或者浏览器提示“证书颁发者无效”。每当出现这类问题,我会按下面这个顺序排查,基本能定位到九成以上的问题:
第一步,检查客户端是否真的安装了正确的根证书。Windows看“受信任的根证书颁发机构”里有无对应条目,Linux执行getcert list或者直接查看/etc/pki/ca-trust/source/anchors目录。第二步,在客户端上用openssl命令做一次完整的证书链校验,看服务端发的证书链里缺了什么:
openssl s_client -connect web.example.internal:443 -showcerts输出中的“Server certificate”后面跟着的证书链,就能看出来败在哪个环节。如果只有Web证书而没有中间CA证书,说明Nginx的ssl_trusted_certificate没配全;如果客户端完全不认根证书,那就是根证书安装环节出了问题。
其中一个根因,就是热搜里提到的“系统自带的CA证书版本旧了,因为系统没更新了,所以系统CA证书补丁也不更新”。这种老系统场景下,系统内置的信任证书库版本非常老,要么不包含新签发的内网根证书,要么对现代证书签名算法(如SHA-256)兼容不佳。处理方式不在CA这边,而是在客户端侧把内网根证书手动注入系统信任库,并且明确它的优先级高于系统自带证书库。手动注入之后即使系统自带的CA证书补丁不再更新,也不影响内网根证书的信任链。
5.2 证书链不完整导致的握手失败
内网环境里还有一种经典的报错:浏览器可以打开页面,但命令行工具如git clone或curl却疯狂报错。这往往不是根证书信任问题,而是服务端证书链没发完整。TLS协议规定,服务端在握手阶段要把自己的证书和所需的中间/根证书一并发给客户端,如果只发一张服务器证书,客户端就得自己去“上网”查根证书,内网环境又没法上网,于是握手失败。
解决方式就是把根CA证书追加到一个chain文件里,然后让Nginx同时加载:
cat web_server.crt cacert.pem > /etc/nginx/ssl/fullchain.crt然后配置ssl_certificate指向fullchain.crt。这个文件包含了两张证书,Nginx会自动把完整链发给客户端,握手成功率瞬间就上去了。我排查这类问题的最快方法就是抓包看ServerHello的Certificate消息,但大多数场景下,用openssl s_client看输出的证书数量就够了。
5.3 常见问题速查表
把这段时间踩过的坑整理成一个速查表,每一条都是真金白银的教训:
| 现象 | 根因 | 解法 |
|---|---|---|
| Windows能访问,Linux curl报错 | Linux系统信任库没更新 | 手动导入根证书到/etc/pki/ca-trust并update-ca-trust |
| 浏览器提示证书链不完整 | Nginx没配置ssl_trusted_certificate | 生成fullchain.crt并加载 |
| 证书签发报“File exists” | unique_subject=yes导致DN重复 | 配置文件改为unique_subject = no |
| nginx -t报错 | 证书路径写错或证书与私钥不匹配 | 用modulus比对确认密钥配对 |
| 浏览器显示证书已到期 | 有效期规划不合理 | 按825天规划Web证书,设自动提醒 |
| CA私钥口令遗忘 | 公私钥管理不规范 | 私钥密码保存在密码管理器里,并设置紧急恢复流程 |
| 客户端仍报不受信任 | 根证书安装位置不全局 | Windows选本地计算机,Linux用系统信任库 |
这个表里最想提醒大家的是倒数第二条:CA私钥口令遗忘的代价。私钥口令丢了,不是说重新生成一个就行,而是所有依赖这把私钥签发过的证书,信任链都会变成历史遗留问题,客户端装的每一个根证书都得重新导一遍,代价是完全不可控的。所以口令一定要写进密码管理工具,或者用企业密码保险箱保存。
5.4 兼容性测试清单
部署完成后,强烈建议做一轮系统性的客户端兼容性测试,覆盖企业里可能出现的所有设备。我自己常用的一款内网环境就是Windows 10/11、Ubuntu 20.04/22.04、openEuler、macOS各挑一台真实终端测一遍。
测试步骤很简单,每个终端上用浏览器访问内网Web页面,同时用命令行做一次证书链校验:
# Linux / macOS echo | openssl s_client -connect web.example.internal:443 -CAfile cacert.crt 2>/dev/null | grep "Verify return code"如果输出“ok”,说明证书链完整;如果输出是“unable to get local issuer certificate”或“certificate has expired”,按上面的速查表逐个排查。另外别忘了测一下移动端,手机浏览器现在越来越严格,对证书链的要求比桌面端更苛刻。iOS设备对根证书安装位置有特殊要求,如果企业的移动端主要依赖iOS,建议查一下Apple官方对根证书的信任策略。
这一轮测试别嫌麻烦,一次做扎实,后面两年几乎不用再碰证书系统。我实测过,只要根证书分发到位、证书链配置完整,不同终端基本只需要跑一轮就能全部通过,剩下的就是维护到期提醒这一个动作了。
6. 我在这套方案里的实操心得
这套自建CA方案,我在openEuler环境里从零完整部署过好几轮,最直观的感受是:技术本身不复杂,真正考验人的是“纪律性”。证书系统的每一个环节——密钥保管、根证书分发、有效期规划、续期提醒——都是“做一次就够,但出错一次就很麻烦”的工程。
有个小细节想单独拎出来说:根证书在客户端安装完毕之后,最好做一次备份,并且把CA服务器的整个/root/ca目录纳入企业备份体系。我见过不止一次,有人做完CA一两个月后,发现CA服务器磁盘坏了,而备份策略里根本没有覆盖这个目录,结果所有已签发的证书变成了无法证明来源的“孤儿证书”,用户端信任链全部断掉,还得从头再来一遍。所以加一条备份规则:CA服务器整机备份,至少每周一次;根CA私钥文件,用加密方式离线存放一份;签发数据库index.txt、serial文件,随备份保留。这三条做到了,这套内网身份体系才算真正落地。
如果你所在的内网规模不大,应用也不多,完全可以把这套方案当作基础模板先用起来,等规模上来了再叠加证书吊销列表自动分发、API签发的自动化流程。但无论如何,根CA稳住,后面所有扩展都是水到渠成的事。