
1. 为什么一个 Tomcat 要监听两个端口第一次碰到“Tomcat 开启两个端口”这个需求是在一个前后端分离还没完全普及的项目里。当时后端接口和一套老的管理后台都跑在同一个 Tomcat 上前端同学希望接口走 8080管理后台走 9090理由是“方便做网关分流和日志区分”。听起来有点多此一举但实际排查问题时确实省事——哪个端口出问题日志一眼就能定位。Tomcat 默认只监听一个 HTTP 端口也就是server.xml里那个Connector port8080 ... /。所谓“开启两个端口”本质上就是让 Tomcat 同时绑定两个不同的端口对外提供两套访问入口。它可以是两个 HTTP 端口也可以是一个 HTTP 加一个 HTTPS甚至可以是 HTTP 加 AJP。核心动作只有一个在conf/server.xml里增加一个Connector元素。这件事解决的核心问题是单实例多入口。你不需要部署两个 Tomcat 实例不需要复制两份webapps也不用担心两个实例抢内存、抢 JVM 参数。一个 JVM 进程两个监听端口共享同一套应用和线程池配置资源利用率更高运维也更简单。适合谁来参考如果你正在做下面这些事这篇内容基本能直接抄作业想让同一套应用通过不同端口区分访问来源比如内网 8080、外网映射 9090想在不额外起实例的前提下同时提供 HTTP 和 HTTPS 服务本地开发时想模拟多端口环境验证网关或反向代理的转发规则接手了一个老项目发现server.xml里已经有两个 Connector但不知道各自作用。需要提前说清楚Tomcat 开两个端口不是开两个独立的 Web 容器。两个端口背后是同一个Engine、同一个Host、同一份webapps。你在 8080 上部署的应用9090 上同样能访问路径完全一致。这一点如果理解错了后面配置很容易踩坑。2. 核心原理Connector 到底在做什么2.1 一个 Connector 就是一条监听通道Tomcat 的架构里Server是最外层容器里面包着ServiceService里再包Connector和Engine。Connector负责的是网络通信层它监听某个端口接收 TCP 连接按照协议解析请求然后把请求交给Engine处理处理完再把响应写回去。你可以把Connector理解成公司前台。前台可以有好几个一个负责接待普通访客HTTP一个负责接待 VIPHTTPS但他们把访客领进去之后见到的都是同一套内部团队Engine 和具体的 Web 应用。前台数量不影响内部团队的数量。所以增加一个端口就是在Service里多放一个Connector。多个Connector共享同一个Engine这是 Tomcat 原生支持的不需要任何插件。2.2 两个端口共享线程池的真相很多人担心“开两个端口会不会线程数翻倍”。默认情况下每个Connector有自己的执行器配置如果不显式指定共享Tomcat 会为每个 Connector 维护各自的线程池。但你可以通过executor属性让多个 Connector 共用一个线程池。具体做法是在Service里先定义一个Executor然后两个Connector都通过executortomcatThreadPool引用它。这样无论请求从哪个端口进来用的都是同一批工作线程总线程数可控不会因为多开一个端口就把内存吃满。这个细节在官方文档里写得很清楚但实际配置时经常被忽略。我见过一个项目两个 Connector 各自默认 200 个线程结果高峰期线程数直接飙到 400GC 压力明显上升。后来改成共享 Executor问题立刻缓解。2.3 端口冲突与绑定顺序Tomcat 启动时会按server.xml中Connector出现的顺序依次绑定端口。如果第二个端口已经被别的进程占用启动日志里会出现Address already in use但第一个端口通常已经绑定成功。这时候 Tomcat 可能整体启动失败也可能只报错不退出取决于版本和配置。所以配置两个端口之前务必先用系统命令确认端口空闲。Linux 下用ss -lntp | grep 端口号Windows 下用netstat -ano | findstr 端口号。这一步花十秒钟能省掉后面半小时的排查。3. 动手配置从单端口到双端口3.1 找到并备份 server.xml配置文件位置在 Tomcat 安装目录的conf/server.xml。改之前先复制一份命名成server.xml.bak。这不是形式主义我确实遇到过改错一个标签导致 Tomcat 起不来、又忘了原始内容的情况最后只能重新解压一份 Tomcat 对比。用文本编辑器打开server.xml找到类似下面这段Service nameCatalina Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 / Engine nameCatalina defaultHostlocalhost ... /Engine /Service这就是默认的单端口配置。Connector只有一个端口 8080。3.2 新增第二个 Connector在原有Connector后面紧挨着再加一个Connector port9090 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 /保存重启 Tomcat。访问http://localhost:8080/你的应用和http://localhost:9090/你的应用应该都能打开同一个页面。这里有几个参数值得说明port监听端口两个 Connector 必须不同。protocolHTTP/1.1 是默认的 NIO 实现Tomcat 8 以后推荐保持默认不要手动写org.apache.coyote.http11.Http11NioProtocol除非有特殊需求。connectionTimeout连接超时单位毫秒。两个 Connector 可以设不同值比如内网端口设短一点外网端口设长一点。redirectPort当收到需要 SSL 的请求时重定向到的端口。如果两个 Connector 都走 HTTP这个值保持一致即可。3.3 共享线程池的正确写法如果希望两个端口共用线程池把配置改成这样Service nameCatalina Executor nametomcatThreadPool namePrefixcatalina-exec- maxThreads300 minSpareThreads30 maxIdleTime60000 prestartminSpareThreadstrue/ Connector port8080 protocolHTTP/1.1 executortomcatThreadPool connectionTimeout20000 redirectPort8443 / Connector port9090 protocolHTTP/1.1 executortomcatThreadPool connectionTimeout20000 redirectPort8443 / Engine nameCatalina defaultHostlocalhost ... /Engine /ServicemaxThreads300表示整个 Service 最多 300 个工作线程两个端口共享。minSpareThreads30是保持的最小空闲线程数。prestartminSpareThreadstrue让 Tomcat 启动时就把最小空闲线程创建好避免第一波请求来时临时创建线程造成延迟。注意Executor必须定义在Connector之前否则 Tomcat 启动时会报找不到 executor 的错。XML 解析是按顺序来的这个顺序不能乱。3.4 验证配置是否生效重启后查看 Tomcat 启动日志通常会在logs/catalina.out或控制台输出里看到类似内容信息: Starting ProtocolHandler [http-nio-8080] 信息: Starting ProtocolHandler [http-nio-9090]两行都出现说明两个端口都绑定成功。如果只有一行或者出现Address already in use就要去检查端口占用。另一个验证方式是看 Tomcat 管理页面如果启用了 manager 应用在Server Status里能看到当前 Service 下的 Connector 列表两个端口都会列出来。4. 进阶场景HTTP 与 HTTPS 双端口共存4.1 为什么需要 HTTP 和 HTTPS 同时开有些场景下内部服务调用走 HTTP 图个快对外用户访问必须走 HTTPS 保证安全。这时候一个 Tomcat 同时开两个端口就很合适8080 走 HTTP 给内网8443 走 HTTPS 给外部。HTTPS 的 Connector 配置比 HTTP 多几个参数Connector port8443 protocolHTTP/1.1 executortomcatThreadPool maxThreads200 schemehttps securetrue SSLEnabledtrue keystoreFileconf/your_keystore.jks keystorePass你的密码 clientAuthfalse sslProtocolTLS connectionTimeout20000 /关键参数解释SSLEnabledtrue开启 SSL。schemehttps告诉 Tomcat 这个连接是安全的应用里request.getScheme()会返回 https。securetrue标记该连接为安全连接request.isSecure()返回 true。keystoreFile证书库文件路径可以是绝对路径或相对于 Tomcat 根目录的路径。keystorePass证书库密码。sslProtocol建议用 TLS不要用 SSLv3后者已经不安全。4.2 证书准备的实操细节证书库可以用keytool生成自签名证书做测试keytool -genkeypair -alias tomcat -keyalg RSA -keysize 2048 \ -keystore conf/your_keystore.jks -validity 365 \ -storepass 你的密码 -keypass 你的密码 \ -dname CNlocalhost, OUDev, ODemo, LCity, STState, CCN生产环境当然要用正式证书。拿到证书后通常需要把 PEM 格式转成 JKS 或 PKCS12 格式。PKCS12 现在是更推荐的选择Tomcat 9 以后对 PKCS12 支持很好配置里把keystoreTypePKCS12加上即可。提示keystorePass直接写在server.xml里是明文有安全风险。更稳妥的做法是用 Tomcat 的property替换或者外部化配置。本地测试无所谓生产环境一定要处理。4.3 两个端口的请求分流验证配置完成后分别访问http://localhost:8080/应用路径正常返回浏览器地址栏显示 http。https://localhost:8443/应用路径浏览器可能提示证书不受信任自签名证书的正常现象继续访问后能看到同样内容。在应用代码里打印request.getScheme()和request.isSecure()可以确认两个端口进来的请求属性确实不同。这一点在做重定向或生成绝对 URL 时很重要如果搞混了可能生成 http 链接给 https 页面浏览器会报混合内容警告。5. 常见问题与排查速查5.1 启动报端口被占用现象日志出现java.net.BindException: Address already in use。排查步骤确认端口是否被其他进程占用。Linux 用ss -lntp | grep 9090Windows 用netstat -ano | findstr 9090。如果是被别的 Tomcat 占用改端口或停掉那个实例。如果是被自己上一次没关干净的 Tomcat 占用用ps -ef | grep tomcat找到进程号kill -9掉再重启。检查server.xml里是不是不小心写了两个相同的port值。5.2 第二个端口访问 404现象8080 能访问应用9090 返回 404。原因通常不是 Connector 的问题而是应用部署路径的问题。两个 Connector 共享同一个 Host理论上访问路径应该完全一致。如果 9090 返回 404先确认访问的 URL 路径是否和 8080 完全一样包括应用名和大小写。Tomcat 对 URL 路径大小写敏感/MyApp和/myapp是两个不同的路径。另一个可能是 9090 的 Connector 配置里加了URIEncoding或别的参数导致解析差异但这种概率很低。最直接的排查方式是看 9090 请求进来时localhost_access_log里记录的路径是什么。5.3 两个端口性能表现不一致现象同样压力下一个端口响应快另一个慢。如果两个 Connector 没有共享 Executor各自线程池独立可能出现一个端口线程打满、另一个端口线程空闲的情况。解决办法就是前面说的配置共享 Executor。如果已经共享了检查两个 Connector 的acceptorThreadCount、pollerThreadCount等参数是否一致。这些参数控制的是网络层接收线程不共享但通常不需要改默认值够用。5.4 修改后 Tomcat 启动变慢增加 Connector 本身不会明显拖慢启动。如果感觉启动变慢检查是不是prestartminSpareThreadstrue配合了很大的minSpareThreads。启动时预创建大量线程确实会花时间。把minSpareThreads调小或者关掉预创建让线程按需增长。5.5 常见问题速查表问题现象可能原因解决方向启动报 Address already in use端口被占用换端口或停占用进程第二个端口 404访问路径不一致核对 URL 大小写和应用名一个端口快一个端口慢线程池未共享配置共享 Executor启动变慢预创建线程过多调小 minSpareThreadsHTTPS 端口浏览器警告自签名证书测试环境忽略生产换正式证书应用内生成链接协议错误scheme/secure 未配检查 Connector 的 scheme 和 secure6. 几个容易被忽略的实操心得第一个心得改 server.xml 之前一定先停 Tomcat。热加载server.xml在部分版本里支持但 Connector 的增删通常需要重启才能生效。边跑边改容易出现配置读了一半、状态不一致的情况。第二个心得两个端口的日志最好分开。Tomcat 的 access log 默认按 Host 或 Engine 记录不区分 Connector。如果确实需要按端口区分访问日志可以在 Connector 上配置accessLogPrefix或者在应用层用 Filter 根据request.getLocalPort()打不同标记。这个需求不常见但一旦有提前规划比事后补要省事。第三个心得防火墙和安全组别忘了放行新端口。本地测试通了部署到服务器上访问不了十有八九是防火墙没开。Linux 的firewalld或iptables云服务器的安全组规则都要检查。这个坑我踩过不止一次每次都是先怀疑 Tomcat最后发现是网络层。第四个心得端口号不要选太随意的值。9090、8081、8443 这些都是常见选择但也要注意别和系统里其他服务撞车。比如 9090 有时被 Prometheus 占用8443 有时被其他 Web 服务占用。选之前扫一眼当前监听列表心里有数。第五个心得配置改完后用catalina.sh configtest做语法检查。这个命令会解析server.xml并报告语法错误不用真正启动 Tomcat。在正式重启前跑一下能提前发现标签闭合错误、属性拼写错误这类低级问题。Windows 下对应的是catalina.bat configtest。7. 配置模板与参数速查把前面零散的内容整理成一份可以直接复制的模板方便对照修改Service nameCatalina Executor namesharedThreadPool namePrefixcatalina-exec- maxThreads300 minSpareThreads30 maxIdleTime60000 prestartminSpareThreadsfalse/ !-- 端口一HTTP 内网访问 -- Connector port8080 protocolHTTP/1.1 executorsharedThreadPool connectionTimeout20000 redirectPort8443 URIEncodingUTF-8 compressionon compressionMinSize2048 compressableMimeTypetext/html,text/xml,text/plain,text/css,text/javascript,application/json/ !-- 端口二HTTP 管理后台或外网映射 -- Connector port9090 protocolHTTP/1.1 executorsharedThreadPool connectionTimeout20000 redirectPort8443 URIEncodingUTF-8/ Engine nameCatalina defaultHostlocalhost Host namelocalhost appBasewebapps unpackWARstrue autoDeploytrue Valve classNameorg.apache.catalina.valves.AccessLogValve directorylogs prefixlocalhost_access_log suffix.txt pattern%h %l %u %t quot;%rquot; %s %b %{X-Forwarded-For}i/ /Host /Engine /Service参数速查表参数作用建议值port监听端口两个 Connector 必须不同protocol协议实现HTTP/1.1 默认即可executor共享线程池引用两个 Connector 填同一个名字connectionTimeout连接超时毫秒20000 左右redirectPortSSL 重定向端口与 HTTPS 端口一致URIEncodingURL 编码UTF-8compression是否压缩响应on节省带宽maxThreads最大工作线程根据并发量300 起步minSpareThreads最小空闲线程30 左右提示compression开启后对文本类响应压缩效果明显但图片、视频这类已经压缩过的内容不要重复压缩所以compressableMimeType里只列文本类型。8. 端口数量还能继续加吗理论上一个Service下可以放任意多个Connector只要端口不冲突。我见过一个内部系统开了四个端口8080 给普通用户、8081 给移动端、8082 给内部服务调用、8443 给 HTTPS。四个 Connector 共享一个 Executor跑在一台 4 核 8G 的机器上日常压力完全扛得住。但端口不是越多越好。每多一个 Connector就多一份网络监听资源多一份配置维护成本。如果两个端口的用途没有本质区别只是访问路径不同那完全可以用一个端口加不同的 Context 路径来解决没必要开两个端口。判断标准很简单两个端口的请求是否需要不同的网络层行为。比如一个要 HTTPS 一个不要一个要限制来源 IP 一个不要一个要不同的超时策略一个不要——这些才是开两个端口的合理理由。如果只是“想区分一下”那用路径区分更合适。另外端口开多了之后监控和日志的复杂度会上升。每个端口的连接数、吞吐量、错误率都要单独看运维成本是实打实增加的。我个人的经验是单实例超过三个 Connector 就要停下来想想是不是该拆成两个实例或者上反向代理了。9. 和反向代理配合时的注意事项很多生产环境里Tomcat 前面还有一层反向代理。这时候 Tomcat 开两个端口反向代理的配置也要对应调整。假设反向代理监听 80 和 443分别转发到 Tomcat 的 8080 和 8443那么反向代理配置里要有两条转发规则。关键是转发时要带上正确的头信息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;X-Forwarded-Proto这个头特别重要。如果 Tomcat 的 HTTPS Connector 没有配schemehttps应用里生成绝对 URL 时可能生成 http 链接导致浏览器混合内容警告。加上这个头再配合 Tomcat 的RemoteIpValve就能让应用正确识别原始请求协议。Tomcat 端的RemoteIpValve配置放在Engine或Host里Valve classNameorg.apache.catalina.valves.RemoteIpValve remoteIpHeaderX-Forwarded-For protocolHeaderX-Forwarded-Proto protocolHeaderHttpsValuehttps/这样应用里request.getScheme()就会根据X-Forwarded-Proto返回正确的值而不是永远返回 Connector 上配的那个。10. 我个人的使用体会Tomcat 开两个端口这件事配置本身五分钟就能搞定真正花时间的是想清楚“为什么要开”和“开了之后怎么维护”。我自己的习惯是每加一个 Connector就在server.xml里用注释写清楚这个端口的用途、负责人、开通日期。过半年再回来看不用去翻文档就能明白每个端口是干嘛的。还有一点改完配置后不要只测“能不能访问”要测“两个端口的请求是否真的走了同一套应用逻辑”。最简单的办法是在应用里加一个接口返回request.getLocalPort()然后分别从两个端口调一下看返回值是否和预期一致。这个验证花不了一分钟但能避免“以为配好了其实没生效”的尴尬。最后如果只是本地开发临时用改完记得改回去。我见过有人把测试用的双端口配置直接提交到生产分支结果上线后多开了一个不该开的端口虽然没出大事但被安全扫描报了出来解释起来挺麻烦的。配置文件的版本管理和代码一样重要。