Tomcat 8.5.35 Linux部署实战:配置、避坑与调优全指南
简介这是一份面向 Linux 环境的 Tomcat 8.5.35 安装包适合后端开发、运维人员以及需要搭建 Java Web 服务的入门学习者。压缩包包含完整发行组件解压后即可获得 Catalina、Jasper、Coyote 等核心模块支持 Servlet、JSP、WebSocket 与 Java EE 8 规范。包体共 645 个文件约 9.2MB主要涵盖 HTML 页面、Java 类与源码、JSP 页面、JAR 库、XML 配置、Shell 启动脚本及 GIF/PNG 图标等既可支撑直接部署也为二次开发提供了参考。已有 555 人学习下载。内部保留 bin、conf、lib、logs、temp、webapps、work 等标准目录结构bin 放置启动与停止脚本conf 保存核心服务器配置lib 存放运行所需 JAR 依赖webapps 用于投放应用包work 记录 JSP 编译产物与临时数据。其中 class 文件与 Java 源码并存适合在排错或定制功能时对照查看XML 配置和 Shell 脚本则覆盖了日常启停、端口调整、安全加固等常见运维操作。整体体积小巧、版本稳定适合在 Linux 服务器上快速搭建可靠、可扩展的 Java Web 运行环境。1. 为什么这个 tar 包让不少人在 Linux 上就是一遍过Tomcat 8.5.35 的 Linux tar.gz 包是 Java 8 时代最常见的应用容器安装包它提供了解压即用的目录结构只要 JAVA_HOME 指对、端口和权限没被占启动就是一条命令的事。这一个版本正好卡在 javax 命名空间和 Java 8 兼容性的舒适区很多老的业务系统都愿意钉在它上面。适合刚接手 Linux 服务器、想自己布一个能跑 war 的 Java 环境的人也适合在多个机器上批量铺同版本容器的运维同学。后面所有步骤都在这个版本上验证过照着做基本不会出现“教程说得通、自己跑不通”的断档。2. 装之前把三件事定下来JDK、tar 包校验和目录规划2.1 先确认 JDK 8 和 8.5.35 是否真的匹配Tomcat 不捆绑 JDK它只是消费一个已经装好的 Java 运行时。8.5.35 在编译期需要 Java 7 以上的 JDK 才能运行但在实际部署里我一般只推荐配对 Java 8。一个原因是 8.5.x 中期的很多类加载和反射路径都在 Java 8 上被反复验证过换到 Java 11 或更高版本容易出现“能启动但个别功能报 NoClassDefFoundError”的隐性问题另一个原因是这个版本诞生年代正好是 Java 8 大规模铺开的时候配套的旧项目、旧插件也都是按 Java 8 写的。还有个容易被忽略的点系统自带的 java 命令未必是你要的那个。Linux 上多版本 JDK 共存是常态命令行里敲 java -version 返回的可能来自某个其它路径而 Tomcat 的启动脚本判断 JAVA_HOME 用的是环境变量不依赖 PATH。所以安装 Tomcat 前我会先写死 JAVA_HOME而不是等着从 PATH 里继承。检查当前默认 Java 的做法很简单java -version echo $JAVA_HOME如果输出的版本和你预期不一致先不要往下解压先把环境变量问题解决。否则后面所有启动报错都会指向同一个原因找不到 JAVA_HOME但真正问题其实是 PATH 里那个 java 指向了别处。这一步花三十秒能省掉后面一小时排错。我自己的习惯是准备一个专门记录 Java 安装路径的小本子每台机器装完先写一行避免多个服务器来回切换时记混。2.2 下载和校验不是解压完就完事拿到 Linux 版本 tomcat8-8.5.35.tar.gz 之后第一步先确认文件大小是否合理。Tomcat 官方发布的 tar.gz 一般只有十几到几十 MB因为它不捆绑 JDK也不带一堆运行时。如果下载来源显示这个包超过 200MB十有八九是个改过的版本直接换源。下载完成后我会先对一下校验值再解压sha256sum tomcat8-8.5.35.tar.gz # 输出结果和发布页给出的 SHA256 值比对一致再继续这里用 SHA256 而不是 MD5MD5 在现代场景下已经能被碰撞为了做一个可复现的完整性基线至少要用 SHA256。校验值对不上时常见处理是重新下载一次偶尔是网络代理缓存了损坏分片导致。注意要以发布页同一个链接下的校验文件为准不要拿旧链接的校验值硬套。解压这一步我一般放进规划的安装路径父目录而不是先解压到临时目录再移动。移动大目录会遇到属主、权限位丢失的问题而且临时目录的文件可能被清理任务误删。解压命令是cd /opt tar -xzf /root/software/tomcat8-8.5.35.tar.gz ls -d */ # 确认解压出的目录名 ln -s /opt/apache-tomcat-8.5.35 /opt/tomcat # 目录名以实际解压结果为准参数说明-x 是解包-z 是解压 gzip-f 指定文件名。解压后建立一个软链接 /opt/tomcat 指向真正的版本目录。这样做的原因是以后升级就把新版本解压成新目录然后改一下软链接指向业务脚本里写路径不用跟着改。如果跳过软链接直接使用版本号目录每次升级都要同步改所有配置文件迟早漏一处。这个“版本目录 软链接固定名”的做法在多个 Java 服务并存时特别有用。2.3 解压后目录里每一层是干什么的解压完成后安装目录下会看到 bin、conf、lib、logs、temp、webapps、work 这七个一级目录。很多人一上来就盯着 conf/server.xml 改其实先花两分钟把目录职能过一遍后面排查问题会快很多。bin 目录里是启动和停止脚本。startup.sh 和 shutdown.sh 是给用户直接调用的入口catalina.sh 是真正的执行主体接收 start、stop、run 这些参数。conf 目录是所有配置文件的集中地server.xml 管端口和 Hostweb.xml 管全局 Servlet 默认配置context.xml 管全局上下文参数。logs 目录是运行时日志的输出位置catalina.out 是主日志。webapps 目录是 war 包放置位置默认部署就是丢进去然后等自动解压。work 目录是 JSP 编译后的 class 缓存遇到改 JSP 不生效的怪问题时先清这个目录。还有一个容易忽略的是 temp 目录。Tomcat 运行时常往 temp 写临时文件如果这个目录被系统定时清理脚本误删会出现启动时正常、运行一段时间后偶发找不到临时文件的现象。所以我不建议把 Tomcat 放进 /tmp 下面而是放在 /opt 这种稳定的业务分区。如果机器上只有一块盘至少保证日志目录所在分区的剩余空间是 war 包体积的三倍以上因为解压后的目录树有大量小文件磁盘 inode 耗尽也会表现为“创建文件失败”。这一步和第 4 章的几个坑都有关系提前规划好能省掉后续很多麻烦。装前的三件事到这里就齐了JDK 对齐、校验比对、目录规划。这些基础打稳真正启动时的故障率会直线下降。3. 让 Tomcat 跑起来三处关键配置和启动验证3.1 JAVA_HOME 写到哪里setenv.sh 是受控入口Tomcat 启动脚本读取环境变量是有顺序的catalina.sh 会先加载 bin/setenv.sh如果存在再用这个文件里的变量覆盖系统级的 export。所以自定义环境变量要写进 setenv.sh而不是直接改 catalina.sh。直接改 catalina.sh 的问题在于升级时新版本会覆盖这个文件而 setenv.sh 是官方预留的自定义入口升级后依然保留不会被覆盖。创建 setenv.sh 并写入 JAVA_HOMEcat /opt/tomcat/bin/setenv.sh EOF JAVA_HOME/usr/local/jdk1.8.0_202 JRE_HOME$JAVA_HOME/jre CATALINA_HOME/opt/tomcat CATALINA_BASE/opt/tomcat EOF chmod x /opt/tomcat/bin/setenv.sh参数说明JAVA_HOME 指向 JDK 安装路径这里顺手写了 JRE_HOME其实 8.5.35 只要 JAVA_HOME 配置正确JRE_HOME 可以省略但写上之后对旧版脚本兼容性更好。CATALINA_HOME 是 Tomcat 安装根目录CATALINA_BASE 在单实例下和 CATALINA_HOME 相同多实例时每个实例指向自己的目录。这里用 heredoc 方式创建文件好处是文件内容不会被执行 shell 展开路径里的特殊字符都能原样保留。注意这个文件里的变量不会自动导出到系统全局环境变量它只在 Tomcat 的启动脚本里生效。如果还希望 SSH 登录后能直接敲 catalina 命令需要在 /etc/profile.d/ 下追加脚本但那不是必须的。我见过有人在 setenv.sh 里写 export 后以为改的是全局结果机器重启后环境变量消失又回来排查半天。养成“环境变量归属清晰”的习惯这类玄学问题就能少一大半。3.2 server.xml端口、连接数与 Host 配置的推荐初值conf/server.xml 是 Tomcat 主配置文件。新手最常见的做法是只改 HTTP 端口号就收工但在 8.5.35 上还有几个参数值得在部署前一次性设置好。下面是一个比较完整的 Connector 配置Connector port8080 protocolHTTP/1.1 connectionTimeout20000 maxThreads200 minSpareThreads10 acceptCount100 redirectPort8443 maxPostSize10485760 URIEncodingUTF-8/逐项说明port 是监听端口建议不要用 8080 裸奔改成不常见的端口比如 18080至少能少挨一点扫描脚本的骚扰。connectionTimeout 是客户端建立连接后读请求的超时时间单位毫秒20000 对大多数业务足够。maxThreads 是处理 HTTP 请求的最大工作线程数决定 Tomcat 同时能处理多少请求minSpareThreads 是空闲时保持的最小线程数防止请求突发时现场建线程。acceptCount 是请求队列长度当线程全部忙时新请求会进队列等待。这两个参数要配合看maxThreads acceptCount 不是越大越好因为 Linux 每个线程都要占栈空间200 个线程对应 1MB 线程栈就是 200MB 虚拟内存设置过大会给 JVM 的堆带来额外压力。maxPostSize 是表单提交的最大字节数默认是 2MB如果你的业务要上传大文本或 base64 内容默认值很容易触发报错改成 1048576010MB是一个常见的起步值。URIEncoding 在 8.5.x 默认已是 UTF-8显式写出来是为了给后来维护的人一个提示中文参数乱码并不是改这里能解决的先检查前端页面编码和数据库字符集。Host 节点里常用的配置是 appBase 和 autoDeploy。appBase 是 webapps 目录路径默认是 webappsautoDeploy 决定是否自动部署新放入的 war 包。生产环境我习惯把 autoDeploy 设为 false因为线上发布应该走受控流程而不是一丢文件就自动挂载导致正在被访问的应用被 reload 打断。另外 Server 标签里默认 shutdown 端口是 8005密码是 SHUTDOWN这个端口一定不要暴露到公网最省心的做法是只监听本机回环地址。3.3 启动、停止和确认“真的起来了”配置完成后第一个启动命令很多人会直接敲 startup.sh。这条命令的本质是让 Tomcat 后台启动用 nohup 方式把 JVM 进程拉起来日志写到 logs/catalina.out。启动后马上会遇到的困惑是命令返回了但不知道成功没有。/opt/tomcat/bin/startup.sh tail -f /opt/tomcat/logs/catalina.out启动脚本会在终端输出 Tomcat started 以及耗时但真正能说明问题的是 catalina.out 里的最后几行。如果看到 INFO: Server startup in [xxx] milliseconds那才是启动完成。只有 Starting 看不到下文多半是端口没起来或者 Host 配置解析失败。验证方式有三种从外到内curl -s -o /dev/null -w %{http_code} http://127.0.0.1:18080/ jps -l | grep Bootstrap netstat -tlnp | grep 18080curl 返回 200 说明 HTTP 层正常jps 里能看到 org.apache.catalina.startup.Bootstrap 进程说明 JVM 层的主进程活着netstat 确认端口在监听。这三条同时满足才算这一轮启动真正通过。如果 curl 返回连接拒绝但 jps 有进程问题大概率在端口配置或防火墙先看 netstat 和 server.xml 是否一致。如果 jps 都没有这个 Java 进程回 catalina.out 末尾看异常栈基本能定位到具体哪一行配置出错。这套验证顺序我一直习惯用每次都能把“到底起没起来”的争论直接终结。4. Tomcat 8.5.35 部署中的五个高频坑与排查思路先说结论Tomcat 的报错信息已经写得相当直白多数启动失败在 catalina.out 前二十行就能定位。真正浪费时间的是那些“日志没报错但行为不对”的问题。这一章讲五个从实际部署里拎出来的坑每条都按现象、原因、解决的顺序写。4.1 明明装了 JDK启动却说找不到 JAVA_HOME 或 JRE_HOME现象执行 startup.sh 后终端直接打印 Neither the JAVA_HOME nor the JRE_HOME environment variable is defined随后退出。 原因catalina.sh 判断的是环境变量而不是仅仅看 PATH 里有没有 java。很多机器上 java 是通过软链接或版本管理工具动态加载的shell 里敲 java 能用但 JAVA_HOME 从未被导出。这类问题在使用系统自带包管理器安装 Java 时特别常见。 解决不要往 /etc/profile 里乱加而是在 setenv.sh 里显式写死 JAVA_HOME 绝对路径写完后重新登录会话再启动。判断是否生效的办法是 echo $JAVA_HOME能打印出路径才算导出成功。这里有个容易翻车的细节setenv.sh 里的 JAVA_HOME 赋值要写绝对路径不能写成 JAVA_HOME$(which java) 这种动态取值因为 which 解析出来的往往是软链接路径真正的 JDK home 在链接的上一层。这个原因几乎解释了我见过的一半“明明装了就说不存在”的案例。4.2 改完端口还是访问不到防火墙拦截在背锅现象catalina.out 显示启动成功本机 curl 127.0.0.1:8080 有响应但从另一台机器访问超时或连接拒绝。 原因Tomcat 监听没问题问题出在系统层面的包过滤。常见的是防火墙规则未放行新端口或者 SELinux 对非标准端口有拦截策略。 解决先确认 Tomcat 确实在监听用 ss -tlnp | grep 18080 看监听地址再给防火墙放行。注意不要为了省事直接关掉防火墙只放行必要端口就好。如果机器开了 SELinux还需要用对应工具把端口加入允许列表。这一类问题的排查要诀是先本地再跨机先端口再防火墙不要一上来怀疑 Tomcat 配置。我见过有人在 server.xml 里改了三遍端口最后发现是防火墙规则把所有高端口都 drop 了和 Tomcat 一点关系都没有。4.3 启动成功但 catalina.out 里没有日志或者文件越写越大现象Tomcat 在跑业务也正常但 logs/catalina.out 长时间不更新或者这个文件膨胀到几个 GB。 原因catalina.out 是标准输出重定向文件如果应用代码里的日志框架把输出定向到别的文件比如用 logback 的 RollingFilecatalina.out 就不会有新内容。同时System.out.print 这类原生日志如果被大量使用且没配置轮转文件就会一直增长直到把磁盘填满。 解决生产环境把业务日志交给日志框架管理用 logback 或 log4j2 输出到独立文件并配置滚动策略。catalina.out 只保留启动信息和未捕获异常。如果 catalina.out 已经太大收割日志前先确认业务日志在哪然后清空它 /opt/tomcat/logs/catalina.out清空不需要停 Tomcat因为 JVM 持有的是文件描述符而不是文件锁用重定向清空内容不影响运行。这算是 Linux 下少见的“可以边跑边清”的场景但要注意清空后要及时补上 logrotate 配置否则同样的问题会再回来。4.4 root 启动一时爽日志权限埋雷现象用 root 执行 startup.sh 启动成功后面切换计划任务或监控脚本去读日志时报权限不足甚至应用自己无法写日志。 原因root 启动后logs 目录下的新文件属主都是 root后续服务如果以普通用户身份运行或采集日志会因权限不足读取失败。反过来用普通用户启动时如果 war 包临时目录 work 属主不对JSP 编译也会失败。 解决部署前先创建专用系统用户把安装目录属主递交给它再统一用这个用户启动useradd -r tomcat chown -R tomcat:tomcat /opt/tomcat su - tomcat -c /opt/tomcat/bin/startup.sh以后所有操作都在这个用户名下做。启动脚本里不要用 sudo 包裹直接用 su 切换身份。这一步能规避后续一大堆权限类故障特别是你有监控采集、日志转发这类自动化任务时权限统一是减少抓狂前提。4.5 解压后的目录缺少可执行权限脚本报 Permission denied现象tar 包明明解压成功执行 ./bin/startup.sh 时报 Permission denied但 ls 看文件没有异常。 原因tar 包本身在压缩文件里保留了可执行位但如果你是通过某些 Web 管理页面或 FTP 工具上传的文件流转过程中权限位被剥掉了。有些文件系统的挂载选项也会忽略 exec 权限。 解决给脚本补上执行权限chmod x /opt/tomcat/bin/*.sh执行后再跑 startup.sh。如果还报权限问题检查所在分区是不是以 noexec 方式挂载的用 mount 查看挂载选项。如果分区是 noexec把 Tomcat 解压到非 noexec 分区即可。这个问题本身不复杂但第一次碰到的人往往会怀疑是包损坏重新下载一遍浪费时间其实一行 chmod 就解决。5. 让它更扛打JVM 参数、线程池配置和 systemd 托管5.1 JVM 内存参数给 Tomcat 预留多大的堆才合理默认情况下Tomcat 的 JVM 启动参数沿用 Java 虚拟机的默认值。这里有个误解点64 位 JDK 默认最大堆是物理内存的四分之一听起来不小但实际是它只在真正需要时才把堆撑到那个值年轻代大小、垃圾回收策略都不会针对 Web 容器做优化。所以生产部署我会显式设置初始堆和最大堆在 setenv.sh 里追加CATALINA_OPTS-Xms512m -Xmx2048m -XX:MetaspaceSize256m -XX:MaxMetaspaceSize512m -Djava.awt.headlesstrue参数说明-Xms 是堆初始大小-Xmx 是堆最大上限。设置 -Xms 的意义在于让 JVM 启动时就申请好内存避免运行时缓慢扩张。堆大小怎么估算按一台 2 核 4G 的虚拟机Tomcat 独占的话堆给 2G留 1.5G 给系统缓存和线程栈。如果机器上还跑着数据库堆就要压到 1G 以内堆设得再大也只是和数据库争内存性能反而更差。MetaspaceSize 是元空间的初始大小Java 8 里取代了原来的 PermGen不设上限的话类加载特别多时会吃掉大量物理内存所以设一个 MaxMetaspaceSize 兜底。headlesstrue 是为了在没有显示设备的 Linux 上让图片处理等 AWT 功能正常工作不加它某些报表导出功能会抛 HeadlessException。这里必须提一个关键点CATALINA_OPTS 里的参数只会传给 Tomcat 的 JVM 进程而 JAVA_OPTS 是给所有 Java 相关脚本用的。在 catalina.sh 里两个变量的加载位置不同。调 Tomcat 时用 CATALINA_OPTS 更精确避免把堆参数泄漏给其它调用 Java 的脚本否则可能出现“我只是配了 Tomcat 参数结果某天另一个 Java 工具启动时报堆内存冲突”的怪事。5.2 Connector 性能参数怎么配先看请求特征server.xml 里的 Connector 参数直接决定 HTTP 请求的吞吐上限。前面第 3 章已经给了推荐初始值这里再补充怎么根据请求特征调整而不是照抄默认值。默认配置下Tomcat 的 maxThreads 是 200acceptCount 是 100。如果应用是重 IO、低 CPU 的比如对外提供文件下载线程数可以适当调高但要注意线程栈内存叠加。如果应用是重 CPU 计算的比如大量加解密线程数反而要调低因为 CPU 核数就那么多线程再多只是排队。更重要的一个点是协议选择。8.5.35 默认的 HTTP/1.1 走的是 NIO 模型不需要额外配 BIO。要确认当前用的是哪个模型看启动日志里会出现 http-nio-8080 这样的协议名看到 nio 就说明走的是 NIO。如果在 Connector 标签上加一个 executor 属性可以把线程池统一管理起来但引入后要特别注意 maxThreads 的归属线程池里配的是全局共享上限Connector 上的 maxThreads 会失效。很多人同一台机器上部署多个应用时踩过这个坑以为每个 Connector 的 maxThreads 是隔离的结果共用线程池后一个应用的慢请求把另一个拖死。所以引入 executor 前先想清楚所有应用是不是同一个接口团队在维护否则遇到线上互相拖累时很难分锅。还有两个参数容易被忽略connectionUploadTimeout 和 disableUploadTimeout。默认情况下如果请求体上传时间较长Tomcat 会在 connectionTimeout 后断开。虽然规范里有上传统计的逻辑但显式设置这两个参数能避免慢网络下的误断开。这两个参数在做文件上传业务的系统里值得留一行注释不然现场上传大文件时断连运维第一反应是去调 Nginx 超时调完发现没用才回头查 Tomcat浪费大半天。5.3 用 systemd 托管 Tomcat开机自启与异常拉起用 startup.sh 启动有个痛点进程靠 nohup 在后台运行机器一重启它不会自动恢复进程异常退出后也没有看护。生产环境建议用系统自带的 systemd 来托管。先创建服务文件cat /etc/systemd/system/tomcat.service EOF [Unit] DescriptionTomcat 8.5.35 Service Afternetwork.target [Service] Typeforking Usertomcat Grouptomcat EnvironmentCATALINA_HOME/opt/tomcat EnvironmentCATALINA_BASE/opt/tomcat ExecStart/opt/tomcat/bin/startup.sh ExecStop/opt/tomcat/bin/shutdown.sh Restarton-failure RestartSec10 [Install] WantedBymulti-user.target EOF systemctl daemon-reload systemctl enable tomcat systemctl start tomcat配置项解释Typeforking 表示启动脚本会 fork 出子进程后父进程退出这正是 startup.sh 的行为。User 和 Group 指定运行身份和第 4 章的权限坑呼应。Restarton-failure 表示只有异常退出才重启正常 stop 不触发。RestartSec 是重启前的等待秒数给系统一点缓冲时间。enable 是设置开机自启start 是立即启动。用 systemd 托管后有一个明显变化如果不在 service 里单独指定标准输出Tomcat 的系统日志会被 journald 接管用 journalctl -u tomcat 查看而不是 catalina.out。停止服务时 systemd 会给 shutdown.sh 一个超时窗口如果应用关闭太慢会收到 SIGKILL。有事务型长任务的系统要在应用侧处理优雅退出不能指望脚本一刀切。第一次从手动启动切到 systemd建议先在测试环境完整走一遍 start 和 stop确认没有因为 shutdown 超时把进程杀掉再上生产。6. 老版本也有新用法多实例、日志轮转和升级判断6.1 单机多实例用 CATALINA_BASE 隔离实例如果一台机器要跑多个互相隔离的应用不要整个复制 Tomcat 目录而是新建一个实例目录作为 CATALINA_BASE复制 conf、logs、temp、webapps、work 这五类目录bin 和 lib 继续共享安装目录。新实例启动时用 -config 参数指定自己的 server.xml同时保证 HTTP 端口、shutdown 端口、日志路径各自独立。这样一套 tar 包就能跑多个实例最省资源。6.2 日志轮转用 logrotate 按天切分生产环境给 catalina.out 加一条 logrotate 规则按天和大小切分保留 30 天/opt/tomcat/logs/catalina.out { daily rotate 30 size 100M copytruncate compress missingok }copytruncate 参数是关键。Tomcat 的 JVM 进程一直占着这个文件描述符如果 logrotate 用默认的 rename 方式重命名Tomcat 会继续往旧文件里写新文件永远是空的。copytruncate 先复制当前内容到归档文件再清空原文件不用重启 Tomcat也就不会中断服务。6.3 升级判断别把旧版本当永久归宿8.5.35 在 Java 8 场景下很稳但后续版本一直在修安全通告。我会定期查看该小版本之后的安全修复列表只要与自己的业务组件相关就计划升级。升级路径建议走 8.5.x 最新版而不是跳到 10.x 大版本因为前者兼容 javax 命名空间war 包和 server.xml 基本不用动后者要把 javax 换成 jakarta迁移成本高出一截。我在生产线上吃过一次闷亏为了图稳定一直钉在 8.5.35后来安全扫描直接暴露旧版问题被迫在业务低峰期紧急升级加了一个通宵的班。那之后我给自己定的习惯是每个季度看一次这个版本的维护状态不再留恋旧版本。把升级当成常规维护而不是事故演习希望这次整理的安装、排查和调优流程能帮把你 Tomcat 这条线管得更省心一些。本文还有配套的精品资源点击获取