Web 服务器
Tomcat 配置 SSL 证书(PEM 格式)
Tomcat 的 SSLHostConfig 可以直接引用 PEM 格式的证书文件,不用转换成 JKS 或 PKCS12。做法是用 Certbot 申请 Let's Encrypt 证书,部署钩子把文件复制给 Tomcat,再在 443 端口配置 HTTPS 连接器,HTTP 自动跳转。
适用于 Tomcat 10.1、11.0(9.0 的 SSLHostConfig 写法相同),Linux + Java 17 及以上,Certbot 4.1 及以上。
前提条件
Certbot 首次签发要用 80 端口跑临时验证服务,Tomcat 需要有绑定 443 的权限。
- Tomcat 以 systemd 服务方式运行,用户为
tomcat,CATALINA_BASE为/opt/tomcat(按实际安装路径调整)。 - 已用 snap 安装 Certbot:
sudo snap install --classic certbot && sudo ln -s /snap/bin/certbot /usr/local/bin/certbot。 example.com的 A/AAAA 记录指向服务器,例如203.0.113.10;服务器在中国大陆的需已备案。- 防火墙和安全组放行 TCP 80、443,80 端口当前没有被占用。80 端口不通时改用 acme.sh DNS 验证 签发。
第一步:申请证书
80 端口空闲时,用 standalone 模式让 Certbot 临时起一个 Web 服务完成 HTTP-01 验证:
sudo certbot certonly --standalone -d example.com -d www.example.com
证书在 /etc/letsencrypt/live/example.com/:cert.pem(站点证书)、chain.pem(中间证书)、fullchain.pem 和 privkey.pem。
第二步:把证书复制到 Tomcat 可读的位置
/etc/letsencrypt/live 只有 root 能读。写一个部署钩子,每次签发和续期后复制文件并设置属主:
sudo tee /etc/letsencrypt/renewal-hooks/deploy/tomcat.sh > /dev/null <<'EOF'
#!/bin/sh
set -e
dest=/opt/tomcat/conf/ssl
install -d -o tomcat -g tomcat -m 700 "$dest"
install -o tomcat -g tomcat -m 644 "$RENEWED_LINEAGE/cert.pem" "$dest/cert.pem"
install -o tomcat -g tomcat -m 644 "$RENEWED_LINEAGE/chain.pem" "$dest/chain.pem"
install -o tomcat -g tomcat -m 600 "$RENEWED_LINEAGE/privkey.pem" "$dest/privkey.pem"
systemctl restart tomcat
EOF
sudo chmod 755 /etc/letsencrypt/renewal-hooks/deploy/tomcat.sh
sudo RENEWED_LINEAGE=/etc/letsencrypt/live/example.com /etc/letsencrypt/renewal-hooks/deploy/tomcat.sh
第三步:配置 HTTPS 连接器
编辑 /opt/tomcat/conf/server.xml,添加 443 的 HTTPS 连接器,并把 HTTP 连接器的 redirectPort 指向它:
<Connector port="80" protocol="HTTP/1.1"
connectionTimeout="20000" redirectPort="443" />
<Connector port="443" protocol="org.apache.coyote.http11.Http11NioProtocol"
SSLEnabled="true" scheme="https" secure="true" maxThreads="150">
<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
<SSLHostConfig protocols="+TLSv1.2+TLSv1.3">
<Certificate certificateFile="conf/ssl/cert.pem"
certificateKeyFile="conf/ssl/privkey.pem"
certificateChainFile="conf/ssl/chain.pem" />
</SSLHostConfig>
</Connector>
相对路径以 CATALINA_BASE 为基准。certificateChainFile 不要填 fullchain.pem,它不能包含站点证书。
第四步:允许绑定 443 并强制 HTTPS
普通用户的 Java 进程默认不能绑定 1024 以下端口,在 systemd 里授予权限:
sudo systemctl edit tomcat
[Service]
AmbientCapabilities=CAP_NET_BIND_SERVICE
让所有请求跳转到 HTTPS,在 conf/web.xml(或应用自己的 web.xml)的 </web-app> 前加:
<security-constraint>
<web-resource-collection>
<web-resource-name>Entire site</web-resource-name>
<url-pattern>/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
现在 80 端口归 Tomcat 了,续期不能再用 standalone。改成 webroot 方式,由 ROOT 应用提供验证文件:
sudo systemctl daemon-reload && sudo systemctl restart tomcat
sudo certbot reconfigure --cert-name example.com --webroot -w /opt/tomcat/webapps/ROOT
验证
配置正确时,443 端口返回 Let's Encrypt 证书和完整证书链,HTTP 请求跳转到 HTTPS。用 SSL 证书检查 从外部检查,或在终端运行:
curl ssl.sh/example.com
在服务器上确认证书链和跳转:
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null 2>/dev/null | grep -E '^ *[0-9] s:'
curl -sI http://example.com | head -n 3
sudo journalctl -u tomcat -n 50 --no-pager | grep -i -E 'ssl|443'
自动续期
snap 版 Certbot 的定时器每天运行两次 certbot renew,4.1 及以上按 ARI 建议时间续期;续期成功后第二步的钩子复制新证书并重启 Tomcat。用演练检查验证路径:
sudo certbot renew --dry-run
演练不会执行部署钩子,第一次实际续期后要确认线上的到期时间。重启 Tomcat 会短暂断开连接;Tomcat 也提供了不重启重载 SSLHostConfig 的 JMX 操作,用法以对应版本的官方文档为准。Let's Encrypt 不再发到期提醒邮件,建议加到 ssl.sh 监控(开发中)。
常见问题排查
- 443 端口报 java.net.BindException: Permission denied:第四步的权限没生效,运行
sudo systemctl daemon-reload && sudo systemctl restart tomcat。 - Tomcat 启动了但连接器报私钥错误:
tomcat用户读不到privkey.pem,重新执行部署钩子。 - Java、安卓客户端报 PKIX 错误:证书链配错了,见 PKIX path building failed 和 缺少中间证书。
- 续期报连接超时:webroot 目录没有在
/.well-known/acme-challenge/下对外提供,见 certbot 连接超时。 - ERR_SSL_PROTOCOL_ERROR:443 上是明文 HTTP 连接器或漏了
SSLEnabled,见 ERR_SSL_PROTOCOL_ERROR。
常见问题
阿里云、腾讯云下载的 Tomcat 格式证书怎么用?
用 certificateKeystoreFile、certificateKeystorePassword 和 certificateKeystoreType="PKCS12"(JKS 填 JKS)直接引用。云厂商的 Tomcat 包一般是 PFX(PKCS#12)或 JKS 文件加一个密码文件。也可以下载 Nginx 格式的 PEM 文件,按 PEM 文件的方式配置(见第三步)。
Tomcat 自己做 HTTPS 好,还是放在 Nginx 后面好?
都可以。前面放 Nginx + Certbot,Tomcat 只监听 127.0.0.1:8080,换证书只要重载 Nginx,不用重启 Java 应用,HTTP/2 和跳转也更好配。没有代理层时才让 Tomcat 直接处理 TLS。
Java 客户端访问我的 Tomcat 报 PKIX path building failed?
通常是证书链文件缺失或放错了证书,客户端找不到受信任的根。certificateChainFile 指向 chain.pem,再用 SSL 检查确认,见 PKIX path building failed。