尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

内网自建私有CA:用OpenSSL签发多域名IP证书的完整指南

发布时间:2026/9/15 15:16:03

资讯中心
01
ARTICLE

内网自建私有CA:用OpenSSL签发多域名IP证书的完整指南

内网自建私有CA:用OpenSSL签发多域名IP证书的完整指南
做内网服务的老哥应该都被浏览器那个“您的连接不是私密连接”红屏恶心过。每次点“高级-继续访问”倒是不费劲但到了分布式环境里监控系统、CI/CD平台、Kubernetes组件、NAS、数据库管理面几十台机器来回跳天天点继续访问手都麻了。自己搭一套CA把根证书装进所有终端重新签发支持多域名和IP的证书这一劳永逸的办法我前前后后折腾过好几回这次终于把完整方案跑通了。这篇东西不是教科书就是一次实操记录。内容覆盖从生成根CA私钥、自签名根证书到为服务器生成CSR、用CA签发同时包含多个DNS域名和多个IP地址的证书再到Nginx部署和客户端信任配置。适合被内网证书问题折磨的运维、开发、网络管理员也适合想搞明白OpenSSL证书签发到底是怎么回事的新手。跟着敲一遍命令你也能在半小时内把一套私有CA用起来。1. 为什么要自建CA先搞清楚需求再动手1.1 自签名证书和私有CA的区别很多人第一反应是“直接openssl生成一个自签名证书不就完了”但这里有个概念误会。单张自签名证书的本质是“自己给自己背书”浏览器没有理由信任一个从未见过的签发者所以每次都会红屏警告。私有CA的思路不一样它先产生一个“公证处”根证书再拿这个根证书去签发各个服务器的证书。客户端只需要信任根证书这一张由这个根签出来的所有证书就都被认可了。这就好比你家里配了一把万能钥匙门锁全都按这把钥匙来配往后不管添多少扇门都不用再单独换锁芯。我实际体验下来的区别很直接自签名证书在每个新服务上都要手动“信任此证书”有时候还得挨个导到CentOS的ca-bundle里烦得很而自建CA之后根证书往各台机器上一装后面签的每张证书都不需要客户端再点任何“继续访问”。1.2 什么时候需要多域名/IP证书公共CA签发的证书一般要求有公网域名纯内网IP、局域网主机名这类资源很难申请就算能申请过程又长又贵。而且公网证书是公开信任链内网服务没必要把自己的资产暴露在这个体系里。多域名和IP的需求我遇到最多的是几种场景内网Web管理平台比如GitLab、Jenkins、JumpServer同一个域名要被多个环境共用微服务集群里各个节点的访问地址是IP比如10.0.0.7、192.168.10.20节点间通信又必须走TLSKubernetes的ingress-controller、etcd集群证书里既要有service域名也要有Pod IPNAS、路由器后台、监控大屏这类设备访问时压根不输入域名只输IP如果一张证书只能写一个域名那多域名环境就得每个域名签一次。OpenSSL里的SANSubject Alternative Name主题备用名称扩展就是为解决这个需求存在的它可以在同一张证书里塞进若干个DNS名称和若干个IP地址。1.3 信任模型和架构选择私有CA可以做成两级根CA签发中间CA中间CA再签发服务器证书公共CA基本都是这套玩法。但内网场景做两级容易把自己绕晕根CA直接签发服务器证书信任链清晰简单完全够用。我这次采用的模型是根CA离线存放私钥加密码保护只用来签发服务器证书服务器证书按需签发有效期不要太长方便轮换客户端只导入根CA证书自动信任所有由此根签发的服务器证书这样做的好处是根私钥不暴露在在线服务所在的机器上即使某台业务机被入侵攻击者拿到了服务器私钥也无法伪造一张被全网信任的根证书。2. 准备工作与核心概念2.1 确认OpenSSL版本开始前先确认系统里OpenSSL的版本openssl version我用的环境是Debian 12OpenSSL 3.0.11Ubuntu 22.04、CentOS 7OpenSSL 1.0.2也能跑通下面的命令但部分参数需要注意。比如-addext这个参数在OpenSSL 1.1.1才开始支持老版本要改用配置文件扩展的方式。高版本OpenSSL在3.x版本里对某些签名算法有默认安全级别调整我建议直接用系统包管理器安装的版本别自己从源码编译一个后面牵扯的动态库匹配问题能少一大半。这年头用OpenSSL做CA根本不需要追新稳定、顺手最重要。2.2 SAN扩展和工作目录规划SAN是整个多域名/IP签发里最关键的一个概念。我打个比方证书是身份证CN字段是身份证上的“姓名”而SAN是“曾用名护照号工号”一证可以对应你所有的行为轨迹。Chrome 58之后浏览器验证主机名时只看SANCN字段几乎被无视了。也就是说你证书CN写的是gitlab.example.com但用户用https://192.168.10.20访问如果没有对应的IP SAN浏览器照样报NET::ERR_CERT_COMMON_NAME_INVALID。工作目录我习惯统一放在/opt/myca下结构是这个样子mkdir -p /opt/myca/{certs,private,newcerts,crl} chmod 700 /opt/myca/private cd /opt/myca touch index.txt echo 1000 serialprivate/保存根私钥权限必须锁到700certs/保存签出来的服务器证书newcerts/OpenSSL命令自动输出证书的位置index.txt证书数据库记录哪些证书有效、哪些已吊销serial下一个证书的序列号从1000开始这套目录其实是模仿OpenSSL默认CA目录结构来的好处是后面如果要引入证书吊销列表CRL不用再改架构。2.3 两种签名工作方式的选择用OpenSSL签发证书常见有两种姿势。一种是纯命令行生成CSR的时候用-addext把SAN直接写进去签发时再用-extfile指定扩展文件。另一种是写一个自定义openssl.cnf把CA相关的配置、默认策略、扩展项都写在配置文件里签发时用-config指定。我这次选择的是第二种思路的轻量版把SAN扩展内容单独写进一个extfile再用-extfile带进签发命令。不修改系统级/etc/ssl/openssl.cnf避免污染全局配置也方便每个证书保存自己的SAN配置后续审计时直接看文件就知道这张证书覆盖了哪些域名和IP。3. 一步步建立CA并签发证书3.1 生成根CA私钥和自签名证书先切换进我的工作目录cd /opt/myca生成根私钥用RSA 4096位并开启AES-256加密这样每次拿根私钥签发时都要输入密码降低泄露风险openssl genrsa -aes256 -out private/ca.key 4096执行后会要求你设置一个密码这个密码必须记牢根私钥丢了还能靠备份恢复密码忘了就等于整套CA作废。然后基于这个私钥生成根证书openssl req -x509 -new -nodes -sha256 -days 3650 \ -key private/ca.key \ -out ca.crt \ -subj /CCN/OMy Private Lab/CNMy Private CA Root \ -addext basicConstraintscritical,CA:TRUE \ -addext keyUsagecritical,keyCertSign,cRLSign参数解释-x509直接输出一个自签名证书而不是CSR-days 3650根证书有效10年-nodes不加密输出私钥这里针对的是中间生成逻辑实际根私钥刚才已经用-aes256加过密了-subj打印证书主体信息CN写成根CA自己的名称-addext basicConstraintscritical,CA:TRUE声明这是一个CA证书而且这个约束是critical的不允许被忽略-addext keyUsagecritical,keyCertSign,cRLSign指定私钥用途只能用于签发证书和吊销列表生成后用下面的命令检查openssl x509 -in ca.crt -noout -text | head -20输出里能看到CA:TRUE和Key Usage: Certificate Sign, CRL Sign就说明根证书没问题。3.2 准备SAN扩展文件这一步是整个流程的核心多域名和IP能不能签进证书全靠它。我新建一个server.ext文件subjectAltName DNS:gitlab.internal.example, DNS:*.apps.internal.example, IP:192.168.10.20, IP:10.0.0.7 extendedKeyUsage serverAuth这个文件就是给签发命令用的扩展配置。SAN里可以混写DNS和IP注意IP地址必须写IP:前缀域名必须写DNS:前缀两者用逗号分隔。*.apps.internal.example这种通配符写法可以覆盖一批子域名。如果还想给客户端用的场景也加上可以把extendedKeyUsage改成serverAuth, clientAuth这样同一张证书既能当服务器证书也能当客户端证书。在etcd集群这类双向TLS场景里这么做省事很多。3.3 生成服务器私钥和CSR服务器私钥我一般用RSA 2048位就够了4096在握手时性能损耗略大内网场景没必要。生成私钥openssl genrsa -out certs/server.key 2048 chmod 600 certs/server.key然后用私钥生成CSR这里CSR里也要带SAN信息虽然OpenSSL签发时会被-extfile覆盖但让CSR本身就包含完整信息同一份CSR拿去别的CA申请证书时不容易漏openssl req -new -key certs/server.key \ -subj /CCN/OMy Private Lab/CNgitlab.internal.example \ -addext subjectAltName DNS:gitlab.internal.example, DNS:*.apps.internal.example, IP:192.168.10.20, IP:10.0.0.7 \ -out certs/server.csr这个CN字段按规范应该写主域名但我们前面说了浏览器实际验证时看SAN。CN写了主域名只是让证书看起来规整不至于某些旧客户端解析CN时拿到一个不认识的字符串。3.4 用CA签发多域名/IP证书接下来是最关键的一步用根证书和根私钥去签发服务器证书openssl x509 -req \ -in certs/server.csr \ -CA ca.crt \ -CAkey private/ca.key \ -CAcreateserial \ -out certs/server.crt \ -days 825 \ -sha256 \ -extfile server.ext执行后系统会提示输入根私钥的密码。这里的-days 825是我习惯用的一个数值公共CA目前对证书最长有效期的限制是825天虽然是内网私有CA不归公共体系管但习惯性按这个标准走以后证书如果暴露到外网或者接入公开系统不会踩有效期的合规坑。验证证书内容看SAN是否真的写进去了openssl x509 -in certs/server.crt -noout -text | grep -A 2 Subject Alternative Name输出类似X509v3 Subject Alternative Name: DNS:gitlab.internal.example, DNS:*.apps.internal.example, IP Address:192.168.10.20, IP Address:10.0.0.7看到IP Address那一项就说明IP地址已经成功签进证书了。验证证书与私钥是否匹配openssl x509 -noout -modulus -in certs/server.crt | openssl md5 openssl rsa -noout -modulus -in certs/server.key | openssl md5两个命令输出的md5值一致说明证书和私钥配套。最后用根证书验证整条信任链openssl verify -CAfile ca.crt certs/server.crt输出server.crt: OK就是全部通过。3.5 吊销和证书数据库的基本用法建立CA时生成的index.txt和serial文件在吊销证书的场景下才会真正用起来。万一某台服务器私钥泄露或者某张证书不再需要可以用下面的命令吊销openssl ca -config /etc/ssl/openssl.cnf -revoke certs/server.crt生成CRLopenssl ca -config /etc/ssl/openssl.cnf -gencrl -out crl/ca.crl不过内网小规模场景下吊销用得很少。我自己更常用的做法是直接把有效期设短一些比如180天到期前批量重签比维护吊销列表简单得多。4. 部署到服务端与客户端信任配置4.1 在Nginx中部署证书拿到server.crt和server.key之后Nginx配置非常简单server { listen 443 ssl; server_name gitlab.internal.example; ssl_certificate /opt/myca/certs/server.crt; ssl_certificate_key /opt/myca/certs/server.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; }配置完重载nginx -t nginx -s reload如果那张证书要同时给多个域名用没必要搞多个server块配不同证书只要server_name分别写在各自server块里它们共用同一张证书文件就行。因为证书里的SAN包含了所有域名TLS握手时只要SNI匹配其中一个域名证书验证就能通过。4.2 让客户端信任根CA这一步决定成败。证书签得再漂亮客户端不信任根CA照样红屏。在Windows上双击ca.crt文件选择“安装证书”存储位置选“本地计算机”然后“将所有证书都放入下列存储”点击浏览选“受信任的根证书颁发机构”一路下一步。在Linux上以Debian/Ubuntu为例sudo cp ca.crt /usr/local/share/ca-certificates/my-private-ca.crt sudo update-ca-certificatesCentOS/RHEL系sudo cp ca.crt /etc/pki/ca-trust/source/anchors/ sudo update-ca-trustmacOS把ca.crt拖进钥匙串然后在“显示简介”里把信任设置为“始终信任”。这里有个注意事项要说清楚浏览器有些还会继续报一次错比如Firefox有自己独立的证书库不走系统根证书需要额外在设置里搜索“证书”并导入ca.crt。Chrome和Edge在Windows/macOS上跟随系统证书库导入系统层就够了。4.3 证书链合并和备份策略如果用的是根CA直接签发的证书Nginx的ssl_certificate直接填server.crt就行不需要拼链。但如果你以后做了中间CA那就要把服务器证书和中间CA证书按顺序合并成一个chain文件cat server.crt middle-ca.crt server-chain.crt根CA私钥的备份我建议离线保存。我实际的操作是把ca.key用一个加密压缩包存到移动硬盘和ca.crt、serial、index.txt一起打一个包。这样即使在线服务器硬盘报废也能从备份恢复出一套能用的CA而不是被迫重新初始化让所有客户端重新信任新根。打包备份的命令tar czf myca-backup.tar.gz ca.crt ca.key serial index.txt certs/备份压缩文件建议用GPG加密gpg -c myca-backup.tar.gz5. 常见问题与排查技巧实录5.1 证书显示“不受信任”或“无法验证”这是自签CA最常见的坑。先反查是不是客户端没装根证书在Linux上可以执行curl -vI https://gitlab.internal.example/ 21 | grep SSL certificate verify如果报错提到unable to get local issuer certificate就是根证书没装或者装进了错误的存储。有些系统装了但没跑update-ca-certificates白白折腾好久。还有一种情况是根证书装了但服务器证书没有按链去发。根CA真接签发的情况下不会出现缺中间链的问题可如果你复制了server.crt却发现上面还带着一个没有发送给客户端的中间证书就要检查Nginx配置里的ssl_certificate是不是漏了中间证书。5.2 ERR_CERT_COMMON_NAME_INVALID这个报错很典型代表浏览器找不到匹配的SAN。Chrome 58之后已经不再看CN字段了只认SAN你的证书里没有对应的DNS或IP SAN就会报这个。排查命令openssl x509 -in server.crt -noout -text | grep -A 2 Subject Alternative Name看看访问的域名或IP是否在SAN列表中。我之前踩过一个坑签发时-extfile server.ext忘了写IP项结果证书里只有DNS导致直接https://192.168.10.20访问怎么都报错。加上IP:192.168.10.20重新签发问题立刻消失。5.3 OpenSSL版本不匹配的问题如果你不是用系统自带的OpenSSL而是自己编译的程序运行时候可能碰到类似openssl version mismatch. built against 30000020, you have 30500060的报错。这表示程序编译时链接的是某个版本的libssl动态库运行时却加载了另一个版本典型的动态链接库冲突。我的建议是不要自己手动去替换系统的libssl优先用系统包管理器安装OpenSSL让程序和库保持同一套源。如果必须用自编译的程序先检查它链接的库路径ldd /path/to/your/program | grep ssl看到路径指向系统库还是自编译库用LD_LIBRARY_PATH指向匹配的库目录可以临时解决但更好的办法是重编程序让它链接到当前系统的OpenSSL头文件和库。5.4 证书轮换和序列号冲突证书有效期内需要重签时如果手动指定了重复的序列号OpenSSL可能报File exists或者数据库冲突。所以正常流程是让serial文件自动递增不要手动去设置-set_serial除非你非常清楚自己在做什么。轮换证书的正确节奏是先生成新私钥和新CSR然后用CA签发新证书再把新证书部署到Nginx并reload。不要直接删掉就证书先保留一个周期万一新证书有问题还能快速回滚。5.5 访问证书时候的curl测试服务端和客户端都配置好之后用curl做一次完整的验证curl --cacert /opt/myca/ca.crt https://gitlab.internal.example/如果你是临时测试不想把根证书装进系统用--cacert指定根证书是最快的。还可以用--resolve来模拟域名解析到指定IPcurl --resolve gitlab.internal.example:443:192.168.10.20 \ --cacert /opt/myca/ca.crt \ https://gitlab.internal.example/这样即使内网DNS还没配好也能提前验证IP SAN和域名匹配是否正常。写在最后整套流程跑下来我最大的体会是私有CA的难点不在命令本身而在于理清信任关系。根证书是要长期信任的所以私钥保护、备份、有效期这些要多花心思服务器证书是经常轮换的所以SAN配置、签发、验证这一套流程要尽量简洁可复制。我现在的习惯是每次签完证书都把证书信息归档成一个文本文件记录签发时间、到期时间、SAN列表、申请原因。半年后回过来看这批记录比什么文档都好使。多域名和IP签发这件事掌握了SAN这个核心概念之后剩下全是重复劳动。希望这份记录能帮你把这块骨头啃下来。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。