浏览器里那个绿色小锁,背后是一整套证书体系。日常开发或者内网运维里,我们经常需要一个 HTTPS 服务,但证书要么找 CA 机构花钱签,要么自己用 OpenSSL 签一张自签名证书顶上。后者是最快、最灵活的路子,尤其适合本地开发、测试环境和内网应用。这篇文章不绕弯子,直接把我用 openssl 签发自签名证书的完整流程、每个命令参数的实际意义,以及一路踩过的坑都整理出来,希望对正在搭测试环境或者想弄明白证书是怎么生成的朋友有帮助。
1. 为什么要自己签发证书:适用场景与核心概念
1.1 自签名证书解决了什么问题
很多项目跑着跑着就需要 HTTPS:前端要连 WebSocket、小程序要校验域名、内网系统要对接接口、甚至本地 Docker 容器之间要做 TLS 加密。如果每个场景都去 CA 机构申请证书,麻烦不说,有些内网环境根本没有外网,域名也不是正式注册的,CA 压根不会给你签。这时候自签名证书就是最务实的选择。
自签名证书说白了就是"自己给自己的公钥做签名"。它和 CA 签发的证书在加密强度上没有本质区别,同样使用 RSA 或 ECDSA 算法,同样包含有效期、颁发者、使用者这些字段。唯一的区别是:它不在浏览器的信任链里,浏览器第一次访问时会弹个警告,需要你手动信任它。这在本地开发、内网后端服务、设备间通信这些场景里完全可以接受,因为你自己知道这把钥匙是靠谱的。
1.2 必须搞清楚的几个名词:密钥、CSR、证书
我见过不少朋友看着教程敲命令,却不知道每一步在干什么,出一丁点问题就懵了。所以先把这几个名词说清楚:
- 私钥(private key):相当于你的身份印章,必须保密。只有自己持有,用来对数据签名。
- 公钥(public key):相当于你的公开名片,包含在证书里发给对方,用来验证签名。
- CSR(Certificate 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,也可以直接调用固定路径下的完整 exe:
C:\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 "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/OU=Dev/CN=localhost"-subj参数可以一次性把所有身份信息传进去,避免交互式填写。各个字段含义如下:
| 字段 | 含义 | 示例 |
|---|---|---|
| C | 国家/地区代码(两位) | CN |
| ST | 省/州 | Beijing |
| L | 城市/区域 | Beijing |
| O | 组织名称 | MyCompany |
| OU | 组织内部门 | Dev |
| CN | Common Name,证书主体名称 | localhost 或域名 |
其中 CN 字段注意一下:浏览器和客户端在验证证书时,会把访问的域名或者 IP 与 CN 字段做比对。所以生成证书前要想清楚这张证书要给谁用。给localhost用的就写CN=localhost,给192.168.1.100用的就写CN=192.168.1.100,给api.example.com用的就写CN=api.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 字段,没有 SAN(Subject 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这里用到了系统自带的certutil,Linux 上没有也可以直接比较两个文件内容是否一致,或者使用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 通常要 JKS,Windows 服务可能需要 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 "/C=CN/O=Internal/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 写对,这套流程跑熟了之后,再回头去看那些商业证书,其实也就是这个流程加了一层付费的信任背书而已。先动手签一张自己的,比翻多少文档都管用。