
浏览器里那个绿色小锁背后是一整套证书体系。日常开发或者内网运维里我们经常需要一个 HTTPS 服务但证书要么找 CA 机构花钱签要么自己用 OpenSSL 签一张自签名证书顶上。后者是最快、最灵活的路子尤其适合本地开发、测试环境和内网应用。这篇文章不绕弯子直接把我用 openssl 签发自签名证书的完整流程、每个命令参数的实际意义以及一路踩过的坑都整理出来希望对正在搭测试环境或者想弄明白证书是怎么生成的朋友有帮助。1. 为什么要自己签发证书适用场景与核心概念1.1 自签名证书解决了什么问题很多项目跑着跑着就需要 HTTPS前端要连 WebSocket、小程序要校验域名、内网系统要对接接口、甚至本地 Docker 容器之间要做 TLS 加密。如果每个场景都去 CA 机构申请证书麻烦不说有些内网环境根本没有外网域名也不是正式注册的CA 压根不会给你签。这时候自签名证书就是最务实的选择。自签名证书说白了就是自己给自己的公钥做签名。它和 CA 签发的证书在加密强度上没有本质区别同样使用 RSA 或 ECDSA 算法同样包含有效期、颁发者、使用者这些字段。唯一的区别是它不在浏览器的信任链里浏览器第一次访问时会弹个警告需要你手动信任它。这在本地开发、内网后端服务、设备间通信这些场景里完全可以接受因为你自己知道这把钥匙是靠谱的。1.2 必须搞清楚的几个名词密钥、CSR、证书我见过不少朋友看着教程敲命令却不知道每一步在干什么出一丁点问题就懵了。所以先把这几个名词说清楚私钥private key相当于你的身份印章必须保密。只有自己持有用来对数据签名。公钥public key相当于你的公开名片包含在证书里发给对方用来验证签名。CSRCertificate Signing Request证书签名请求。里面放着你的公钥和基本信息把它交给 CA或者自己去签发最后变成真正的证书。自签名证书self-signed certificate自己既是申请者又是签发者自己拿自己的私钥给自己的公钥签名。你可以把 CSR 想象成一张申请表上面写着我叫什么、属于哪个组织、我的公钥是多少然后自己拿起私钥这个印章往表上一盖这张表就成了自签名证书。CA 机构和私人颁发者在这个流程中的差别只在于那个印章可信不可信。2. 动手前先搞定环境OpenSSL 版本与 Windows 报错2.1 确认当前 OpenSSL 版本不同版本行为差异很大开始操作前先确认你机器上的 OpenSSL 版本。命令很简单openssl version我平时习惯再看一下详细版本信息openssl version -a为什么要强调版本因为不同版本之间命令参数有差异配置文件路径也不一样。比如 OpenSSL 1.1.1 的默认配置文件路径是/usr/local/ssl/openssl.cnf而 3.x 版本的默认路径通常变成/etc/pki/tls/openssl.cnf或者/usr/lib/ssl/openssl.cnf。如果你在 Windows 上安装的是发行包路径又不一样。版本差异直接决定了你后面写-config参数时要不要手动指定文件路径也影响一些算法的默认选项。2.2 Windows 上最容易遇到的坑版本不匹配报错在 Windows 环境下配置 OpenSSL最常见的报错长这样openssl: version mismatch built against 30000070, you have 38500000这个报错的意思是你正在运行的 OpenSSL 可执行文件是编译时基于某个版本的 OpenSSL 库比如 3.0.7生成的但程序运行时实际加载的 DLL 库却是另一个版本比如 3.8.5 或某个更高版本两者对不上程序不敢继续跑。这个问题的根源几乎都是 PATH 环境变量里同时存在多个 OpenSSL 安装路径。比如你装了 Git for Windows它自带了一套 OpenSSL后来你又装了独立安装包装到了C:\Program Files\OpenSSL-Win64再后来另一个软件又往 PATH 里塞了自己的 OpenSSL。运行openssl命令时系统按 PATH 顺序找到了其中一个它加载的 DLL 却来自另一个目录就炸了。排查方法也简单where openssl这条命令会列出系统能找到的所有 openssl.exe 路径按顺序从上到下就是实际执行顺序。如果列出两三个结果说明环境变量确实乱了。解决办法是把多余的或者旧版本的路径从 PATH 里删掉只保留一个你确定的版本再重新开一个新的命令行窗口测试。如果不想动全局 PATH也可以直接调用固定路径下的完整 exeC:\OpenSSL-Win64\bin\openssl.exe version2.3 选哪个版本合适1.1.1 还是 3.x网上很多教程还在用 OpenSSL 1.1.1w 演示。这个 1.1.1w 是 1.1.1 分支的最后一个版本2023 年之后这个分支已经停止维护。如果是从零开始学建议直接用 3.x 版本命令语法基本兼容安全性上更符合当前标准而且对新算法的支持更全面。如果你纯粹想快速跑通流程、不打算长期使用那手头有哪个版本都可以命令区别不大。不过有一点要注意网上搜资料时看到报错信息里的版本号一定要留个心眼。不同大版本1.0.2、1.1.1、3.0、3.2...之间有些参数的行为不一样。查资料优先看官方文档或与你版本接近的文档不然容易照着别人的解法改半天却没用。3. 核心命令拆解从私钥到自签名证书的三步3.1 第一步生成 RSA 私钥生成私钥是整条链路的起点。最基本的命令openssl genrsa -out server.key 2048这里genrsa是生成 RSA 密钥的指令-out server.key指定输出文件名2048指密钥长度。密钥长度决定安全性底线2048 位是当前的最低推荐标准证书领域基本默认用它想要更放心可以上 4096但生成速度慢一点TLS 握手时 CPU 消耗也稍微多一点实际业务里 2048 已经够用。生成之后一定要给私钥设置权限尽量不要让无关用户能读到chmod 600 server.key如果是在 Windows 上也要留意不要把它放到公开共享目录里。私钥一旦泄露你的证书就毫无意义别人可以冒充你的身份做签名。3.2 第二步基于私钥生成 CSR有了私钥下一步是生成 CSR。正式申请证书时这个文件会提交给 CA自签名流程里它只是中间产物但建议保留一份以便日后使用同一个私钥重新申请。openssl req -new -key server.key -out server.csr -subj /CCN/STBeijing/LBeijing/OMyCompany/OUDev/CNlocalhost-subj参数可以一次性把所有身份信息传进去避免交互式填写。各个字段含义如下字段含义示例C国家/地区代码两位CNST省/州BeijingL城市/区域BeijingO组织名称MyCompanyOU组织内部门DevCNCommon Name证书主体名称localhost 或域名其中 CN 字段注意一下浏览器和客户端在验证证书时会把访问的域名或者 IP 与 CN 字段做比对。所以生成证书前要想清楚这张证书要给谁用。给localhost用的就写CNlocalhost给192.168.1.100用的就写CN192.168.1.100给api.example.com用的就写CNapi.example.com。如果不想用交互式输入-subj是必须的否则命令会卡在提示符等你一个个填写。3.3 第三步用自己的私钥签发证证书CSR 生成后自签名就发生在这一步openssl x509 -req -in server.csr -signkey server.key -out server.crt -days 365逐项解释x509 -req告诉 OpenSSL 我们要生成一个 X.509 格式的证书输入是一个 CSR。-in server.csr指定读取请求文件。-signkey server.key指定用哪把私钥做签名。注意这一步用的就是第一步生成的同一把密钥这是自签名的关键——对应用户和签发者是同一个人。-out server.crt输出证书文件。-days 365有效期这里是 365 天。生成完可以快速看一眼证书内容openssl x509 -in server.crt -text -noout注意-noout表示不打印 base64 编码的证书内容本身只看可读文本。这一步至少能确认证书的签发者、使用者、有效期、签名算法这些基本信息没写错。3.4 更偷懒的方式一行命令直接生成自签名证书其实上面的三步可以合成一条命令openssl req -x509 -newkey rsa:2048 -keyout server.key -out server.crt -days 365 -nodes其中-x509表示直接输出一张自签名证书不再经过 CSR 中间步骤-newkey rsa:2048表示同时生成新私钥长度 2048-nodes表示私钥不用密码保护no DES。如果不加这个参数每次需要使用私钥时都会要求输入密码卡在脚本里非常难受。两种方式怎么选如果你只是临时给测试环境来一张证书一行命令是最快的。如果你希望把私钥和 CSR 都分开保存方便以后给同一个私钥重新签发证书建议还是走标准的私钥 → CSR → 证书三步流程。个人经验是只要不是随手玩玩我都建议至少把私钥单独保留好因为它可以在证书过期后重新签一张新证书不用换密钥对长期运行的系统很友好。4. 进阶实操用配置文件签发带 SAN 扩展的证书4.1 为什么必须有 SAN现代浏览器的严格校验直接用上面的命令签出的证书有个问题它只有 CN 字段没有 SANSubject Alternative Name主题备用名称。在 Chrome 58 之后浏览器已经不再认可只包含 CN 字段的证书它要求证书必须携带 SAN 扩展并且 SAN 里包含当前访问的域名或 IP否则一律提示证书无效。这也是很多朋友照着老教程签出证书后浏览器死活不认账的原因。根本不是什么加密强度问题而是少了一个扩展字段。要让证书在现代浏览器里正常使用必须把域名或 IP 写进 SAN。4.2 编写 openssl 配置文件推荐的做法是写一个配置文件把身份信息和扩展字段都放进去这样命令干净、可重复使用。我自己习惯建一个server.cnf[req] distinguished_name dn x509_extensions v3_ca prompt no [dn] C CN ST Beijing L Beijing O MyCompany CN localhost [v3_ca] subjectAltName alt_names basicConstraints CA:TRUE [alt_names] DNS.1 localhost DNS.2 *.local IP.1 127.0.0.1 IP.2 192.168.1.100重点看[alt_names]段这里声明的 DNS 和 IP 会被写进证书的 SAN 扩展里。DNS.1和DNS.2是域名条目支持通配符IP.1、IP.2是 IP 地址条目。如果你的服务就是内网某个 IP一定要把 IP 加进来如果你用的是自定义域名就写 DNS 条目。注意配置文件里的basicConstraints CA:TRUE。这个字段表示这张证书本身也可以当作 CA 去签发别的证书。纯服务端证书理论上不需要这个属性设成CA:FALSE更规范。但有时候我们会拿自签名证书当内部根证书再往下签发子证书这时就需要CA:TRUE。按需设置即可。4.3 用配置文件签发证书上一步生成 CSR 时会自动读取配置文件openssl req -new -key server.key -out server.csr -config server.cnf然后签发证书时也要指定扩展文件openssl x509 -req -in server.csr -signkey server.key -out server.crt -days 825 -extensions v3_ca -extfile server.cnf这里的关键参数是-extfile server.cnf和-extensions v3_ca意思是签发时把配置文件里 v3_ca 这段的扩展内容写进证书。验证 SAN 是否生效openssl x509 -in server.crt -noout -ext subjectAltName看到输出里列出了 DNS 和 IP 条目说明 SAN 已经写进去了。这也是排查浏览器不认证书时最先应该做的一步检查。5. 签完别急着上线证书验证与常见报错排查5.1 签发后的证书体检清单每次签完证书我会按顺序跑这几条命令做体检openssl x509 -in server.crt -noout -dates # 看有效期 openssl x509 -in server.crt -noout -subject # 看使用者 openssl x509 -in server.crt -noout -issuer # 看签发者 openssl x509 -in server.crt -noout -ext subjectAltName # 看 SAN检查时重点确认三件事有效期是不是预想的范围签发者和使用者是不是同一个自签名特征SAN 里的域名或 IP 是否和你实际要访问的一致。如果 SAN 里写的是localhost但你实际用127.0.0.1访问浏览器照样报错因为两者需要严格匹配。我还喜欢用下面的命令验证证书和私钥是否匹配openssl x509 -in server.crt -noout -pubkey cert_pubkey.pem openssl pkey -in server.key -pubout -out key_pubkey.pem certutil -hashfile cert_pubkey.pem certutil -hashfile key_pubkey.pem这里用到了系统自带的certutilLinux 上没有也可以直接比较两个文件内容是否一致或者使用diff。如果两个公钥对不上说明证书和私钥根本不是同一对部署后一定会出问题。注意certutil -hashfile是 Windows 命令Linux/macOS 下可以跳过用diff cert_pubkey.pem key_pubkey.pem或者其他哈希工具即可。核心思路是一样的证书里的公钥必须和私钥导出的公钥一致。5.2 常见报错与解决办法第一个高频报错unable to load config info from /usr/local/ssl/openssl.cnf这是 OpenSSL 找不到配置文件。解决办法是显式指定配置文件路径openssl req -new -key server.key -out server.csr -config /etc/pki/tls/openssl.cnf如果连系统默认配置文件都没有那就更简单了直接用我上面写的server.cnf自定义配置即可。第二个高频报错unable to write random state这个在老版本里常见多发生在没有写权限的目录下执行命令。新版 OpenSSL 基本不再出现。如果遇到检查当前目录的写权限或者切换到有权限的目录再操作。第三个也是最容易被忽视的证书显示certificate has expired。原因往往不是真的过期而是系统时间不对。特别是内网虚拟机时间漂移后签发出来未来生效的证书或者超过有效期的证书都会导致各种验证失败。检查服务器时间并同步然后再重新签一张。第四个是 Windows 上常见的 OpenSSL 版本不匹配问题前面已经详细说过先where openssl检查多版本再清理 PATH。5.3 浏览器提示不安全之后的正确操作自签名证书再标准浏览器第一次访问时还是会显示警告这是预期行为。此时需要把这张证书手动加入系统的信任区。在 Windows 上可以双击server.crt文件选择安装证书把它放入受信任的根证书颁发机构在 macOS 上可以通过钥匙串访问导入并标记为信任Linux 上则根据发行版不同放在/etc/pki/ca-trust/source/anchors/或/etc/ssl/certs/下然后执行update-ca-certificates。不过我要提醒一句手动信任自签名证书只适合你自己管理的环境。如果是在公司内网给所有同事用正确做法是把这张自签名证书或内部 CA 证书统一下发到大家的机器上信任而不是让每个人点击继续访问。前者是一劳永逸的信任链建设后者是绕过警告最后可能导致安全问题没人愿意负责。6. 把证书用起来Nginx 配置与批量生成经验6.1 让 Nginx 立刻用上自签名证书证书签好之后部署其实很简单。以 Nginx 为例在 server 块里加上三行配置server { listen 443 ssl; server_name localhost; ssl_certificate /etc/nginx/certs/server.crt; ssl_certificate_key /etc/nginx/certs/server.key; location / { proxy_pass http://127.0.0.1:8080; } }改完配置记得先测试再重载nginx -t nginx -s reload我遇到过的常见问题是证书路径没写对、nginx 进程没有权限读取私钥。证书和私钥文件的权限nginx 的 master 进程必须能读到权限太严或者放在了家目录里reload 时就会报错。6.2 证书格式与多场景部署注意OpenSSL 默认生成的是 PEM 格式内容是一段段的 base64 文本。很多中间件和编程语言需要其他格式Java 通常要 JKSWindows 服务可能需要 PFX。格式转换也常有随手记一下# PEM 转 PKCS12 openssl pkcs12 -export -in server.crt -inkey server.key -out server.pfx # 从 PKCS12 提取 PEM 证书 openssl pkcs12 -in server.pfx -clcerts -nokeys -out extracted.crt不过这篇文章的场景主要围绕签名流程格式转换不展开太多。你只要记住一点部署前确认目标平台需要什么格式再决定是否转换宁可多花两分钟查一下也别盲目把 PEM 文件硬塞给不支持它的程序。6.3 证书过期与批量生成的最佳实践自签名证书因为没有 CA 的约束有效期全凭自己定。我个人的习惯是本地开发工具类证书设 1 年以内过期就重新签保持最小信任范围内网服务证书设 2 年左右并写入自动化提醒避免忘记续签导致服务集体报错基本不使用超过 3 年的证书不是因为技术上不行而是时间一长人和环境都变了私钥的管理状态很难保证。如果你要一次性给很多台内网机器生成证书手工敲命令不现实。我写过类似的脚本核心思路就是跑一个循环for host in server01 server02 server03; do openssl req -new -newkey rsa:2048 -nodes \ -keyout ${host}.key -out ${host}.csr \ -subj /CCN/OInternal/CN${host} \ -config server.cnf openssl x509 -req -in ${host}.csr \ -signkey ${host}.key -out ${host}.crt \ -days 730 -extensions v3_ca -extfile server.cnf done这里面每个主机的 CN 都会替换成对应的主机名SAN 里的 IP 则要根据各机器的实际地址去更新配置文件。批量生成时最容易犯的错就是所有机器的 CN 和 SAN 都是一样的部署后互相访问时证书校验不过。所以凡是涉及多机互信的场景务必逐台核对 SAN。最后再分享一个我吃了不少亏之后的体会自签名证书的功能和 CA 证书完全一样它的不受信任并不是技术缺陷而是信任体系没有建立。你在自己的网络里完全可以把它当内部根证书来管理把私钥保护好、把有效期监控起来、把 SAN 写对这套流程跑熟了之后再回头去看那些商业证书其实也就是这个流程加了一层付费的信任背书而已。先动手签一张自己的比翻多少文档都管用。