1. 为什么内网服务也需要一张自签证书很多人在内网搭服务时第一反应是“反正只有我自己访问用 HTTP 就够了”。这个想法在早期确实没问题但一旦你的服务开始涉及登录态、API 密钥、内部管理后台或者你打算把本地开发环境模拟得跟线上一致HTTP 就会带来两个很实际的麻烦一是浏览器会持续提示“不安全”二是某些现代浏览器 API比如地理位置、剪贴板、Service Worker在非安全上下文中直接不可用。自签 SSL 证书解决的正是这个场景。它不是让公网信任你而是让你自己、你的团队、你的测试设备信任你。你可以把它理解成“自己给自己盖了个章”——这个章在你自己家里有效出了门没人认但在家里足够用。OpenSSL 就是那个帮你刻章的工具Linux 下几乎自带不需要额外装什么商业软件。这篇文章面向的是有一定 Linux 基础、需要在本地或内网环境快速启用 HTTPS 的开发者、运维人员和测试人员。我会从证书的基本概念讲起把生成流程拆成可复现的步骤再补充我在实际使用中踩过的坑和排查方法。你不需要事先懂密码学跟着操作就能拿到一张能用的证书。2. 自签证书的核心概念与方案选型2.1 自签证书和 CA 签发证书的本质区别要理解自签证书先得知道正规证书是怎么来的。公网上的 HTTPS 证书由受信任的证书颁发机构CA签发浏览器和操作系统内置了一份“受信任根证书列表”只要你的证书能追溯到列表里的某个根浏览器就认。自签证书的问题在于签发者就是你自己而你的“根”不在任何浏览器的信任列表里所以浏览器会报警。但这不代表自签证书“不安全”。加密强度上自签证书和 CA 证书可以完全一样都是 RSA 2048 位或 ECC 256 位起步。区别只在“信任链”这一层。你可以手动把你的自签根证书导入系统或浏览器的信任库导入之后浏览器就不再报警了。这就是内网 HTTPS 的标准做法。注意自签证书只适合内部使用绝对不要用在面向公网的生产环境。公网用户不会去导入你的根证书他们只会看到红色警告然后关掉页面。2.2 为什么选 OpenSSL 而不是其他工具市面上生成证书的工具不少有基于图形界面的也有封装好的脚本。我最终长期用 OpenSSL原因有三个。第一是普适性。几乎所有 Linux 发行版都预装了 OpenSSL你不需要额外安装任何东西SSH 连上一台新机器就能直接开工。第二是可控性。OpenSSL 的配置文件把证书的每一个字段都暴露给你主题信息、扩展项、有效期、密钥用途全都能精确控制。第三是可脚本化。一条命令加一个配置文件就能批量生成整套证书适合需要多域名、多服务的场景。相比之下一些封装工具虽然上手快但遇到需要自定义 SANSubject Alternative Name或者扩展密钥用途时反而要去翻它的文档不如直接用 OpenSSL 来得直接。2.3 单证书方案和自建 CA 方案的取舍自签证书有两种常见做法。一种是直接自签一条命令生成一个自包含的证书自己签自己。另一种是自建 CA先造一个根证书再用根证书去签发服务器证书。直接自签的优点是快一条命令搞定适合个人开发、临时测试。缺点是每台设备都要单独信任这张证书证书一多管理就乱。自建 CA 的优点是只信任一次根证书之后所有由这个根签发的证书都自动被信任适合团队内网、多服务场景。我的建议是如果你只是本地跑个 Demo直接自签足够如果你要给团队内网搭一套服务花十分钟建个 CA后面会省很多事。下面两套流程我都会给出来。3. 动手前的环境确认与目录规划3.1 确认 OpenSSL 版本和可用性开工前先确认工具在位。打开终端执行openssl version -a正常会输出类似OpenSSL 3.0.x的版本信息。这里有个关键点OpenSSL 1.1.1 和 3.x 在配置文件语法上有差异尤其是req段和扩展段的写法。1.1.1 支持[req]下的req_extensions3.x 更推荐用-addext参数或者独立的扩展文件。如果你照着老教程操作报错八成是版本对不上。我一般会顺手看一眼配置文件位置openssl version -d它会告诉你OPENSSLDIR在哪默认配置文件通常是那个目录下的openssl.cnf。知道这个位置后面排查“为什么我的配置没生效”时很有用。3.2 建立清晰的工作目录结构证书文件一多就容易乱我习惯一开始就把目录分好。推荐结构如下mkdir -p ~/certs/{ca,server,client} cd ~/certsca/放根证书和根私钥server/放服务器证书、私钥、CSRclient/放客户端证书如果做双向认证这样分的好处是后面配置 Nginx 或应用时路径一目了然不会把根私钥和服务器私钥搞混。根私钥泄露等于整个信任体系崩塌必须单独隔离。提示私钥文件的权限一定要收紧。生成后立刻执行chmod 600 *.key避免同机器上其他用户读取。3.3 一个容易被忽略的前置检查在生成证书前先想清楚你要用哪些域名或 IP 访问这个服务。这一步决定了 SAN 字段怎么写。SAN 是现代浏览器真正校验的字段CNCommon Name从很早的版本起就已经被主流浏览器忽略了。也就是说你 CN 写localhost但 SAN 里没有localhost浏览器照样报错。我见过太多人卡在这里证书生成了浏览器还是提示名称不匹配就是因为 SAN 没配对。所以动手前把访问地址列出来比如localhost、127.0.0.1、192.168.1.100、dev.internal后面全部塞进 SAN。4. 方案一直接自签一张服务器证书4.1 一条命令生成私钥和证书最简做法是用一条命令同时生成私钥和自签证书openssl req -x509 -newkey rsa:2048 -nodes \ -keyout server.key -out server.crt \ -days 825 -subj /CCN/STLocal/LLocal/ODev/CNlocalhost \ -addext subjectAltNameDNS:localhost,IP:127.0.0.1逐段拆解一下。-x509表示直接输出自签证书而不是 CSR-newkey rsa:2048表示同时生成一把 2048 位 RSA 私钥-nodes是关键它表示私钥不加密否则每次服务启动都要手动输密码对自动化部署是灾难-days 825是有效期825 天是很多浏览器对证书有效期的上限参考值超过可能被拒-subj是主题信息避免交互式提问-addext就是加 SAN。执行完当前目录下就有了server.key和server.crt。4.2 验证证书内容是否正确生成完别急着用先自检一遍openssl x509 -in server.crt -noout -text重点看三处Subject里的 CN、X509v3 Subject Alternative Name里的域名和 IP、Validity里的起止时间。SAN 里必须包含你实际访问用的地址一个都不能少。还可以验证私钥和证书是否匹配openssl x509 -noout -modulus -in server.crt | openssl md5 openssl rsa -noout -modulus -in server.key | openssl md5两条命令输出的 MD5 值必须一致不一致说明私钥和证书不是一对配上去服务起不来。4.3 直接自签方案的适用边界这套流程适合本地开发、临时演示、单机测试。它的短板在于每换一台设备就要重新信任一次证书。如果你只有一两台机器无所谓如果是团队十几台设备手动导入根证书的体验会让人崩溃。这时候就该上自建 CA 方案了。5. 方案二自建 CA 签发整套证书5.1 生成根私钥和根证书先造根。根私钥是整个体系的命根子建议用 4096 位cd ~/certs/ca openssl genrsa -out ca.key 4096 chmod 600 ca.key然后用根私钥自签一张根证书openssl req -x509 -new -nodes -key ca.key \ -sha256 -days 3650 -out ca.crt \ -subj /CCN/STLocal/LLocal/OMyInternalCA/CNMyInternalRootCA根证书有效期给长一点3650 天十年比较常见因为根证书过期会导致它签发的所有证书全部失效换根成本很高。5.2 编写服务器证书的扩展配置文件这一步是自建 CA 的核心。直接签发的服务器证书需要一份配置文件来声明 SAN 和密钥用途。新建server.extcat server.ext EOF authorityKeyIdentifierkeyid,issuer basicConstraintsCA:FALSE keyUsage digitalSignature, keyEncipherment extendedKeyUsage serverAuth subjectAltName alt_names [alt_names] DNS.1 localhost DNS.2 dev.internal IP.1 127.0.0.1 IP.2 192.168.1.100 EOFextendedKeyUsage serverAuth表示这张证书用于服务器身份验证subjectAltName就是实际生效的域名列表。authorityKeyIdentifier和basicConstraints帮助浏览器正确构建信任链。5.3 生成服务器私钥、CSR 并用根证书签发先生成服务器私钥和证书签名请求CSRcd ~/certs/server openssl genrsa -out server.key 2048 chmod 600 server.key openssl req -new -key server.key -out server.csr \ -subj /CCN/STLocal/LLocal/ODev/CNlocalhost然后用根证书签发注意带上扩展文件openssl x509 -req -in server.csr \ -CA ~/certs/ca/ca.crt -CAkey ~/certs/ca/ca.key \ -CAcreateserial -out server.crt \ -days 825 -sha256 -extfile server.ext-CAcreateserial会生成一个序列号文件用于记录已签发的证书序号避免重复。执行完server.crt就是由你的根签发的服务器证书了。5.4 验证信任链是否完整验证一下签发关系openssl verify -CAfile ~/certs/ca/ca.crt server.crt输出server.crt: OK就说明信任链没问题。如果报错通常是扩展文件没生效或者根证书路径写错了。6. 把证书用起来服务端配置与客户端信任6.1 在常见服务中挂载证书以 Nginx 为例配置片段如下server { listen 443 ssl; server_name localhost; ssl_certificate /home/user/certs/server/server.crt; ssl_certificate_key /home/user/certs/server/server.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; }如果是 Node.js用https模块const https require(https); const fs require(fs); https.createServer({ key: fs.readFileSync(server.key), cert: fs.readFileSync(server.crt) }, (req, res) { res.end(hello over https); }).listen(443);Python 的 Flask 或 FastAPI 也类似把ssl_context指向证书和私钥即可。6.2 让客户端信任你的根证书服务端配好了客户端还得信任你的根。Linux 下把根证书拷进系统信任目录sudo cp ~/certs/ca/ca.crt /usr/local/share/ca-certificates/myinternalca.crt sudo update-ca-certificatesWindows 和 macOS 则是把ca.crt导入系统钥匙串并手动设为“始终信任”。浏览器如果用自己的信任库比如某些独立打包的浏览器还需要在浏览器设置里单独导入一次。注意导入根证书时一定要确认来源是你自己的ca.crt不要从别处随便下载根证书导入那等于把信任权交给了别人。6.3 双向认证的额外一步如果你的服务需要验证客户端身份还要生成客户端证书签发流程和服务器证书一样只是扩展文件里把extendedKeyUsage改成clientAuth。服务端配置里加上ssl_client_certificate指向根证书并开启ssl_verify_client on。这样只有持有你签发的客户端证书的设备才能连上。7. 常见问题与排查技巧实录7.1 浏览器仍然报错的几种情况证书生成了、服务也起来了浏览器还是报警这是最高频的问题。我整理了一张排查表现象可能原因排查方法提示名称不匹配SAN 里没有当前访问地址openssl x509 -text看 SAN提示证书不受信任根证书未导入客户端检查系统信任库是否含 ca.crt提示证书已过期系统时间不对或有效期已过对比Validity和当前时间提示弱签名算法用了 SHA-1签发时加-sha256连接直接被拒私钥权限或路径错误看服务日志确认 key 可读7.2 私钥加密带来的启动失败如果你生成私钥时没加-nodes私钥是加密的服务启动时会卡在输入密码上自动化部署直接失败。补救办法是解密私钥openssl rsa -in server.key -out server_nopass.key然后替换配置里的私钥路径。我个人的习惯是内网证书一律不加密私钥靠文件权限600来保护。7.3 SAN 配置写错的典型表现SAN 写错有两种常见形态。一种是格式错误比如IP:127.0.0.1写成了DNS:127.0.0.1浏览器按域名去解析一个 IP自然匹配不上。另一种是漏写比如只写了localhost但你实际用127.0.0.1访问。记住一个原则你用什么地址访问SAN 里就必须有什么地址域名和 IP 要分别用DNS:和IP:前缀声明。7.4 证书有效期与轮换的实操心得自签证书虽然没人管你但有效期设太长也有风险。我的做法是服务器证书给 825 天根证书给 3650 天并且在日历上设个提醒到期前一个月重新签发。轮换时只需要替换服务器证书和私钥根证书不动客户端不用重新导入影响面最小。另外签发新证书前记得备份旧的万一新证书有问题可以快速回滚。我一般会在server/目录下按日期建子目录比如server/20250101/历史版本一目了然。8. 几个我踩过的坑和最后的小建议第一个坑是用 IP 访问却只配了 DNS。早期我做本地测试SAN 里只写了localhost结果同事用局域网 IP 访问浏览器直接报名称不匹配。后来我养成了习惯凡是可能用到的访问方式全部列进 SAN宁可多写不可漏写。第二个坑是根私钥权限没管好。有一次我把整个certs目录打包发给同事里面连根私钥一起给了出去。虽然内网环境风险可控但这个习惯很危险。现在我的做法是根私钥单独放打包分享时只给ca.crt和服务器证书根私钥永远不出本机。第三个坑是OpenSSL 版本差异导致的配置不生效。同一份server.ext在 1.1.1 上跑得好好的换到 3.x 上某些字段解析行为变了。解决办法是生成后用openssl x509 -text逐项核对确认扩展项真的写进去了而不是想当然。最后分享一个提效小技巧把整套生成流程写成一个 shell 脚本参数化域名和 IP需要新证书时改几个变量跑一遍就行。我自己的脚本里还加了自动校验步骤生成完立刻跑openssl verify和 SAN 检查有问题当场发现不用等到配到服务上才报错。这套流程跑熟之后从零到一张可用的内网证书五分钟以内就能搞定。