前两天有个同事抱着笔记本跑来找我说在阿里云上申请了一张免费的SSL证书下载下来发现是一堆.pem和.key文件而他手头的项目用的是 Tomcat 8一时不知道该怎么把这个证书配上让站点变成https://开头。这个问题其实挺典型的Tomcat 配 SSL 的方式和 Nginx 不太一样很多人第一次弄都会卡在证书格式和server.xml的配置上。我后来帮他把证书转换成了.pfx也就是 PKCS12 格式再改了几处 Tomcat 配置前后不到十分钟HTTPS 就正常访问了。这篇文章就把我这次完整的操作过程记录下来包括证书怎么申请、怎么从 PEM 转成 PFX、server.xml里到底要改哪些参数、HTTP 怎么自动跳转到 HTTPS以及我这些年踩过的坑和排查思路。如果你手头正好也有一台 Tomcat 8 需要上 HTTPS照着做就行。1. 配置前的准备先弄懂 HTTPS 和证书格式1.1 HTTPS 的核心价值与握手过程HTTPS 不是一种新的协议它本质上是“HTTP over TLS”也就是在 HTTP 和 TCP 之间加了一层 TLS 加密通道。这样做能解决三个问题第一传输内容加密防止中间人偷看第二数据完整性校验防止内容被篡改第三服务器身份认证防止用户连到一个假冒的站点。我习惯用一个“透明明信片 vs 密封信封”的类比来解释HTTP 就像寄明信片邮递员和沿途经手的人都能看到内容HTTPS 则像把信装进一个只有收件人有钥匙的信封里中途任何人都打不开也改不了。TLS 握手的关键步骤很简单——客户端先向服务器打招呼服务器把自己的证书包含公钥发过来客户端验证证书是否由受信任的 CA 签发、域名是否匹配、有没有过期没问题就双方协商出一个对称密钥之后的所有请求和响应都用这个密钥加密传输。这里有个很多人忽略的点TLS 握手默认走 443 端口所以你配置 HTTPS 时一般要把 Tomcat 的 SSL 连接器绑到 443 上这样才能直接用https://域名访问不用带上:8443这种端口号。对 Tomcat 8 来说配置 HTTPS 的核心思路就是准备好一个包含私钥和证书的密钥库文件然后在conf/server.xml里声明一个启用了 SSL 的Connector告诉 Tomcat 这个密钥库文件在哪、密码是什么、格式是什么。Tomcat 本身并不关心证书是哪个 CA 签的它只负责在握手阶段把证书发给客户端。1.2 PEM、PFX、JKS 三种证书格式怎么选接触过证书的人都知道同一个证书可以有好几种文件格式常见的有 PEM、PFXPKCS12、JKS 这三种。它们之间的区别和适用场景我整理成了一个表格式扩展名内容特点常见使用场景PEM.pem、.crt、.cer、.key纯文本Base64 编码一个文件只存证书或只存私钥Nginx、Apache、各种 Linux 服务PFX / PKCS12.pfx、.p12二进制证书和私钥打包在一个文件里可用密码保护Windows IIS、Tomcat、Java 程序JKS.jksJava 专属格式用keytool生成和管理老版本 Tomcat、Java 应用直接使用如果你从阿里云或腾讯云下载的是 Nginx 格式的证书拿到的通常是一个.pem证书文件和一个.key私钥文件这是 PEM 格式家族。而 Tomcat 8 传统上习惯用 JKS 或 PKCS12 格式的密钥库因为 Java 的KeyStore机制原生支持这两种格式。那为什么我推荐转成.pfx而不是 JKS原因有三个第一PFX 格式是跨平台通用的同一个文件既能给 Tomcat 用也能给 Windows IIS 或其他 Java 项目用以后迁移方便第二PFX 把证书链和私钥打包在一起配置时只需要指定一个文件和一个密码不容易漏配第三从 PEM 转 PFX 用一条openssl命令就能完成不需要额外安装 keytool 工具链。当然如果你不想转格式Tomcat 8.5 及以上版本也支持直接配置 PEM 格式的证书文件不过考虑到很多线上环境还是 8.0/8.5 混用统一用 PFX 更稳妥。1.3 自签名证书与 CA 签发证书什么场景用什么证书从来源上看可以分为自签名证书和 CA 签发的证书。自签名证书就是用openssl或keytool自己生成一个“自己签自己”的证书浏览器访问时会提示“不安全”需要用户手动信任。这种证书适合开发环境和内网测试用好处是免费、生成快、完全可控。CA 签发的证书则由受信任的证书颁发机构签名浏览器和操作系统内置了这些 CA 的根证书所以访问时不会出现安全警告。CA 证书又分付费的和免费的付费的 OV/EV 证书价格不低适合对身份验证要求高的企业免费的 DV 证书则非常适合个人网站、中小项目做 HTTPS 加密。阿里云、腾讯云、华为云都有免费证书的申请入口一般一张免费 DV 证书的有效期是 3 个月到 1 年可以反复续期。我个人的建议是测试环境一律用自签名证书别浪费时间去申请线上环境用云厂商的免费 DV 证书就够了除非你的业务涉及支付、用户登录等高敏感场景才考虑付费证书。下面的章节我会分别讲 CA 证书的申请与转换以及本地测试证书的快速生成方法。2. 证书获取与格式转换把 PEM 变成 PFX2.1 在阿里云申请免费 SSL 证书单域名如果你还没有证书最省事的办法是去阿里云控制台申请免费证书。流程大致是登录阿里云控制台搜索“SSL证书”进入证书服务页面选择“免费证书”然后创建一张免费证书。创建时会让你填一个域名比如www.example.com这里要注意免费 DV 证书基本都只支持单域名不支持*.example.com这类通配符域名。如果你有多个子域名要同时上 HTTPS要么给每个域名各申请一张证书要么花钱买通配符证书。提交申请之后阿里云会要求你做域名验证一般有两种方式DNS 验证和文件验证。DNS 验证是最常用的阿里云会给你一个解析记录你到域名的 DNS 管理后台添加一条 TXT 记录等几分钟到几小时验证通过后证书就会签发。文件验证则是把一段校验内容放到网站根目录下适合已有 HTTP 服务的场景。签发完成后在证书列表里点击“下载”会看到一个选择服务器类型的界面里面有 Nginx、Apache、Tomcat、IIS 等选项。选择 Nginx 下载解压后拿到的是xxx.pem和xxx.key两个文件。为什么选 Nginx 格式而不是直接选 Tomcat 格式因为 Tomcat 格式直接给的是 JKS 文件而 JKS 是 Java 专属的灵活性差我后面会用openssl把 PEM 转成 PFX这个路径最通用。2.2 使用 OpenSSL 将证书转换成 PFX 格式拿到.pem和.key之后转成.pfx只需要一条命令。先解释一下三个变量的含义server.pem是你的域名证书文件server.key是私钥文件chain.pem是证书链文件如果在 Nginx 下载包里看到了类似chain.crt或ca.crt的文件它就是证书链没有也没关系单张 DV 证书通常不需要额外配链。转换命令如下openssl pkcs12 -export \ -out server.pfx \ -inkey server.key \ -in server.pem \ -certfile chain.pem \ -passout pass:你的密码参数说明-export表示执行导出操作生成 PKCS12 文件。-out server.pfx指定输出文件名我习惯叫server.pfx。-inkey server.key指定私钥文件。-in server.pem指定证书文件。-certfile chain.pem可选参数如果有多级证书链就加上如果没有可以省略。-passout pass:你的密码为生成的 PFX 设置访问密码。这个密码非常重要后面配置 Tomcat 时keystorePass必须和它一致。命令执行后当前目录下就会生成一个server.pfx文件。你可以用下面的命令验证一下这个文件是否正常openssl pkcs12 -info -in server.pfx -nodes -passin pass:你的密码如果能看到证书和私钥的信息说明转换成功。如果提示No certificate matches private key说明-in和-inkey两个文件不匹配要么下载错了要么中间换过私钥需要重新生成证书或重新下载。2.3 没有证书时怎么在本地快速生成 PFX 测试如果你只是想在本地环境联调不想走申请流程可以直接用keytool生成一张自签名证书一步到位得到 PFX 文件。keytool是 JDK 自带的工具路径在$JAVA_HOME/bin/keytool不需要额外安装。生成命令如下keytool -genkeypair \ -alias tomcat \ -keyalg RSA \ -keysize 2048 \ -storetype PKCS12 \ -keystore server.pfx \ -validity 365 \ -storepass 你的密码 \ -dname CNlocalhost, OUTest, OTest, LBeijing, SBeijing, CCN这里有几个关键点-storetype PKCS12指定生成的是 PKCS12 格式也就是 PFX 文件。如果不写这个参数默认生成的是 JKS那就不叫server.pfx了。-keysize 2048密钥长度。现在 RSA 2048 是底线4096 更安全但握手稍慢测试用 2048 足够。-validity 365有效期天数测试环境设 365 天方便。-dname证书的 Subject 信息其中CN字段最重要表示证书绑定的域名。如果你是用https://localhost访问就填CNlocalhost如果用 IP 访问填CN127.0.0.1之类的。生成完同样会在当前目录出现server.pfx带密码保护。本地开发调试时用这个文件就够了浏览器首次访问会提示风险点“继续前往”即可。3. Tomcat 8 配置 HTTPS 实操3.1 连好第一步确认版本与备份配置文件动手改配置之前我先习惯性地确认三件事Tomcat 版本、JDK 版本、当前配置文件的备份情况。查看 Tomcat 版本可以进入 Tomcat 的 bin 目录执行./version.shWindows 下执行version.bat。输出里会有Server version: Apache Tomcat/8.5.xx这样的信息确认是 8 系就行。这里顺便提一句Tomcat 8.0 和 8.5 在 SSL 配置上略有差异8.5 开始支持在SSLHostConfig中直接配置 PEM 格式证书但默认的密钥库配置方式两个版本都兼容所以下面我给的写法在 8.0 和 8.5 上都能用。JDK 版本可以通过java -version查看。Tomcat 8 对 JDK 的最低要求是 1.7但线上我强烈建议用 JDK 1.8 或更高这样 TLS 协议和加密套件的支持更完善。备份配置文件是一个成本极低但收益极高的习惯。改server.xml之前先复制一份cp conf/server.xml conf/server.xml.bak-$(date %Y%m%d)万一配错了一条命令就能回滚不用靠记忆恢复。我见过太多人改完配置文件忘了备份出了问题只能从头重装 Tomcat实在没必要。3.2 server.xml 中的 SSL Connector 配置详解Tomcat 的所有连接器配置都在conf/server.xml里。默认的配置文件中有一段被注释掉的 8443 端口 SSL 连接器示例大致长这样!-- Connector port8443 protocolorg.apache.coyote.http11.Http11Protocol maxThreads150 SSLEnabledtrue SSLHostConfig Certificate certificateKeystoreFileconf/localhost-rsa.jks typeRSA / /SSLHostConfig /Connector --我们不要直接在这个示例上改更稳妥的做法是理解它的结构之后自己写一段清晰完整的配置。下面是我在生产环境里验证过的写法Connector port443 protocolorg.apache.coyote.http11.Http11NioProtocol maxThreads200 acceptCount100 connectionTimeout20000 SSLEnabledtrue schemehttps securetrue SSLHostConfig Certificate certificateKeystoreFileconf/server.pfx certificateKeystorePassword你的密码 certificateKeystoreTypePKCS12 / /SSLHostConfig /Connector逐个解释这些参数port443HTTPS 的默认端口。想用https://域名直接访问就必须用 443。如果只是测试也可以临时用8443访问时就得写成https://域名:8443。protocolorg.apache.coyote.http11.Http11NioProtocol使用 NIO 非阻塞模式相比默认的 BIO 模式在高并发下表现更好Tomcat 8 默认就是 NIO显式写出来更清楚。maxThreads200最大工作线程数。这个值不是越大越好要根据服务器 CPU 核数和业务耗时来定。简单估算公式是CPU核心数 * 2比如 4 核机器可以先给 200再压测观察。acceptCount100等待队列长度。当所有线程都在忙时新连接会进入这个队列满了之后才会拒绝连接。SSLEnabledtrue开启 SSL 支持这是 HTTPS 生效的必要条件。schemehttps和securetrue这两个参数告诉 Tomcat我 443 端口收到的请求应该被当作 HTTPS 请求处理。如果漏配应用层拿到的request.getScheme()还是会返回http有些框架做重定向或拼 URL 时就会出错。再看SSLHostConfig和Certificate里的三个关键属性certificateKeystoreFileconf/server.pfx指定 PFX 文件的位置。这里的相对路径是相对于 Tomcat 安装根目录的所以conf/server.pfx就表示把文件放在$CATALINA_HOME/conf/下。一定要确保这个路径是可读的而且不是软链指向临时目录。certificateKeystorePassword你的密码前面用openssl转换时-passout设置的密码。密码写错会直接导致启动失败或握手失败。certificateKeystoreTypePKCS12密钥库类型PFX 对应的就是PKCS12。如果不写这个参数Tomcat 会默认按照 JKS 格式去解析导致报Keystore was tampered with之类的错误。我的习惯是显式写出来不让 Tomcat 去猜。配置完成后把之前生成的server.pfx文件拷贝到$CATALINA_HOME/conf/目录下然后重启 Tomcat 让配置生效。3.3 让 HTTP 自动跳转到 HTTPS 的两种方式证书配好之后还有一个很现实的问题用户如果输入http://域名访问默认情况下 Tomcat 还是会把这个 80 端口的请求当作普通 HTTP 处理直接返回明文内容。这样一来用户可能根本不知道你的站点有 HTTPS也就没有加密保护了。我强烈建议顺手把 HTTP 强制跳转到 HTTPS 配上。最常见的做法是在 Tomcat 的全局conf/web.xml中添加一段安全约束。找到/web-app结束标签之前插入以下内容security-constraint web-resource-collection web-resource-nameall-except-download/web-resource-name url-pattern/*/url-pattern /web-resource-collection user-data-constraint transport-guaranteeCONFIDENTIAL/transport-guarantee /user-data-constraint /security-constraint这段配置的含义是所有/*路径的请求都要求使用CONFIDENTIAL即 HTTPS传输。当用户用 HTTP 访问时Tomcat 会根据连接器上的redirectPort配置把请求重定向到 443 端口对应的 HTTPS 地址上。注意前提是你的 HTTP 连接器存在并且配置了redirectPort。在server.xml里找到默认的 8080 连接器Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort443 /关键是redirectPort443这一项如果没有或者写成了 8443跳转就会跑到错误的端口上去。另一种方式是使用 Tomcat 8 自带的 RewriteValve通过重写规则把 HTTP 请求 301 到 HTTPS。这种方式更灵活但需要额外配置RewriteRule而且要注意带上[R301,L]标记否则不会真正跳转。就大多数场景而言用web.xml的安全约束最简单直接我日常也是这样配的。4. 启动验证与常见问题排查4.1 启动 Tomcat 并验证证书是否生效配置全部改完之后先不要急着用startup.sh后台启动我建议第一次用前台模式启动方便直接看到日志输出cd /usr/local/tomcat/bin ./catalina.sh run如果一切正常你会看到类似INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [xxx] milliseconds的输出。这里有一个很容易被忽略的细节即使 Tomcat 启动成功也不代表 SSL 一定配置正确有些证书链问题要到握手阶段才会暴露。所以启动之后必须实际验证一次 HTTPS 访问。验证方式有两种第一种是浏览器直接访问https://你的域名如果能看到站点内容、地址栏出现锁标记说明证书生效。第二种更严谨用openssl命令从客户端视角查看证书信息echo | openssl s_client -connect 127.0.0.1:443 -servername 你的域名 2/dev/null | openssl x509 -noout -subject -issuer -dates这条命令会输出证书的 Subject绑定的域名、Issuer签发 CA、有效期。如果这三项都正常说明 Tomcat 已经在完整地提供 HTTPS 服务了。顺手提一句浏览器首次访问如果报了“证书无效”或“不安全”不要慌先检查访问的域名和你证书里绑定的域名是否一致。很多人调试时用https://127.0.0.1访问而证书绑的是www.example.com这必然报错因为证书和域名不匹配。4.2 常见报错与解决方案速查我把这些年配置 Tomcat SSL 遇到的高频报错整理成了一个速查表每条都附上了排查思路报错信息可能原因解决办法java.net.BindException: Address already in use: JVM_Bind443 端口被占用执行netstat -tlnp | grep 443找到占用进程关掉后重启 Tomcatjava.io.IOException: Keystore was tampered with, or password was incorrect密钥库密码不对或文件损坏确认keystorePass与生成 PFX 时设置的密码一致重新转换证书java.security.KeyStoreException: PKCS12 not foundkeystoreType没写或写错在Certificate中显式添加certificateKeystoreTypePKCS12javax.net.ssl.SSLException: No available certificate corresponds to the SSL cipher suites证书与私钥不匹配或证书链不完整重新用openssl pkcs12转换确认-in与-inkey配对页面出现 404webapps 下没有部署应用或上下文路径不对检查webapps目录确认访问路径与应用名一致浏览器提示“证书链不完整”服务端没有下发中间证书在转换 PFX 时加上-certfile chain.pem把证书链一起打包HTTP 访问不跳转 HTTPSweb.xml安全约束没配或redirectPort不对确认安全约束生效redirectPort443与 SSL Connector 端口一致这里单独展开说一下“证书链不完整”的问题。很多 DV 证书实际由两级结构组成服务器证书 中间证书。如果只把服务器证书打进 PFX而没有包含中间证书链大部分现代浏览器会提示错误因为客户端无法通过服务器证书直接追溯到它信任的根证书。解决办法就是我在 2.2 节写的-certfile chain.pem参数把中间证书一起打包。验证方法很简单用openssl s_client看返回的证书数量如果只有一个证书大概率就是缺了中间证书。4.3 线上环境容易踩的细节坑除了报错信息本身还有几个细节坑我几乎每次帮别人排查时都会遇到。第一个坑是 Tomcat 启动脚本里的JAVA_HOME或者环境变量指向了错误的 JDK 版本。我在排查一个线上问题时发现Tomcat 用的其实是系统里另外一个老版本 JDK而那个版本对 TLS 1.2 的支持不完整导致 HTTPS 握手总是失败。所以配置 SSL 之前务必在catalina.sh run的启动输出里确认 JVM 版本符合预期。第二个坑是防火墙和安全组。很多云服务器的 443 端口默认没有开。你知道 Tomcat 已经在监听了但外网就是访问不了。先检查云厂商控制台的安全组再检查服务器本机防火墙firewall-cmd --list-ports如果没有开放 443执行firewall-cmd --zonepublic --add-port443/tcp --permanent firewall-cmd --reload。这个步骤漏掉的话HTTPS 验证会把你绕进死胡同。第三个坑是访问时出现中文乱码也就是热搜词里经常和 Tomcat 一起出现的问题。乱码很多时候和 SSL 没有直接关系但在配置 HTTPS 时容易一起暴雷。最经典的场景是在server.xml的 HTTP Connector 上忘了配置URIEncodingUTF-8导致带中文参数的 GET 请求乱码。在配置 HTTPS 连接器时顺手加上这个参数并不多余Connector port443 ... URIEncodingUTF-8 /这个参数对 POST 请求影响不大但对 GET 请求的 URL 参数编码是决定性的。如果你在 HTTPS 站点上发现中文参数乱码八成就是这里少了一行。5. 上线后的安全加固与证书续期5.1 只允许 TLSv1.2 与 TLSv1.3 等安全协议HTTPS 配好能访问算不上真正完事。我见过不少站点虽然挂了 HTTPS但还开着 TLSv1.0、SSLv3 这些古董协议各种安全检查一扫描全是漏洞。这些老协议早已被证明存在严重的安全缺陷比如 POODLE 攻击针对的就是 SSLv3。在 Tomcat 8 中控制协议版本是在SSLHostConfig节点上完成的。之前的配置基础上我们可以进一步收紧SSLHostConfig protocolsTLSv1.2,TLSv1.3 Certificate certificateKeystoreFileconf/server.pfx certificateKeystorePassword你的密码 certificateKeystoreTypePKCS12 / /SSLHostConfig注意protocols这个属性它不叫sslProtocols很多人第一次配置时会写成后者。protocolsTLSv1.2,TLSv1.3意味着服务器只接受这两个版本的 TLS 协议握手。TLSv1.0 和 TLSv1.1 会在连接时被直接拒绝。如果你的客户端还依赖老协议比如某些老 Android 系统、老浏览器这个配置可能会导致它们无法访问但考虑到这些老客户端本身也不安全该弃就弃。配置完成后可以通过openssl快速验证协议支持情况openssl s_client -connect 你的域名:443 -tls1_1如果返回的是握手失败之类的错误说明 TLSv1.1 已经被成功禁用加固到位了。5.2 会话 Cookie 设置 HttpOnly 与 SecureHTTPS 上线之后还有一个很容易被忽略的安全项Cookie 的Secure属性。如果一个会话 Cookie 没有标记Secure浏览器在有 HTTPS 的情况下也可能在某些情况下把它通过 HTTP 明文请求发送出去从而被中间人截获。Tomcat 8 中可以在全局conf/web.xml中统一配置会话 Cookie 的安全属性。找到session-config节点如果没有就新建一个做如下修改session-config session-timeout30/session-timeout cookie-config http-onlytrue/http-only securetrue/secure /cookie-config /session-confighttp-only的作用是禁止 JavaScript 通过document.cookie读取会话 Cookie能有效防御大部分 XSS 窃取会话的攻击。secure则保证浏览器只在 HTTPS 连接下上传这个 Cookie。两个属性都建议开启。这里说个经验如果你在配置了secure之后发现用户的登录状态在新旧版本间老是丢失先别急着回滚大概率是站点上还残留着某些 HTTP 资源引用导致部分请求走了 HTTPCookie 自然就传不过去了。这时应该排查页面中的外链资源把所有的http://改成https://或使用相对路径而不是关闭secure属性。5.3 阿里云免费证书的到期监测与续期流程免费证书最大的痛点就是有效期短容易忘记续期导致线上突然 HTTPS 报错。我习惯在服务器上写一个简单脚本定期检查证书剩余天数并输出提醒。检查证书过期时间最直接的方式还是opensslecho | openssl s_client -connect 你的域名:443 -servername 你的域名 2/dev/null | openssl x509 -noout -dates输出会显示notBefore生效时间和notAfter过期时间拿到过期时间后再用date命令算出剩余天数就能做到心中有数。如果想更省事可以把这条命令写进cron每周跑一次剩余天数小于 30 天时输出告警。阿里云免费证书的续期流程很简单在 SSL 证书控制台找到已签发的免费证书点击“续期”或者“重新申请”一般几分钟就能签发新证书。然后下载新证书重复 2.2 节的转换命令生成新的server.pfx覆盖旧的重启 Tomcat 即可。这里我再强调一个很多人血泪教训过的点替换证书之前务必把旧的server.pfx和旧的server.xml备份好。我遇到过几次新证书因为域名验证信息没同步好导致生成的文件有问题如果没备份线上就瘫在那里了。备份命令一行就够cp conf/server.pfx conf/server.pfx.$(date %Y%m%d)真的出问题的时候这一行备份就是你回滚的救命稻草。另外续期之后域名解析可能因为 CDN 等原因有缓存客户端可能在一段时间内仍看到旧证书这是正常的不用着急。要确认服务器上的新证书已经生效直接用 4.1 节的openssl s_client命令查看notAfter时间是否已经更新即可。最后再分享一个小技巧如果你用的是 Nginx 和 Tomcat 混搭的架构Nginx 已经终止了 SSL那么 Tomcat 内部的连接器其实不需要配置 SSL保持 HTTP 就好。这种情况下千万不要在 Tomcat 上再配一遍证书否则客户端和 Nginx 之间是 HTTPSNginx 转发给 Tomcat 时又试图走一遍 HTTPS反而会适得其反。分清 SSL 在哪一层终止是我这些年配置 HTTPS 总结出来最重要的一条经验。
阅读完成 · 觉得有帮助?