1. 项目概述:为什么必须让Tomcat 10“穿好外套”再出门?
在Ubuntu 20.04上跑Tomcat 10,很多人第一反应是解压、配置
JAVA_HOME
、启动
startup.sh
,看到
INFO [main] org.apache.catalina.startup.Catalina.start Server startup in [xxx] milliseconds
就以为万事大吉。但现实很骨感——默认的Tomcat 10 HTTP端口(8080)是裸奔状态:所有通信明文传输,登录凭证、会话ID、用户提交的表单数据,全在网线上“裸泳”。这就像把银行金库大门敞开,只挂个“请勿入内”的纸牌子。Apache和Nginx在这里不是可有可无的“锦上添花”,而是给Tomcat套上TLS加密外套、加装反向代理防火墙、实现动静分离的“刚需三件套”。尤其当你的应用要处理用户注册、支付回调、后台管理这类敏感操作时,没有TLS的Tomcat连现代浏览器的地址栏都会打上“不安全”红标,Chrome甚至会直接拦截混合内容(HTTP资源加载HTTPS页面)。更关键的是,Tomcat原生的AJP连接器在高并发下存在性能瓶颈,而Apache/Nginx作为成熟的Web服务器,在连接复用、请求队列、SSL卸载(offloading)方面有数十年的工程优化积累。我去年帮一家做教育SaaS的客户迁移旧系统,他们原来直接暴露Tomcat 8080端口,结果被爬虫扫出大量未授权API接口,三天内数据库被拖走27万条学生信息。后来我们用Nginx做TLS终止+IP白名单+速率限制,配合Tomcat的
RemoteAddrFilter
双重防护,攻击流量下降98%。所以这不是“如何配置”的技术问题,而是“为什么必须配置”的生存问题——Ubuntu 20.04的LTS支持周期到2025年4月,你部署的Tomcat 10服务很可能要在线上跑三年以上,安全基线从第一天就必须拉满。
2. 整体架构设计与方案选型逻辑
2.1 为什么放弃Tomcat原生SSL,坚持用Apache/Nginx做TLS终止?
Tomcat确实内置了SSL支持,通过
server.xml
配置
<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
就能启用HTTPS。但实际生产中,我几乎从不这么干,原因有三层硬伤:
第一层是 性能损耗 。Tomcat的Java SSL实现(基于JSSE)在高并发场景下CPU消耗远高于C语言编写的OpenSSL。实测数据:在同等4核8G服务器上,用Tomcat原生SSL处理1000并发HTTPS请求,CPU使用率稳定在85%以上;而用Nginx做TLS终止后,Nginx CPU仅占35%,Tomcat专注业务逻辑,整体吞吐量提升2.3倍。这是因为Nginx的SSL握手由epoll事件驱动,而Tomcat每个SSL连接需独占一个Java线程,线程上下文切换开销巨大。
第二层是
功能短板
。Tomcat的SSL配置项极其有限:无法动态加载证书(修改
server.xml
必须重启)、不支持OCSP Stapling(导致TLS握手多一次网络往返)、不支持HSTS预加载列表提交。而Nginx的
ssl_certificate
指令支持通配符证书热更新,
ssl_stapling on
一行代码就能启用OCSP,
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
直接满足Google HSTS预加载要求。Apache的
mod_ssl
同样支持
SSLOptions +StdEnvVars
传递客户端证书信息,这对需要双向认证的金融类应用至关重要。
第三层是
运维风险
。Tomcat重启意味着整个Java应用停服,而Nginx/Apache reload配置(
nginx -s reload
或
systemctl reload apache2
)是零停机的。去年某政务系统升级证书,运维同事误操作导致Tomcat配置文件XML格式错误,重启失败,整个市民办事平台宕机47分钟。如果采用Nginx TLS终止,reload失败只会中断新连接,存量连接不受影响。
提示:这里有个常见误区——认为“多一层代理就多一层故障点”。实际上,Nginx/Apache的稳定性经过全球数百万站点验证,其进程模型(master-worker)比Tomcat的JVM更轻量。我们团队维护的23个生产环境,Nginx平均无故障运行时间达142天,而Tomcat因内存泄漏、GC风暴导致的非计划重启频率是Nginx的5.7倍。
2.2 Apache vs Nginx:选型决策树与真实场景适配
很多教程笼统说“用Nginx或Apache”,但实际选型必须看你的具体场景。我画了一张决策树,基于三年来27个真实项目的踩坑经验:
是否需要深度集成Java生态(如Shiro权限控制、JNDI数据源)?
├─ 是 → 选Apache:mod_jk/mod_proxy_ajp对AJP协议支持更成熟,能透传Tomcat的request.getRemoteUser()等安全上下文
└─ 否 → 看流量特征:
├─ 静态资源占比>40%(如前端Vue/React打包文件、图片CDN回源)→ 选Nginx:sendfile零拷贝、gzip_static预压缩、slice模块分片传输效率碾压Apache
└─ 需要复杂Rewrite规则(如WordPress多站点、Magento URL重写)→ 选Apache:.htaccess分布式配置更灵活,正则引擎兼容性更好
举个典型例子:某电商后台管理系统,前端用Angular打包,后端是Spring Boot + Tomcat 10。静态资源(js/css/img)占总请求量63%,且需要强制HTTPS+HSTS。我们选Nginx,配置
location /static/ { alias /var/www/app/static/; expires 1y; }
,配合
gzip_static on
,首屏加载时间从3.2秒降到0.8秒。而另一个政府公文流转系统,要求所有请求必须经Shiro框架校验角色权限,且需调用LDAP服务。这里Apache的
mod_authnz_ldap
与Tomcat的
Realm
集成更平滑,
ProxyPass /app ajp://localhost:8009/app
透传的
REMOTE_USER
变量能直接被Shiro的
@RequiresRoles("admin")
注解识别。
注意:不要迷信“Nginx一定比Apache快”。在纯动态PHP场景下,Apache的mpm_event模块配合OPcache,QPS反而比Nginx+PHP-FPM高12%。关键看你的技术栈耦合点在哪里。
2.3 Ubuntu 20.04的特殊约束与版本陷阱
Ubuntu 20.04的软件源对老版本有严格限制,这是很多教程失效的根源。必须明确三点:
-
OpenSSL版本锁定 :Ubuntu 20.04默认
openssl version是1.1.1f,而Tomcat 10要求OpenSSL ≥1.1.1。但如果你手动升级到3.x,会导致libssl.so.1.1缺失,Nginx启动报错error while loading shared libraries: libssl.so.1.1: cannot open shared object file。解决方案是 绝不升级系统级OpenSSL ,用apt install openssl保持原版,证书生成用openssl req -x509 -sha256 -nodes -days 365 -newkey rsa:2048即可。 -
Java版本强依赖 :Tomcat 10.0.x仅支持Java 11+(不支持Java 17的某些新特性),而Ubuntu 20.04默认
openjdk-11-jdk是11.0.11+9-Ubuntu-0ubuntu1.20.04。但实测发现该版本存在TLS 1.3握手兼容性问题,需升级到openjdk-11-jdk-headless=11.0.19+7-0ubuntu1~20.04.1。命令:sudo apt update && sudo apt install openjdk-11-jdk-headless。 -
SELinux不存在,但AppArmor生效 :Ubuntu用AppArmor替代SELinux,很多人忽略这点导致Nginx无法读取证书文件。检查命令:
sudo aa-status | grep nginx。若显示/usr/sbin/nginx (enforce),则需执行sudo aa-disable /usr/sbin/nginx临时关闭,或更安全地sudo nano /etc/apparmor.d/usr.sbin.nginx添加/etc/ssl/private/** r,权限。
3. 核心细节解析与实操要点
3.1 TLS证书获取与安全加固:从Let's Encrypt到企业级实践
证书不是“有就行”,而是安全链条的第一环。我见过太多人用
openssl req -x509 -nodes -days 365
生成自签名证书,结果在iOS设备上直接提示“无法验证服务器身份”。正确路径分三级:
第一级:开发测试用自签名证书(必须带SAN扩展)
Tomcat 10要求证书必须包含Subject Alternative Name(SAN),否则Chrome 94+会拒绝连接。生成命令:
# 创建配置文件
cat > san.cnf <<EOF
[req]
distinguished_name = req_distinguished_name
x509_extensions = v3_req
prompt = no
[req_distinguished_name]
C = CN
ST = Beijing
L = Beijing
O = MyOrg
OU = Dev
CN = localhost
[v3_req]
keyUsage = keyEncipherment, dataEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = localhost
DNS.2 = 127.0.0.1
IP.1 = 127.0.0.1
EOF
# 生成密钥和证书
openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout localhost.key -out localhost.crt -config san.cnf
关键点:
[alt_names]
段必须包含
DNS.1 = localhost
和
IP.1 = 127.0.0.1
,否则浏览器访问
https://127.0.0.1:8443
会报错。
第二级:生产环境Let's Encrypt免费证书(推荐acme.sh)
Certbot在Ubuntu 20.04上常因Python依赖冲突失败,acme.sh更轻量。安装与签发:
curl https://get.acme.sh | sh -s email=my@example.com
source ~/.acme.sh/acme.sh.env
# DNS方式(需API密钥,最安全)
acme.sh --issue --dns dns_dp -d example.com -d www.example.com
# 或HTTP方式(需Nginx临时配置)
acme.sh --issue -d example.com --webroot /var/www/html
证书自动存于
~/.acme.sh/example.com/
,但Nginx需要PEM格式。执行:
acme.sh --install-cert -d example.com \
--key-file /etc/ssl/private/example.com.key \
--fullchain-file /etc/ssl/certs/example.com.fullchain.cer \
--reloadcmd "systemctl reload nginx"
--reloadcmd
确保证书更新后Nginx自动重载,避免手动干预。
第三级:企业级证书与HSM集成(高阶)
金融类客户要求私钥不出HSM(硬件安全模块)。此时需用
openssl pkcs11
引擎。步骤:
-
安装
libengine-pkcs11-openssl -
编辑
/etc/ssl/openssl.cnf,在[default_conf]下添加:
engines = engine_section
[engine_section]
pkcs11 = pkcs11_section
[pkcs11_section]
engine_id = pkcs11
dynamic_path = /usr/lib/x86_64-linux-gnu/engines-1.1/pkcs11.so
MODULE_PATH = /usr/lib/softhsm/libsofthsm2.so
init = 1
-
生成CSR时指定引擎:
openssl req -new -engine pkcs11 -keyform ENGINE -key "pkcs11:token=MyToken;object=mykey" -out csr.pem
实操心得:Let's Encrypt证书90天过期,必须设置自动续期。但
systemctl enable acme.sh无效!正确做法是编辑crontab -e:0 0 * * 1 "/root/.acme.sh/acme.sh" --cron --home "/root/.acme.sh" > /dev/null。我曾因忘记这步,凌晨3点被告警电话叫醒——证书过期导致支付接口全部失败。
3.2 Apache反向代理配置:AJP协议的深度调优
Apache与Tomcat通信首选AJP 1.3协议(端口8009),而非HTTP代理,因为AJP二进制协议减少序列化开销,且支持Tomcat会话粘滞。配置分三步:
第一步:启用必要模块
sudo a2enmod proxy proxy_ajp proxy_http ssl headers
sudo systemctl restart apache2
注意
proxy_http
模块必须启用,否则
ProxyPassReverse
重写Location头会失败。
第二步:核心虚拟主机配置
<IfModule mod_ssl.c>
<VirtualHost *:443>
ServerAdmin webmaster@localhost
ServerName example.com
ServerAlias www.example.com
# TLS基础配置
SSLEngine on
SSLCertificateFile /etc/ssl/certs/example.com.fullchain.cer
SSLCertificateKeyFile /etc/ssl/private/example.com.key
# 强制HSTS
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
# AJP代理核心
ProxyRequests Off
ProxyPreserveHost On
# 关键:禁用正向代理,只允许反向代理
<Proxy *>
Require all granted
</Proxy>
# 将/路径代理到Tomcat
ProxyPass / ajp://127.0.0.1:8009/
ProxyPassReverse / ajp://127.0.0.1:8009/
# 安全加固:禁止访问WEB-INF和META-INF
<LocationMatch "/WEB-INF|/META-INF">
Require all denied
</LocationMatch>
# 日志记录真实IP(X-Forwarded-For)
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined_forwarded
CustomLog ${APACHE_LOG_DIR}/access.log combined_forwarded
</VirtualHost>
</IfModule>
第三步:Tomcat端AJP连接器调优
编辑
$CATALINA_HOME/conf/server.xml
,找到AJP连接器并修改:
<Connector port="8009"
protocol="AJP/1.3"
redirectPort="8443"
secretRequired="true"
secret="my_ajp_secret_123" <!-- 必须设置,防未授权访问 -->
maxThreads="200"
minSpareThreads="10"
connectionTimeout="20000"
packetSize="65536"
address="127.0.0.1" /> <!-- 绑定本地回环,禁止外部访问 -->
关键参数解释:
-
secretRequired="true"+secret:AJP协议认证密钥,Apache端需对应配置ProxyPass / ajp://127.0.0.1:8009/ secret=my_ajp_secret_123 -
address="127.0.0.1":强制AJP只监听本地,即使防火墙失效也无法从外部连接 -
packetSize="65536":增大AJP包大小,避免大文件上传时被截断
常见问题:Apache代理后,Java代码中
request.getRemoteAddr()返回127.0.0.1而非真实IP。解决方案是在Tomcat的server.xml中添加Valve:<Valve className="org.apache.catalina.valves.RemoteIpValve" remoteIpHeader="x-forwarded-for" protocolHeader="x-forwarded-proto" internalProxies="127\.0\.0\.1" />这样
request.getRemoteAddr()就能获取真实IP。
3.3 Nginx反向代理配置:TLS卸载与高级安全策略
Nginx配置更简洁,但安全策略更精细。以下是生产环境标准配置:
upstream tomcat_backend {
server 127.0.0.1:8080 max_fails=3 fail_timeout=30s;
# 可添加多台Tomcat实现负载均衡
# server 192.168.1.10:8080 weight=2;
}
server {
listen 443 ssl http2;
server_name example.com www.example.com;
# TLS证书
ssl_certificate /etc/ssl/certs/example.com.fullchain.cer;
ssl_certificate_key /etc/ssl/private/example.com.key;
# TLS协议与密码套件(2023年安全基线)
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:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# OCSP Stapling(提升TLS握手速度)
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 1.1.1.1 valid=300s;
resolver_timeout 5s;
# HSTS强制HTTPS
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
# 安全头加固
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# 静态资源缓存
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public, immutable";
try_files $uri @tomcat;
}
# 动态请求代理到Tomcat
location / {
proxy_pass http://tomcat_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
send_timeout 60s;
}
# 禁止访问敏感路径
location ~ ^/(WEB-INF|META-INF|classes|lib|logs)/ {
deny all;
}
}
# HTTP强制跳转HTTPS
server {
listen 80;
server_name example.com www.example.com;
return 301 https://$server_name$request_uri;
}
关键安全点解析:
-
ssl_ciphers:剔除所有RSA密钥交换套件(易受ROBOT攻击),强制ECDHE前向保密 -
resolver:必须显式配置DNS服务器,否则OCSP Stapling会因DNS超时失败 -
X-Frame-Options "DENY":防止点击劫持(Clickjacking)攻击 -
proxy_set_header:X-Forwarded-For和X-Forwarded-Proto是Tomcat识别真实客户端的关键,缺一不可
实操心得:Nginx的
proxy_buffering默认开启,对大文件下载(如Excel导出)可能导致内存溢出。遇到upstream sent too big header错误时,添加:proxy_buffering off; proxy_buffer_size 128k; proxy_buffers 4 256k; proxy_busy_buffers_size 256k;
4. 实操过程与核心环节实现
4.1 Ubuntu 20.04环境初始化:从裸机到安全基线
这不是简单的
apt update
,而是构建安全底座的七步法。每一步都经过生产环境验证:
步骤1:系统更新与内核加固
# 升级到最新内核(Ubuntu 20.04.6含关键TLS修复)
sudo apt update && sudo apt full-upgrade -y
sudo apt install linux-image-generic-hwe-20.04 linux-headers-generic-hwe-20.04 -y
sudo reboot
# 启用内核TLS支持(Ubuntu 20.04.4+已默认启用,但需确认)
echo 'net.ipv4.conf.all.rp_filter=1' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv4.tcp_fin_timeout=30' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
步骤2:Java环境安装(OpenJDK 11)
# 移除可能存在的旧Java
sudo apt remove openjdk-* -y
# 安装官方OpenJDK 11(非Adoptium,避免许可证风险)
sudo apt install openjdk-11-jdk-headless -y
# 验证版本与TLS支持
java -version # 应显示11.0.19+
java -cp . TestTLS.java # 自编译测试类,验证TLS 1.3可用
步骤3:Tomcat 10.0.27安装与最小化配置
# 下载官方二进制包(非apt源,避免版本滞后)
cd /tmp
wget https://downloads.apache.org/tomcat/tomcat-10/v10.0.27/bin/apache-tomcat-10.0.27.tar.gz
sudo tar xzvf apache-tomcat-10.0.27.tar.gz -C /opt/
sudo ln -s /opt/apache-tomcat-10.0.27 /opt/tomcat
# 创建专用用户(禁止root运行)
sudo useradd -r -m -U -d /opt/tomcat -s /bin/false tomcat
sudo chown -R tomcat:tomcat /opt/tomcat
sudo chmod -R u+x /opt/tomcat/bin/
# 配置systemd服务(关键!)
sudo nano /etc/systemd/system/tomcat.service
/etc/systemd/system/tomcat.service
内容:
[Unit]
Description=Apache Tomcat Web Application Container
Wants=network.target
After=network.target
[Service]
Type=forking
Environment=JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid
Environment=CATALINA_HOME=/opt/tomcat
Environment=CATALINA_BASE=/opt/tomcat
Environment='CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC'
Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom'
ExecStart=/opt/tomcat/bin/startup.sh
ExecStop=/opt/tomcat/bin/shutdown.sh
User=tomcat
Group=tomcat
UMask=0007
RestartSec=10
Restart=always
[Install]
WantedBy=multi-user.target
关键点:
UMask=0007
确保Tomcat创建的文件权限为660,防止其他用户读取日志;
Restart=always
保证崩溃后自动恢复。
步骤4:防火墙配置(UFW)
sudo ufw enable
sudo ufw default deny incoming
sudo ufw allow OpenSSH
sudo ufw allow 443/tcp # 仅开放HTTPS
sudo ufw allow 80/tcp # HTTP跳转用
# 严格禁止Tomcat端口外露
sudo ufw deny 8080
sudo ufw deny 8009
sudo ufw status verbose
步骤5:证书部署与验证
# 创建证书目录并赋权
sudo mkdir -p /etc/ssl/{private,certs}
sudo chown root:ssl-cert /etc/ssl/private
sudo chmod 710 /etc/ssl/private
sudo chmod 644 /etc/ssl/certs/*.cer
# 使用acme.sh获取证书(以DNS方式为例)
sudo apt install socat -y
curl https://get.acme.sh | sh
source ~/.acme.sh/acme.sh.env
acme.sh --issue --dns dns_dp -d example.com -d www.example.com
acme.sh --install-cert -d example.com \
--key-file /etc/ssl/private/example.com.key \
--fullchain-file /etc/ssl/certs/example.com.fullchain.cer \
--reloadcmd "systemctl reload nginx"
步骤6:Web服务器安装与配置
根据选型执行:
-
Nginx方案
:
sudo apt install nginx-full -y(full版含更多模块) -
Apache方案
:
sudo apt install apache2 apache2-utils -y
然后部署前述配置文件,最后:
# 启用配置
sudo ln -sf /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
# 或Apache
sudo a2ensite example.com.conf && sudo systemctl reload apache2
步骤7:最终安全验证
# 检查端口监听
sudo ss -tlnp | grep -E ':443|:80|:8080|:8009'
# 测试HTTPS连接(应返回200)
curl -I https://example.com
# 检查TLS配置(Qualys SSL Labs评分)
# 访问 https://www.ssllabs.com/ssltest/analyze.html?d=example.com
# 检查HTTP跳转
curl -I http://example.com # 应返回301到https
# 检查敏感路径
curl -I https://example.com/WEB-INF/web.xml # 应返回403
4.2 TLS握手深度调试:当“创建TLS客户端凭据时发生严重错误”出现时
标题中的错误码“内部错误状态为10013”是Windows系统特有(WSAEACCES),但在Linux上类似问题表现为
SSL routines::wrong_version_number
或
gnutls recv error (-110)
。我整理了五类高频问题及根因分析:
问题1:Nginx/Apache证书链不完整
现象:浏览器显示“您的连接不是私密连接”,但
curl -v https://example.com
正常。
根因:Let's Encrypt的
fullchain.cer
必须包含中间证书,而很多教程只复制
cert.pem
。
验证:
openssl s_client -connect example.com:443 -servername example.com | openssl x509 -noout -text | grep "CA Issuers"
修复:确保
ssl_certificate
指向
fullchain.cer
而非
cert.pem
,且
ssl_trusted_certificate
指向同一文件。
问题2:Tomcat AJP连接器未绑定本地回环
现象:Apache能启动,但访问页面返回503 Service Unavailable。
根因:
server.xml
中AJP连接器
address
属性未设为
127.0.0.1
,导致连接被Ubuntu AppArmor阻止。
验证:
sudo journalctl -u apache2 -n 50 --no-pager | grep ajp
查看
Connection refused
错误。
修复:修改
server.xml
,添加
address="127.0.0.1"
,并执行
sudo systemctl restart tomcat
。
问题3:Java信任库未更新
现象:Tomcat应用内调用HTTPS API失败,日志报
PKIX path building failed
。
根因:Ubuntu的
/etc/ssl/certs/java/cacerts
未同步系统证书。
修复:
sudo /usr/bin/awk -v cmd='openssl x509 -noout -text' '
/BEGIN CERTIFICATE/ {f=1; system(cmd)}; f' \
< /etc/ssl/certs/ca-certificates.crt | \
sudo keytool -importcert -alias ca-$(date +%s) -keystore /usr/lib/jvm/java-11-openjdk-amd64/lib/security/cacerts -storepass changeit -noprompt
问题4:Nginx resolver DNS超时
现象:OCSP Stapling失败,
ssl_stapling
日志显示
no resolver defined to resolve
。
根因:
resolver
指令未在
server
块内,或DNS服务器不可达。
验证:
sudo nginx -t
应无警告;
dig ocsp.int-x3.letsencrypt.org @8.8.8.8
测试DNS。
修复:将
resolver
移到
server
块内,并添加
valid=300s
。
问题5:系统时间不同步
现象:所有HTTPS请求均失败,
curl
报
SSL certificate problem: certificate has expired
。
根因:Ubuntu 20.04默认使用systemd-timesyncd,但虚拟机中常失效。
验证:
timedatectl status
查看
System clock synchronized: no
。
修复:
sudo timedatectl set-ntp on && sudo systemctl restart systemd-timesyncd
。
实操心得:我建立了一个
tls-debug.sh脚本,一键诊断:#!/bin/bash echo "=== 证书链检查 ===" openssl s_client -connect $1:443 -servername $1 2>/dev/null | openssl x509 -noout -text | grep -E "(Issuer|Subject|CA Issuers)" echo -e "\n=== DNS解析检查 ===" dig +short $1 @8.8.8.8 echo -e "\n=== 时间同步检查 ===" timedatectl status | grep "synchronized"
5. 常见问题与排查技巧实录
5.1 Tomcat 10专属问题:从“Could not initialize class”到URI编码
Tomcat 10基于Jakarta EE 9,包名从
javax.*
改为
jakarta.*
,这是最大兼容性雷区。我整理了TOP5问题及解决方案:
问题1:Spring Boot应用启动报
java.lang.NoClassDefFoundError: javax/servlet/Filter
现象:Tomcat日志显示
SEVERE [main] org.apache.catalina.startup.HostConfig.deployWAR Error deploying web application archive
。
根因:Spring Boot 2.5+才完全支持Jakarta EE 9,旧版仍引用
javax.servlet
。
解决方案:
- 升级Spring Boot至2.5.0+(推荐2.7.18 LTS)
- 或降级Tomcat至9.0.x(不推荐,放弃新特性)
-
或手动替换依赖(高风险):在
pom.xml中添加<dependency> <groupId>org.glassfish.web</groupId> <artifactId>jakarta.servlet.jsp.jstl</artifactId> <version>2.0.0</version> </dependency>
问题2:中文URL路径400 Bad Request
现象:访问
https://example.com/api/user/张三
返回400。
根因:Tomcat 10默认URI编码为UTF-8,但浏览器发送的百分号编码需显式声明。
修复:在
server.xml
的HTTP连接器中添加
URIEncoding="UTF-8"
:
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
URIEncoding="UTF-8" />
问题3:JSP页面中文乱码
现象:JSP中
<%= "你好" %>
显示为
??
。
根因:JSP编译器未指定编码。
修复:在
web.xml
中添加:
<jsp-config>
<jsp-property-group>
<url-pattern>*.jsp</url-pattern>
<page-encoding>UTF-8</page-encoding>
<scripting-invalid>false</scripting-invalid>
</jsp-property-group>
</jsp-config>
问题4:Session ID在URL中泄露(不安全)
现象:浏览器地址栏出现
;jsessionid=ABC123
。
根因:客户端禁用Cookie或Tomcat未配置安全Cookie。
修复:在
context.xml
中添加:
<Context>
<CookieProcessor
className="org.apache.tomcat.util.http.Rfc6265CookieProcessor"
sameSiteCookies="Strict" />
<Manager pathname="" />
</Context>
并在
web.xml
中添加:
<session-config>
<cookie-http-only>true</cookie-http-only>
<cookie-secure>true</cookie-secure>
<tracking-mode>COOKIE</tracking-mode>
</session-config>
问题5:WebSocket连接失败
现象:前端
new WebSocket("wss://example.com/ws")
报
Error during WebSocket handshake
。
根因:Nginx/Apache未透传Upgrade头。
修复:Nginx配置中
location /ws

410

被折叠的 条评论
为什么被折叠?



