私钥、CSR 与证书
OpenSSL 自签名证书:SAN、localhost 与信任安装
自签名证书是用自己的私钥给自己签名、不经过 CA 的证书,OpenSSL 3.x 用一条 openssl req -x509 就能连私钥一起生成。它的加密和正规证书没区别,但任何客户端在你手动安装之前都不信任它,所以适合本地开发、内网设备、测试环境,不适合对外网站。输出示例来自 OpenSSL 3.6.1。
一条命令生成带 SAN 的自签名证书
req -x509 跳过 CSR 直接出证书,-newkey 同时生成私钥。
openssl req -x509 -newkey rsa:2048 -sha256 -days 365 -noenc \
-keyout example.com.key -out example.com.crt \
-subj "/CN=example.com" \
-addext "subjectAltName=DNS:example.com,DNS:*.example.com"
openssl x509 -in example.com.crt -noout -subject -dates -ext subjectAltName
subject=CN=example.com
notBefore=Nov 9 18:17:00 2026 GMT
notAfter=Nov 9 18:17:00 2027 GMT
X509v3 Subject Alternative Name:
DNS:example.com, DNS:*.example.com
客户端只认 SAN,只写了 CN 的证书在所有现代浏览器里都会报域名不匹配。-addext 需要 OpenSSL 1.1.1 以上。不想敲命令可以用自签名证书生成工具,在浏览器本地生成同样的文件。
生成 ECDSA 自签名证书
把密钥类型换成 P-256 曲线的 ec,证书更小、握手更快。
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -noenc -days 365 \
-keyout example.com.key -out example.com.crt \
-subj "/CN=example.com" \
-addext "subjectAltName=DNS:example.com,DNS:www.example.com,IP:203.0.113.10"
openssl x509 -in example.com.crt -noout -text | grep -E "Signature Algorithm|Public-Key|IP Address" | head -3
Signature Algorithm: ecdsa-with-SHA256
Public-Key: (256 bit)
DNS:example.com, DNS:www.example.com, IP Address:203.0.113.10
所有现代客户端都支持 ECDSA P-256。-newkey ed25519 也能生成 Ed25519 自签证书,但大多数浏览器不接受 Ed25519 的 TLS 证书,只适合内部 mTLS。
用已有私钥或 CSR 自签
想沿用私钥就在 req -x509 里用 -key;手头已经有 CSR 就用 x509 -req。
# 已有私钥
openssl req -x509 -key example.com.key -days 365 -subj "/CN=example.com" \
-addext "subjectAltName=DNS:example.com" -out example.com.crt
# 已有 CSR(把 CSR 里的 SAN 复制进证书)
openssl x509 -req -in example.com.csr -key example.com.key -days 365 \
-copy_extensions copy -out example.com.crt
Certificate request self-signature ok
subject=CN=example.com
x509 -req 默认丢弃 CSR 里的扩展,必须加 -copy_extensions copy(OpenSSL 3.0 起)或用 -extfile 另行指定。自签证书“没有 SAN”多半就是这个原因。
设置有效期
-days 决定有效期,不写的话 req -x509 只给 30 天。
openssl req -x509 -newkey rsa:2048 -noenc -keyout n.key -out n.crt -subj "/CN=example.com"
openssl x509 -in n.crt -noout -dates
notBefore=Nov 9 18:17:00 2026 GMT
notAfter=Dec 9 18:17:00 2026 GMT
很多测试环境“一个月后突然坏了”就是这个默认值导致的。较新的 3.x 还支持用 -not_before、-not_after 写绝对日期(用 openssl req -help 确认)。有效期别超过 825 天,否则苹果设备即使手动信任也会拒绝。
生成 localhost 开发证书
明确标成非 CA 的服务器证书,同时写上域名和回环地址。
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -noenc -days 825 \
-keyout localhost.key -out localhost.crt -subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1,IP:::1" \
-addext "basicConstraints=critical,CA:FALSE" \
-addext "keyUsage=critical,digitalSignature" \
-addext "extendedKeyUsage=serverAuth"
openssl x509 -in localhost.crt -noout -ext basicConstraints,extendedKeyUsage,subjectAltName
X509v3 Subject Alternative Name:
DNS:localhost, IP Address:127.0.0.1, IP Address:0:0:0:0:0:0:0:1
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Extended Key Usage:
TLS Web Server Authentication
默认配置下 req -x509 会加 CA:TRUE,上面显式写的 basicConstraints 会覆盖它。苹果要求 TLS 服务器证书带 serverAuth EKU。RSA 私钥把 keyUsage 写成 critical,digitalSignature,keyEncipherment。前端开发用 Vite、webpack-dev-server 时,把这两个文件路径填到 https 配置即可。
用 -noenc 代替 -nodes
OpenSSL 3.0 把“不加密私钥”的参数从 -nodes 改名为 -noenc。
openssl req -help 2>&1 | grep -E "noenc|nodes"
-noenc Don't encrypt private keys
-nodes Don't encrypt private keys; deprecated
3.x 两个都能用,老脚本不用改。新脚本写 -noenc;OpenSSL 1.1.1 只认 -nodes。两个都不写时 OpenSSL 会要求设置私钥密码,Nginx 每次启动都会卡在输密码。
用 s_server 和 curl 测试
起一个临时 TLS 服务,用 --cacert 连接,确认证书和私钥能正常配合。
openssl s_server -accept 8443 -cert localhost.crt -key localhost.key -www &
curl https://localhost:8443/ -o /dev/null
curl --cacert localhost.crt https://localhost:8443/ -o /dev/null -w "%{http_code}\n"
echo | openssl s_client -connect localhost:8443 -servername localhost 2>&1 | grep "Verify return"
curl: (60) SSL certificate problem: unable to get local issuer certificate
200
Verify return code: 18 (self-signed certificate)
不带 --cacert 时报 60 号错误是预期结果,详见 curl 60 报错。验证码 18 表示证书链终点是一张自己签自己、且不在信任库里的证书。测试完用 kill %1 关掉服务。
在 macOS 上信任证书
把证书作为受信任根加入系统钥匙串,Safari 和 Chrome 都使用 macOS 的信任设置。
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain localhost.crt
(成功时没有输出,系统可能要求输入密码或指纹)
以后不用了可以在“钥匙串访问”里删除,或执行 sudo security remove-trusted-cert -d localhost.crt。Firefox 有自己的证书库,需要在“设置 → 隐私与安全 → 证书 → 查看证书 → 证书颁发机构”里单独导入。
在 Windows 上信任证书
用管理员权限导入“本地计算机 → 受信任的根证书颁发机构”。
certutil -addstore -f Root localhost.crt
# 或者 PowerShell
Import-Certificate -FilePath .\localhost.crt -CertStoreLocation Cert:\LocalMachine\Root
Root "Trusted Root Certification Authorities"
Signature matches Public Key
Certificate "localhost" added to store.
CertUtil: -addstore command completed successfully.
Edge、Chrome,以及 360、QQ 浏览器这类 Chromium 内核浏览器都读 Windows 证书库。也可以双击 .crt 文件选“安装证书”,手动选择“受信任的根证书颁发机构”。certmgr.msc(当前用户)和 certlm.msc(本地计算机)可以查看和删除。
在 Ubuntu、Debian、CentOS/RHEL 上信任证书
把 PEM 文件放进系统信任目录,再重建证书包。
# Ubuntu / Debian:文件名必须以 .crt 结尾
sudo cp localhost.crt /usr/local/share/ca-certificates/localhost.crt
sudo update-ca-certificates
# CentOS / Rocky / Alibaba Cloud Linux / Fedora
sudo cp localhost.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust
Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.
这样 curl、wget、Go 程序和多数命令行工具都会信任它。Java 用自己的 cacerts,Node.js 默认用内置列表,需要设置 NODE_EXTRA_CA_CERTS;Linux 版 Chrome 读 ~/.pki/nssdb 里的 NSS 数据库。
排查浏览器为什么还报警
信任只是检查项之一,每种失败对应不同的报错。
| 现象 | 原因 | 处理 |
|---|---|---|
NET::ERR_CERT_AUTHORITY_INVALID |
没导入这个浏览器使用的证书库 | 按上文导入,Firefox 单独导入 |
NET::ERR_CERT_COMMON_NAME_INVALID |
访问的域名或 IP 不在 SAN 里 | 重新生成,把所有域名和 IP 写进 subjectAltName |
MOZILLA_PKIX_ERROR_CA_CERT_USED_AS_END_ENTITY |
证书带 CA:TRUE |
加 basicConstraints=critical,CA:FALSE |
| 只在 Mac 或 iPhone 上被拒 | 有效期超过 825 天,或缺 serverAuth EKU |
缩短 -days,加 extendedKeyUsage=serverAuth |
| 电脑正常、手机报错 | 手机要单独安装证书,iOS 还要在设置里开启完全信任 | 安装描述文件并开启信任 |
Firefox 的自签名报错见 MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT。HSTS 预加载的域名(例如 .dev 下的所有域名)遇到证书错误时浏览器不允许点“继续访问”。微信内置浏览器、小程序等场景基本无法让用户手动信任自签证书,这类场景只能用公网证书。
团队使用改用私有 CA
多台机器、多名开发者都要信任时,建一个根证书,用它给各个服务签证书。
# 只需一次:在每台设备上信任 ca.crt,然后按需签发服务证书
openssl x509 -req -in app.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-days 90 -extfile server.ext -out app.crt
openssl verify -CAfile ca.crt app.crt
Certificate request self-signature ok
subject=CN=example.com
app.crt: OK
有了私有 CA,每台设备只装一次根证书,服务证书可以随便轮换,不用每次重新信任。完整做法(含中间 CA、名称约束)见私有 CA。
常见问题
自签名证书是不是不如 CA 签的证书安全?
加密强度完全一样,差别在于没有第三方担保“这把公钥属于这个域名”,所以每个客户端都要手动信任它。开发环境、内网设备可以用;对外网站请用免费的 Let's Encrypt 等公网证书。
开发用的自签名证书有效期设多长?
一年以内比较合适,最长不要超过 825 天。从 iOS 13 和 macOS 10.15 起,苹果系统拒绝 2019 年 7 月 1 日之后签发、有效期超过 825 天的 TLS 服务器证书,手动信任了也不行。
能给内网 IP 签自签名证书吗?
可以,在 subjectAltName 里写 IP:203.0.113.10 或 IP:192.168.1.10。客户端只拿 IP 去匹配 IP 类型的 SAN,写在 CN 或 DNS 类型里都不算。