首页 / 资讯中心 / 文章详情

OpenSSL生成SSL证书实战:自签名、私有CA到Nginx配置全解析

OpenSSL生成SSL证书实战:自签名、私有CA到Nginx配置全解析 ★ FEATURED ARTICLE
1. 证书类型先选明白自签名、私有CA还是公开CA做SSL证书生成之前第一个要解决的并不是怎么生成而是给谁用、用来干嘛。这个决定直接决定你走哪条路后面所有的命令、参数、信任配置全都不一样。我在实际带项目时见过太多人一上来就openssl req -x509生成一个自签名证书然后用在内网生产环境被同事吐槽浏览器红屏警告还以为是证书格式错了。其实不是格式问题是信任链根本没有建立。自己签发的证书浏览器不认识签发者自然不肯给绿锁。分一下场景就很清楚了本地开发调试、联调环境要求不高能加密就行那就是自签名证书。内网多台服务器、多个服务之间互相调用自己维护一套私有CA把根证书装到所有客户端的信任库里让服务证书由这个CA统一签发。线上公网对外服务比如官网、小程序后端接口、App的HTTPS端口那必须是公开CA签发的证书最好直接用自动化方案申请。这三种路径的生成方式和命令完全不同后面我会拆开讲。先说一个判断标准你的用户浏览器是否认识这个签发者。认识就用私有方案不认识就得走公开CA路径。公网域名那部分因为涉及域名所有权验证我不过多展开网上教程一抓一大把用的工具无非就是acme.sh或者在面板里点几下流程已经很成熟了。反倒是自签名和私有CA这套很多文档写得零散实际踩坑最多下面重点讲这两块。2. 环境准备OpenSSL的版本、目录规划、配置文件2.1 先确认OpenSSL版本不要用太老的生成证书最核心的工具就是OpenSSL几乎所有的命令都是围绕它来的。先检查一下你本机的版本openssl version不同版本之间有一些行为差异。OpenSSL 1.1.1以上的版本对密钥格式、签名算法、默认参数的处理都更规范建议至少是1.1.1最好是3.x。如果是老掉牙的1.0.2比如某些还停在CentOS 6阶段的机器我建议先升级再往下走否则后面配ECC证书、配TLS1.3的时候会有各种想不到的问题。Windows环境下如果没有openssl可以选择Git Bash自带的openssl路径一般是C:\Program Files\Git\usr\bin\openssl.exe能用但版本可能不是最新。直接下载Win64版的OpenSSL安装包装完把bin目录加进PATH。有Docker环境的一条docker run --rm -it -v $(pwd):/certs alpine/openssl就能跑起来目录映射后证书直接生成在当前目录里这台机器的openssl版本往往还挺新。我个人在Windows上更推荐直接用WSL或者Docker省去环境变量和路径反斜杠的折腾。每台机器路径浅显但是坑深后面生成的权威文件全部要在项目目录里统一管理建议直接建立一个约定好的目录结构。2.2 目录结构规划证书文件绝不能乱放证书文件不是只生成一次就完事的后续的续期、排错、部署全都依赖于一份清晰的文件管理。我习惯的目录规划是这样的certs/ |-- root/ | |-- rootCA.key | |-- rootCA.pem | |-- rootCA.srl | -- openssl.cnf |-- server/ | |-- server.key | |-- server.csr | |-- server.crt | -- fullchain.pem |-- client/ -- archive/几个关键点说明一下rootCA.key这种根CA的私钥权限要收紧建议600权限甚至可以考虑离线保存签发证书的时候才临时拿出来。rootCA.srl是CA签发序列号文件第一次签发前需要手动创建否则会报错。fullchain.pem是证书链文件Nginx这类服务通常配置它而不是单独的server.crt。建议所有过期的证书备份到archive/目录这不仅是合规要求也是事后排查问题的重要依据。别小看这个目录规划。我收到过不少求助对方发来一串意思说不清楚的文件就是因为他们把ca证书、服务器证书、私钥全部生成在/tmp后来重新部署的时候全都找不到了只能重新生成。重新生成的后果是私钥变了如果客户端已经固定了证书指纹那就全得跟着重新分发一遍那工作量就大了。3. 核心实操三种场景下的SSL证书生成完整流程3.1 场景一本地开发自签名证书这是最轻量、最常用的一种。通过配置一个模拟域名来演示比如我们内部团队习惯用dev.local这种不会真实解析的假域名。生成的目的是在本地启动HTTPS服务时不被浏览器的显示策略直接拦死。3.1.1 一步命令生成最简单的方式一条命令完成openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -keyout dev.local.key -out dev.local.crt -days 365 -nodes -subj /CCN/STBeijing/LBeijing/ODevTeam/CNdev.local命令参数逐一拆解reqOpenSSL处理证书请求和自签名证书的子命令。-x509直接输出自签名证书而不是CSR。-newkey ec生成新私钥算法是ECC椭圆曲线密钥现代TLS推荐做法。-pkeyopt ec_paramgen_curve:P-256指定曲线为P-256这也是TLS握手兼容性最好的一条曲线。-nodes不加密私钥。本地测试无所谓生产环境不要这么干。-days 365有效期一年本地测试够用了。-subj一次性填充证书里的全部subject信息不用交互式问答。如果是老系统不支持-pkeyopt ec_paramgen_curve:P-256这种写法就退回RSAopenssl req -x509 -newkey rsa:2048 -keyout dev.local.key -out dev.local.crt -days 365 -nodes -subj /CCN/STBeijing/LBeijing/ODevTeam/CNdev.localRSA 2048和ECC P-256在当前都是安全的但ECC生成的密钥体积更小握手速度更快新项目建议直接切ECC。3.1.2 生命周期管理用配置文件代替交互命令行插一句经验。如果经常要生成证书就别每次敲一整条命令还带一堆交互问答了写一个配置文件一劳永逸。以下是我项目里常用的openssl.cnf[req] distinguished_name req_distinguished_name prompt no x509_extensions v3_req [req_distinguished_name] C CN ST Beijing L Beijing O DevTeam CN dev.local [v3_req] basicConstraints CA:FALSE keyUsage digitalSignature, keyEncipherment subjectAltName alt_names [alt_names] DNS.1 dev.local DNS.2 api.dev.local IP.1 127.0.0.1然后生成证书时直接指定配置文件openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -keyout dev.local.key -out dev.local.crt -days 825 -nodes -config openssl.cnf这版本的-days 825不是随意写的是Chrome从2020年之后对所有TLS证书有效期的硬限制。超过这个天数Chrome一律拒绝信任。这个有效期限制主要影响公开CA签发的公众受信证书自签名证书其实不受这个政策约束但统一按这个规则来养成习惯后面上公网证书时就不会踩坑了。配置文件的好处在于SANSubject Alternative Name主题备用名称在这里被明确定义。只写CN是没用的现代浏览器基本不看Common Name了全看SAN。你想让证书同时支持dev.local和api.dev.local两个域名靠多个文件重复执行就不如一个[alt_names]节来得干净。免交互适合脚本化、自动化生成。配置CN时注意要保持和实际访问的域名一致或至少保证域名出现在[alt_names]里否则浏览器会报域名不匹配的加密警告。3.2 场景二私有CA签发服务器证书当内网有几十台服务器每台都各自签一个自签证书客户端那边就信仰杂乱了。轻巧的方式是搭一个私有CA它只在你自己的网络里被信任但一次信任之后里面签发出来的所有服务器证书都连续可用。这个流程分解为四个步骤创建根CA、创建服务器私钥和CSR、用根CA签发、给客户端配信任。3.2.1 创建根CA# 生成根CA私钥RSA 4096密钥长度更高一些更安全 openssl genrsa -out rootCA.key 4096 # 生成自签名根CA证书有效期建议10年 openssl req -x509 -new -key rootCA.key -sha256 -days 3650 -out rootCA.pem -subj /CCN/STBeijing/LBeijing/ODemoOrg/CNDemoOrg Root CA这里我把-sha256单独拿出来说。签名哈希算法现在最低也得是SHA-256再往下的SHA-1已经被判死刑了你就算生成出来浏览器也不认纯白费功夫。根CA证书有效期十年是一个合理区间长是为了减少根证书的轮换频率。根CA因为密钥不能太长保留RSA 4096是底线有条件可以考虑更高的但4096已经是性能和安全的均衡点。同时创建一个序列号文件echo 01 rootCA.srl这个文件记录下一次签发的证书序号。如果缺失签发的时候OpenSSL会给一个明确报错提示你创建它。3.2.2 生成服务器私钥和CSR根CA就位后生成服务器自己的私钥和证书请求文件openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out server.key openssl req -new -key server.key -subj /CCN/STBeijing/LBeijing/ODemoOrg/CNapp.demo.local -addext subjectAltNameDNS:app.demo.local,DNS:api.demo.local,IP:10.24.1.8 -out server.csr这里有个重点要画出来-addext subjectAltName...这种方式直接一次性把SAN写进CSR里。记得把SAN放进CSR因为签发的时候如果CSR里没有你需要额外去配置CA侧的扩展来回折腾。内网证书不仅要支持域名还常需要支持IP地址直接访问比如某些服务就是通过https://10.24.1.8:8443访问的。那就把IP:10.24.1.8写进SAN。光写IP不写DNS或者反过来都不完整按实际需求配全。3.2.3 用根CA签发服务器证书签发命令需要指定扩展配置文件因为要在证书里加上serverAuth用途和SANopenssl x509 -req -in server.csr -CA rootCA.pem -CAkey rootCA.key -CAcreateserial -out server.crt -days 825 -sha256 -extfile server_ext.cnf -extensions server_ext其中server_ext.cnf的内容如下[server_ext] basicConstraints CA:FALSE keyUsage digitalSignature, keyEncipherment extendedKeyUsage serverAuth subjectAltName alt_names [alt_names] DNS.1 app.demo.local DNS.2 api.demo.local IP.1 10.24.1.8有几个细节值得注意-CAcreateserial和之前手动建rootCA.srl可以二选一你用哪一个都行但别同时存在两个相互覆盖的机制容易把序列号搞乱。extendedKeyUsage serverAuth限定这个证书只能用于服务端身份认证。有些系统里同一个证书既被当客户端证书又被当服务端证书用这种场景要同时加clientAuth写成extendedKeyUsage serverAuth, clientAuth。有效期我统一设825天就是呼应前面浏览器证书限制虽然私有CA不受这种强制约束但统一采用这个时间就绝对不会过期被浏览器拒。3.2.4 验证证书内容签发完之后必须做验证否则部署完了才发现SAN错了就太晚了openssl x509 -in server.crt -text -noout只看两个关键部分Subject:这一行有没有写对。X509v3 Subject Alternative Name:这一行是不是已经包含了你需要的域名和IP。顺带可以验证证书链是否完整openssl verify -CAfile rootCA.pem server.crt输出server.crt: OK就是通过。如果报错说找不到签发者就是根CA和服务器证书没对上仔细检查是不是拿错文件了。3.3 场景三公网证书自动化申请这个场景下公网证书基础设施已经很成熟了流程反而比私有CA简单。用acme协议自动化申请例如Lets Encrypt风格的免费证书通过DNS API或者HTTP文件验证域名所有权拿到证书后自动续期。我不打算在这里展开全部细节只给出一条比较通用的安装路径# 通过包管理器安装 apt install certbot # 如果服务是Nginx直接用nginx插件自动配置 certbot --nginx -d app.example.com -d api.example.com如果你不想安装额外工具也可以用纯shell脚本配合DNS服务商的API实现自动申请核心逻辑就是把一个_acme-challenge的TXT记录添加到你控制的域名下。这部分的坑主要在域名所有权验证环节有CDN代理时HTTP验证可能失败DNS服务商的API权限只给了子域名TXT记录也添不上去。哪个环节不通就排查哪一步问题往往出在权限配置而不是工具本身。4. 让证书真正生效Nginx、应用服务器与本地信任配置生成证书只是第一步证书放进服务里能正常握手才是在工作时间真正意义上的生成明细。4.1 Nginx配置HTTPS最常用、也是我刚入行时最容易配错的就是Nginx这一环节。先给一个标准配置片段server { listen 443 ssl http2; server_name app.demo.local; # 证书路径 ssl_certificate /etc/nginx/certs/server.crt; ssl_certificate_key /etc/nginx/certs/server.key; # 链配置有些环境需要把根CA也拼接进去 # ssl_certificate /etc/nginx/certs/fullchain.pem; # 协议优化 ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; ssl_prefer_server_ciphers off; # 会话缓存 ssl_session_timeout 1d; ssl_session_cache shared:SSL:50m; ssl_session_tickets off; # 可选强制跳转HTTP到HTTPS # return 308 https://$host$request_uri; }配置完成后重载Nginx/usr/sbin/nginx -t systemctl reload nginx一个很多新手会踩的坑只配置了server.crt但server.crt里只包含叶子证书没有包含中间证书或者根CA。对浏览器来说它本地有根CA的话也许能自己去找中间证书但在内网私有CA场景下客户端本地已经有根CA的情况不会依旧没有中间证书浏览器就无法正常构建信任链。解决方式是把根CA证书内容追加到服务器证书后面形成fullchain.pem配置时用这个合并文件。合并命令cat server.crt rootCA.pem fullchain.pem注意顺序先叶子后根。顺序反了Nginx也不会报错但有些客户端解析会有问题最好统一按标准来。4.2 Java应用配置HTTPSJava应用比如Spring Boot、内网管理系统跟Nginx不太一样它要么直接用证书放到classpath要么用keytool导入。假如拿到的是PEM格式的证书和私钥想转成Java用的PKCS12格式openssl pkcs12 -export -in server.crt -inkey server.key -certfile rootCA.pem -out keystore.p12 -name appdemo -passout pass:changeit然后在application.yml里根据实际框架指示指定server.ssl.key-store: classpath:keystore.p12、server.ssl.key-store-password: changeit、server.ssl.key-store-type: PKCS12。如果是纯内网环境没有域名只有IP地址Java的TLS握手有时依然正常但最好在SAN里把IP加上否则一些严格模式的Java版本会拒绝校验域名一致。4.3 客户端信任配置私有CA最后一步是把根CA装进所有需要访问服务的那一端。桌面端浏览器Windows环境双击rootCA.pem选择安装证书存储位置选本地计算机放入受信任的根证书颁发机构。注意如果选当前用户浏览器以管理员权限运行时可能找不到证书导致仍然报错。装完以后重启浏览器访问内网地址绿锁就出来了。Linux命令行环境Ubuntu/Debian路径把rootCA.pem复制到/usr/local/share/ca-certificates/然后执行sudo update-ca-certificates。CentOS/RHEL路径把rootCA.pem复制到/etc/pki/ca-trust/source/anchors/然后执行sudo update-ca-trust extract。Java环境通过KeyStore导入keytool -import -alias demoRootCA -keystore cacerts -file rootCA.pem -storepass changeit -noprompt不要覆盖生产环境的cacerts先备份原文件。移动端相对特殊。Android对用户安装的根证书和系统级根证书区分明显不同版本行为差异较大iOS则要求在描述文件里配置信任开关。考虑到篇幅这里不展开每个细节操作核心理解是让所有客户端都信任你的签发者这是私有CA方案的全部本质。5. 常见问题与排错实录5.1 证书提示不安全的几类原因把我在项目中实际遇到的高频问题整理成一张速查表问题现象常见原因排查方式浏览器显示NET::ERR_CERT_AUTHORITY_INVALID根CA未装到客户端或证书链不完整检查客户端受信任根列表里有没有你的根CA检查Nginx配的是不是fullchain显示NET::ERR_CERT_COMMON_NAME_INVALID证书的SAN与访问域名不匹配openssl x509 -in server.crt -text -noout查看SAN显示证书已过期有效期设置过短或服务器时间不准检查证书有效期和服务器的系统时间Java客户端报PKIX path building failedJava信任库没有根CA用keytool导入根CA到cacertscurl报SSL certificate problemcurl调用时没指定--cacert或系统根证书环境没有更新更新系统根证书库或curl加--cacert rootCA.pem安卓手机不识别内网证书部分App只信任系统级根证书考虑用系统证书安装方式或走公开CA5.2 私钥文件权限与保管私钥文件权限过宽是个很常见的安全隐患。我在公司巡检时多次发现私钥文件权限是644意味着普通用户也能读。正确做法chmod 600 server.key chmod 600 rootCA.key还有一个私钥相关的经验教训曾经遇到一个项目服务器上配置的负载均衡证书私钥和其它节点上的不一样导致负载分发到不同节点时握手结果不一致客户端一会儿成功一会儿失败用户以为是网络抖动实际是证书私钥不一致酿成的。多节点部署时所有节点必须用完全相同的证书和私钥这是运维层面的基本准则。5.3 证书续期与轮换私有CA签发的证书到期前手动续期流程也不复杂还是老流程CSR重新生成、重新签发。但有一个更省事的做法——如果你原私钥没有泄露完全可以用同一个私钥和CSR重新走一遍签发流程。这样做的好处是私钥不变很多已经配置好的客户端不会因为密钥更换而产生额外变化。日常巡检建议在证书到期前至少30天做续期别把时间卡得太死。我见过有同事周五下午开始续期结果发现根证书本身也快过期了然后周末加班推证书链替换。这种教训一次就够了。自动化续期的方向倒可以提一下私有CA场景也可以写脚本定时检查证书剩余天数低于阈值就自动重新签发然后用CI/CD管道部署到目标服务器。5.4 常见命令片段速查最后给一套常用操作速查查看证书有效期openssl x509 -in server.crt -noout -dates查看证书的SANopenssl x509 -in server.crt -noout -ext subjectAltName校验私钥和证书是否匹配openssl pkey -in server.key -pubout | openssl sha256 openssl x509 -in server.crt -pubkey -noout | openssl sha256两行结果一致私钥和证书才是一对。查看证书链内容openssl crl2pkcs7 -nocrl -certfile fullchain.pem | openssl pkcs7 -print_certs -noout这个命令能列出fullchain.pem中的所有证书确认是否按顺序包含了叶子证书和根CA。6. 一点长期实践心得在我自己的经验里SSL证书生成这件事的本质归纳起来就是三点私钥安全、信任链完整、生命周期可控。私钥安全靠权限控制和保密习惯信任链完整靠SAN编写、fullchain合并、客户端根证书安装这三件套生命周期可控靠有效期规划和自动化巡检。把这三个维度梳理顺了不管自签名、私有CA还是公网证书都不会再觉得混乱。最后提一个团队协作的习惯把证书相关的文件路径、过期时间、签发者信息统一记录在一个certs/README.md里交接工作或团队扩容时大有用处。这个文件不需要写得多么正式几行简单的表格就能避免很多这张证书是谁签发的、私钥在哪之类的无效沟通。证书生成是门手艺活多生成几套、多踩几个坑自然就顺手了。
阅读完成 · 觉得有帮助?
咨询建站