Tomcat 8.5.35 部署实战:从安装配置到systemd托管与排障
简介这是一份专供Linux环境使用的Tomcat 8.5.35压缩包由Apache软件基金会维护是著名的开源Java Servlet容器适合在部署Java Web应用或搭建动态网站时使用核心组件包括处理Servlet请求的Catalina、解析JSP的Jasper以及负责HTTP/1.1协议的Coyote。整个压缩包共包含645个文件大小约9.2MB其中HTML与JSP文件提供页面和动态脚本Java源文件与Class文件对应编译前后的代码JAR包是依赖库XML与Properties保存服务器及日志等配置Shell脚本用于启动、停止和检测服务。解压后目录结构完整bin、conf、lib、logs、temp、webapps、work各目录分工明确尤其是webapps可直接放置WAR包自动部署conf/server.xml可调整端口、虚拟主机和SSL证书。该版本支持Java EE 8规范中的WebSocket、JSP 2.3、EL 3.0等特性便于开发现代化应用提供了灵活的基础能力目前已有555人学习。适合初次接触Tomcat的开发者用于本地测试也适合运维人员作为生产环境的基础服务器。1. 为什么还在用 tomcat 8.5.35一个稳定到懒得换的版本如果你接手过几套上了年纪的 Java Web 系统大概会明白「版本号越老并不代表越落后」的道理。tomcat 8.5.35 是 8.5 系列里一个非常经典的发布版本支持 Servlet 3.1、JSP 2.3、WebSocket 1.1兼容 JDK 7 到 JDK 9 的范围正好覆盖了大多数传统企业项目的运行时要求。很多生产环境至今跑在它上面不是因为团队不想升级而是因为升级到 9.0 或 10.x 之后一堆老项目的 javax.servlet 依赖会直接翻车。这篇笔记要拆的资源就是 Linux 版本的 tomcat8-8.5.35.tar.gz它不是源码包是官方编译好的二进制发行包解压即可用。适合这几类人刚接触 Linux 部署 Java Web 应用的新手、需要在内网离线环境装一套兼容旧项目的服务器、以及被某些中间件厂商文档要求只能使用这个版本的老油条。接下来我会把安装、配置、部署、排障的全过程都走一遍所有命令都是我自己实际用过且在干净系统上验证过的你可以直接照着抄。2. 安装与启动从 tar.gz 到能跑通的第一个页面2.1 解压前的三件事确认 JDK、确认端口、确认所有者tomcat 本质是一个 Java 程序没有 JVM 它什么都干不了。所以安装 tomcat 的第一步不是解压而是确认这台机器上有没有 JDK。常见做法是先跑java -version如果提示找不到命令就说明要么没装要么没配环境变量。我一般会在解压前把 JDK 的事彻底搞定否则后面启动 tomcat 时会报JAVA_HOME相关的错。以 OpenJDK 8 为例安装命令因发行版而异但配置环境变量是通用的。把下面的内容写进/etc/profile或者/root/.bashrc里二选一export JAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 export PATH$JAVA_HOME/bin:$PATH export CLASSPATH.:$JAVA_HOME/lib:$JAVA_HOME/lib/tools.jar这段配置的作用是让系统在任意目录下都能识别java和javac命令同时告诉你 JVM 的家在哪。JAVA_HOME必须指向 JDK 的安装根目录而不是 bin 目录因为 tomcat 的启动脚本会通过它去拼接类库路径。设置完记得执行source /etc/profile再用java -version验证。接着检查端口。tomcat 默认会占用 8080HTTP和 8005关闭指令如果这两端口被其他程序占了就算启动成功了也访问不到。可以用netstat -tlnp | grep -E 8080|8005先看一眼有输出就说明被占用后面避坑章节会细讲。2.2 解压、改名、设置所有者一套标准的落地姿势拿到tomcat8-8.5.35.tar.gz之后不要直接解压到/usr/local/就算完事。我建议按下面的流程操作能少踩很多权限相关的坑mkdir -p /opt/tomcat tar -zxvf tomcat8-8.5.35.tar.gz -C /opt/tomcat mv /opt/tomcat/apache-tomcat-8.5.35 /opt/tomcat/server useradd -r -s /sbin/nologin tomcat chown -R tomcat:tomcat /opt/tomcat/servertar -zxvf是把包解压到指定目录-C参数指定目标位置解压出来的目录名一般会带完整版本号所以用mv改成短名字server方便后续配置脚本。创建一个系统用户 tomcat不给它登录 shell 权限是为了让 tomcat 进程以低权限用户运行防止被黑后直接拿到 root shell。最后一行把整个 tomcat 目录的所有者改成 tomcat这样它才有权限写日志和临时文件。有一个细节容易漏bin/目录下的脚本默认有执行权限但如果是 Windows 下解压再传到 Linux 上权限会丢失。遇到Permission denied就直接chmod x /opt/tomcat/server/bin/*.sh。2.3 第一次启动startup.sh 与 catalina.out 的双通道验证tomcat 的启动脚本有两个startup.sh是推荐入口catalina.sh run是前台启动方式。我第一次部署时就犯过傻直接用了catalina.sh run结果终端窗口一关 tomcat 就没了。所以正常启动应该用startup.shsu -s /bin/bash tomcat -c /opt/tomcat/server/bin/startup.sh注意这里用了su切换用户因为当前我们可能是 root直接跑 startup.sh 会让进程属于 root违背了安全原则。脚本会调用catalina.sh start日志会追加到logs/catalina.out同时生成 PID 记录在logs/tomcat.pid如果配置了的话。启动之后不要急着马上访问等两秒再执行ps -ef | grep tomcat | grep -v grep tail -f /opt/tomcat/server/logs/catalina.outps是确认进程活着tail -f是看实时日志。正常情况下会看到一行Server startup in [xxx] milliseconds这就代表启动成功了。然后打开浏览器访问http://服务器IP:8080见到那只猫的首页就万事大吉。3. 核心配置server.xml 里藏着的连接器、端口和性能开关3.1 Connector 参数不只是改端口还得调并发tomcat 的 HTTP 能力全在conf/server.xml里的Connector节点。默认配置看着能跑但压测一上就去见鬼。先看一段常见配置我会在关键参数上写注释Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 maxThreads200 minSpareThreads25 acceptCount100 disableUploadTimeouttrue compressionon compressionMinSize2048 compressibleMimeTypetext/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json/maxThreads是最大工作线程数决定同时能处理多少请求。别盲目设成 1000因为线程多了上下文切换也费 CPU一般单核设 100 左右四核给到 300 就很充裕了。acceptCount是等待队列长度排队请求超过它就开始拒绝连接这个值太小容易丢请求太大又容易让用户等半天我习惯设成maxThreads的一半。compression打开能显著减小响应体但注意它只对静态资源和文本类型生效图片视频就别指望了。还能开connectionTimeout这是建立连接后等待数据传输的毫秒数设置太长会留着大量半开连接占着线程不干活。3.2 JVM 内存与 GC 参数catalina.sh 里的后悔药tomcat 默认的 JVM 内存很小跑一个稍大的 war 包就java.lang.OutOfMemoryError: Java heap space。修改的方式不是改 server.xml而是改bin/catalina.sh。在文件开头注释区后面加一段自定义配置CATALINA_OPTS-Xms1024m -Xmx1024m -XX:MaxMetaspaceSize256m -XX:UseG1GC -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/opt/tomcat/server/logs/heapdump.hprof-Xms是初始堆大小-Xmx是最大堆大小。这两个值我一般设成一样免得 JVM 运行时频繁伸缩内存带来无谓的停顿。MaxMetaspaceSize是方法区上限JDK 8 之后不再有 PermGen改叫 MetaspaceSpring 项目加载类多给 256m 比较稳。UseG1GC是 G1 垃圾回收器适合大堆和低停顿场景。最关键的是HeapDumpOnOutOfMemoryError这个参数能在 JVM 崩溃时自动导出一份堆转储文件后面排查 OOM 全靠它。改完必须重启 tomcat 才能生效。查看是否生效执行ps -ef | grep java | grep -v grep jmap -heap PIDjmap -heap会把当前 JVM 的堆配置全部打出来确认MaxHeapSize是你设置的值。如果没有jmap命令说明 JDK 不在 PATH 里回 2.1 检查环境变量。3.3 虚拟主机与 context 路径让一个 tomcat 跑多个项目生产环境经常需要在一台 tomcat 上区分多个域名或上下文路径。默认情况下你把 war 包丢进webapps/它就会以 war 包文件名作为访问路径。比如文件名是demo.war访问 URL 就是http://ip:8080/demo/。如果想用根路径访问有两种做法一是把 war 包改名成ROOT.war覆盖自带的管理页面二是配Context节点。我比较推荐后面这种方法因为它能指定外部路径不污染 webapps 目录。在server.xml里找到Host节点添加如下内容Host namewww.example.com appBase/data/webapps unpackWARstrue autoDeployfalse Context path docBase/data/webapps/demo reloadablefalse/ /Host这里Host的name是域名appBase指向外部应用目录docBase指向具体的项目目录。reloadablefalse表示不热加载 class 文件避免在修改类文件时触发自动重启生产环境我都是关掉的开发环境才开。注意多个 Host 共用一台机器时必须通过端口或域名区分如果只有一个 IP得在Engine上配defaultHost指向某个 Host 的名字。4. 应用部署与日志应用war 包能不能跑起来看这几个目录就够了4.1 war 包部署的正确姿势停止服务再丢包别学在线更新很多人图省事直接用cp命令把 war 包扔进webapps/然后等 tomcat 自动解压。对 demo 项目无所谓但对生产应用我吃过一次亏在系统运行中替换 war 包tomcat 检测到文件变化后自动热部署但旧的 class 还在内存里新老逻辑混在一起出现了一堆诡异的重复数据。所以我的标准流程是systemctl stop tomcat # 或者调用 shutdown.sh mv /opt/tomcat/server/webapps/old.war /opt/tomcat/server/webapps/old.war.bak rm -rf /opt/tomcat/server/webapps/old # 删除已解压的目录 cp /path/to/new.war /opt/tomcat/server/webapps/ chown tomcat:tomcat /opt/tomcat/server/webapps/new.war systemctl start tomcat把旧 war 改成.bak是一种后悔药万一新版有问题还能快速回滚。删除解压目录是为了防止旧 class 残留因为 tomcat 解压 war 时如果发现目录已存在会跳过部分文件。最后必须确保 war 包所有者对 tomcat 用户可读否则启动时会报 Failed to deploy。部署完成后验证方式有三种看catalina.out里有没有Deployment of web application archive [new.war] has finished访问上下文路径的首页以及用curl -I检查 HTTP 状态码。4.2 日志体系catalina.out、localhost.log、manager.log 谁管什么tomcat 的log/目录下有好几种日志新手容易全部让它们堆在 catalina.out 里其实它们各司其职。我用一张表说明日志文件记录内容什么时候看它catalina.out标准输出和标准错误包括 System.out 打印的调试信息启动失败、线程卡死、部分业务输出localhost.logHost 应用的日志主要是 ServletContext 初始化和异常项目无法启动时这里会有 StackTracelocalhost_access_log.2025-xx-xx.txtHTTP 访问日志每个请求的方法、URL、状态码、耗时排查接口调用失败、分析压测数据manager.logHost Manager 和管理员操作日志不是必须但能看谁干了什么默认只开启了 catalina.out 和 localhost.log。访问日志默认是关闭的在server.xml的 Host 节点上加入Valve才能开启Valve classNameorg.apache.catalina.valves.AccessLogValve directorylogs prefixlocalhost_access_log suffix.txt pattern%h %l %u %t quot;%rquot; %s %b %D /%D是请求处理耗时毫秒这个参数在生产排查慢接口时非常有用。如果没开访问日志遇到用户报「页面卡了」就只能干瞪眼。4.3 离线部署环境从开发机拷贝 tar.gz 到内网服务器的注意事项很多企业内网和外网物理隔离你手里的tomcat8-8.5.35.tar.gz可能是从某台能上网的机器上拉下来的。拷贝时有一个容易忽略的坑用 FTP 或 U 盘传文件二进制格式被转成 ASCII 模式tar.gz 会损坏解压时报gzip: invalid compressed data。解决方法是在源机器上先计算哈希值传到目标机器后再校验md5sum tomcat8-8.5.35.tar.gz # 源机器执行记录输出 md5sum tomcat8-8.5.35.tar.gz # 目标机器执行比较两行是否一致如果哈希不一致别怀疑是包的锅重新用二进制方式传一次。另外在离线环境下没有 yum/apt 源如果目标机器缺 JDK你得提前把 JDK 的 tar.gz 也一起带进去而且要注意 JDK 版本必须和 tomcat 兼容。tomcat 8.5.35 在 JDK 8 上跑得最好别拿 JDK 11 去装有些反射调用会直接 IllegalAccessError。5. 常见问题避坑启动失败、端口占用、内存泄漏的排查记录5.1 现象8080 端口起来但请求一直卡住不返回有次某开发人员反馈tomcat 启动成功了首页也能开但一点登录按钮就转圈等几十秒才报 504。当时我先tail -f catalina.out没看到异常又curl -o /dev/null -s -w %{http_code} %{time_total}\n http://localhost:8080/demo/发现响应时间高达 30 秒。原因maxThreads被调成 50但应用里有一个慢 SQL 线程阻塞住了等待数据库返回时把线程池全部占满后续请求在acceptCount里排队。解决先jstack PID thread.txt抓线程快照找到大量TIMED_WAITING状态的线程定位到数据库连接池连接等待。随后把连接池最大连接数从 10 提到 50并给慢 SQL 加索引tomcat 侧维持maxThreads200不变。从那以后我每次压测前都会先看一眼活跃线程数是不是把线程池打满。5.2 现象启动就报Address already in use: JVM_Bind如果你在启动时看到这种异常不用怀疑端口被占了。常见原因有三种上一次 tomcat 进程没有完全停止残留的 Java 进程还占着 8080另一个 Java Web 容器比如 Spring Boot 内嵌 tomcat 也跑了同样的端口或者监听了8005端口的管理进程没关。排查与解决netstat -tlnp | grep 8080 lsof -i :8080找到占用进程的 PID 后确认是不是彻底死掉。如果是残留进程kill -9 PID再启动。如果是别的应用就改server.xml里的port8080改成8081等不冲突端口同时记得把redirectPort和8005也要改。另外有一个玄学点tomcat 有多个 Connector别只改 HTTP 的那个AJP Connector 默认也占用8009如果计划不用 AJP直接注释掉这一行。5.3 现象部署的 war 包启动一半突然无声无息地消失了某模拟项目X的 war 包部署上去catalina.out 里打了一堆日志然后进程直接退出没有任何异常堆栈。我当时第一反应是看logs/下所有文件后来发现catalina.out里的日志在被系统切断后会写入localshost.log但那个文件也没有。最后在messages系统日志里找到Out of memory: Kill process (java) score 361 or sacrifice child。原因物理内存本身不足系统 OOM Killer 直接杀掉了 java 进程JVM 自己的HeapDumpOnOutOfMemoryError没来得及触发。解决加物理内存不现实那就降低-Xmx到 512m并限制-XX:MaxDirectMemorySize同时把 less 空闲进程清理掉。经过这次我明白了一个原则tomcat 的 JVM 参数不能只看应用需求还得看整台机器有多少内存分配前先跑free -g看一下存量。5.4 现象tomcat 能访问但图片验证码加载失败某个老系统的验证码是 jsp 页面生成的部署到 8.5.35 上之后页面能打开但验证码图片显示 X。日志里报ClassNotFoundException: com.sun.image.codec.jpeg.JPEGCodec。原因:JDK 8 到后来版本移除了一些内部类这个 JPEGCodec 在 JDK 9 以后就被移除了。tomcat 8.5.35 本身支持 JDK 8但如果你用的是更高版本的 JDK老代码里调用的内部类就不存在了。解决要么换回 JDK 8要么把生成验证码的库替换成开源组件。这提醒我每次换 JDK 版本时必须先跑一遍项目的冒烟测试用例别只看 tomcat 能启动就认为万事大吉。5.5 现象日志打印出现中文乱码启动脚本里没有指定文件编码时tomcat 默认使用操作系统 locale。如果你的系统是 LANGzh_CN.UTF-8catalina.out 正常如果 LANGC中文全变问号。而有些部署同学的 session 是 Windows SSH 客户端上传的 server.xml 含有中文注释保存成 GBK 编码tomcat 读到时会因为 BOM 导致 XML 解析失败。解决在setenv.sh中强制指定 JVM 编码echo CATALINA_OPTS-Dfile.encodingUTF-8 -Dsun.jnu.encodingUTF-8 /opt/tomcat/server/bin/setenv.sh同时下载资源里的所有 XML 文件如果要在 Windows 下编辑存盘时选 UTF-8 无 BOM 格式。我曾经被这个坑折磨了半天从那以后所有配置文件一律先file命令看一下编码。6. 进阶技巧用 systemd 托管 tomcat 并实现自启动与故障自动拉起到了这一步tomcat 已经能跑能部署但离「生产级」还差一条万一服务器重启tomcat 能否自动起来进程如果崩了有没有东西能自动拉起它答案是用 systemd 写一个 service 单元。这也是我在所有生产环境上都强制走一遍的收尾工作。首先让 tomcat 不使用自身的startup.sh和shutdown.sh而是让 systemd 来管理生命周期。新建一个服务文件cat /etc/systemd/system/tomcat.service EOF [Unit] DescriptionApache Tomcat 8.5.35 Server Afternetwork.target [Service] Typeforking Usertomcat Grouptomcat EnvironmentJAVA_HOME/usr/lib/jvm/java-8-openjdk-amd64 EnvironmentCATALINA_HOME/opt/tomcat/server EnvironmentCATALINA_BASE/opt/tomcat/server ExecStart/opt/tomcat/server/bin/startup.sh ExecStop/opt/tomcat/server/bin/shutdown.sh 60 ExecReload/bin/kill -HUP $MAINPID Restarton-failure RestartSec5 SuccessExitStatus143 [Install] WantedBymulti-user.target EOF这个文件的要点是Typeforking告诉 systemd 启动脚本会 fork 出一个子进程systemd 跟踪这个子进程作为主进程。ExecStop里60表示等待 60 秒让 tomcat 优雅关闭如果超时再强制杀。Restarton-failure表示进程异常退出时自动拉起但如果是手动systemctl stop就不会触犯重启逻辑。SuccessExitStatus143是因为 java 进程收到 SIGTERM 时会返回 143systemd 默认把它当错误这里显式标记为成功。写好后执行systemctl daemon-reload systemctl enable tomcat systemctl start tomcat systemctl status tomcatenable是设置开机自启start是启动服务。注意之前我们直接用startup.sh启动过一次现在再用 systemd 启动会冲突。所以先ps -ef | grep java找到旧进程kill掉再让 systemd 接管。否则会出现 ExecStart 成功但端口已经被占用的情况。还有一个验证技巧检查 systemd 视角下的日志journalctl -u tomcat -f这个命令的输出包含 tomcat 的启动日志和 systemd 的启动事件比tail -f catalina.out多了一层上下文。以后排查故障时第一件事不是看 catalina.out而是看journalctl里有没有 OOM、kill 或 timeout 记录。配合 systemd还有一个实用习惯每台服务器的 tomcat 我都会额外写一个健康检查脚本而不是只依赖 JVM 活着。因为进程活着不意味着应用可用。脚本内容很简单curl -f -s -o /dev/null http://127.0.0.1:8080/healthz || systemctl restart tomcat把这个脚本加进 crontab 每分钟执行一次接口/healthz在应用里返回 200 就代表应用活着。如果某个接口开始 404 或 500且连续三次就自动重启 tomcat。这套组合让我在凌晨两点被电话吵醒的次数少了很多。如果你拿到tomcat8-8.5.35.tar.gz只是为了跑一个开发环境那 systemd 可能有点重。但只要是部署在测试服务器或生产服务器我强烈建议用 systemd 做托管。我给自己定的规矩是每新建一个 tomcat 实例必须先把systemd service文件写好再启动否则后面容易留下孤儿进程和数据目录权限混乱的隐患。希望这个习惯能帮到你免得你在服务器重启后焦急地打电话喊运维起床。本文还有配套的精品资源点击获取