
如果你刚接触Linux服务器搜“Tomcat安装”的教程大概率会得到一套标准动作下载tar.gz、解压、改server.xml端口、执行startup.sh然后把8080端口一放通浏览器能打开那只小猫就算成功。我第一次装的时候也是这么干的结果上线第二天就被运维同事拉去开会——日志把磁盘写满了、Manager后台裸奔在公网、进程被kill -9后留下半截PID文件最尴尬的是连自己Tomcat用的是哪个JDK都说不清楚。后来在好几个项目里反复部署、扩容、排障才慢慢把“装Tomcat”这件事从“能跑”进化到“跑得明白”。今天这篇文章不打算再重复一份tar.gz解压指南而是把Linux上安装部署Tomcat时那些容易被略过、但一旦出问题就很要命的细节摊开讲从版本选型、目录规范、启动原理到systemd托管、日志切割、多实例共存。适合两类人看一是刚接触Linux服务部署的开发者二是在生产环境里想把Tomcat装得更规范、少返工的运维同学。1. 版本选型先过一遍Tomcat配什么JDK第一步就拦人很多教程把第一步直接写成“下载Tomcat 9”但没人解释为什么是9而不是8.5或者10。这一步如果选错后续所有配置都是在错误的地基上盖楼。1.1 Tomcat版本和Java版本的对应关系Tomcat本质上是一个用Java写的Servlet容器它对Java版本有硬性要求装之前务必先确认机子上Java的版本。Tomcat版本最低Java要求主流推荐Java版本注意点Tomcat 8.5Java 7Java 8老项目常用但已停止维护Tomcat 9.0Java 8Java 8 / 11目前最稳的长期使用版本Tomcat 10.0Java 8Java 11命名空间从javax改为jakartaTomcat 10.1Java 11Java 11 / 17需要较新JDK老应用迁移成本高Tomcat 11Java 17Java 17最新主线适合纯新项目这里最容易被坑的地方是Tomcat 10。Tomcat 10开始把Java EE规范迁移到了Jakarta EE原来代码里写的javax.servlet.*全部变成了jakarta.servlet.*。如果你手里是一个老项目代码里全是javax.servlet部署到Tomcat 10上大概率直接抛ClassNotFoundException。别问我是怎么知道的——我有一个模拟项目X就是因为看上了新版本结果一启动一连串包找不到最后老老实实退回9.0.98。1.2 JDK安装的取舍Linux上装JDK的方式主要有三种发行版软件源安装、手动解压官方tar.gz、用容器镜像。我个人的建议是生产环境优先考虑发行版软件源自带的OpenJDK例如在Debian系发行版上可以直接安装openjdk-11-jdk。好处是能跟着系统更新补丁权限和路径也规整不用手动维护PATH。如果必须用特定小版本的JDK那就手动解压到/usr/lib/jvm/下然后用update-alternatives管理版本切换。有一点提醒Tomcat本身不关系你用的是OpenJDK还是Oracle JDK只要JAVA_HOME指对就行。别在上面纠结太多。装完之后验证一下java -version echo $JAVA_HOME很多新手栽在第二步——java -version有输出但JAVA_HOME是空的。这是因为系统默认java命令可能来自/usr/bin/java的软链接环境变量根本没设置。Tomcat启动脚本偏偏只看JAVA_HOME不看PATH所以这一步没做好后面startup.sh大概率跑不起来。2. 下载解压之外部署路径和目录结构才是长期项目的地基Tomcat官网的下载页里有Binary Distributions、Source DistributionsBinary下面又分Core、Full distribution、Deployer等。日常部署只需要Core分类下的tar.gz包后缀带src的、带windows-x64字样的都不要碰。2.1 目录规范比解压动作更重要很多教程随便让你解压到/root/apache-tomcat-9.0.98然后直接用root跑这在开发机没问题上一线就全是事。我建议按这个布局来/opt/ ├── tomcat9 # 固定路径不携带版本号 │ ├── bin/ │ ├── conf/ │ ├── lib/ │ ├── logs/ │ ├── temp/ │ ├── webapps/ │ └── work/ └── tomcat9-9.0.98 # 实际解压目录实际解压时先解到带版本号的目录再做软链接ln -s tomcat9-9.0.98 /opt/tomcat9。这样以后升级只需要解压新版改一下软链接路径在脚本和配置文件里完全不用动。创建普通用户运行Tomcat也是规范化的关键一步useradd -r -s /sbin/nologin tomcat chown -R tomcat:tomcat /opt/tomcat9Tomcat不在root下运行主要是避免WEB应用有漏洞时直接拿到root权限。-s /sbin/nologin表示这个用户不能登录shell只用来跑服务。2.2 七个目录各管一摊得拎清楚目录作用实操建议bin启动/关闭脚本主要用startup.sh、shutdown.sh、catalina.shconf配置文件server.xml、context.xml、web.xml、tomcat-users.xmllibTomcat的jar包不要乱放第三方jar容易引发类冲突logs日志输出重点是catalina.out和localhost日期日志temp临时文件目录定期清理即可webapps应用发布目录生产一般不用热部署只丢war包workJSP编译后的class无需手动操作删了会自动重建有个冷知识work目录去缓存的效果立竿见影。有时候你更新了一个JSP页面发现改动不生效第一反应是重启但重启慢还影响在线的请求。试一下把work目录下的编译产物清掉只留着JSP让Tomcat重新编译速度会快很多这在老旧的JSP应用上很实用。3. JAVA_HOME/catalina.sh/端口映射启动背后那几个绕不开的变量安装完Tomcat后第一个./startup.sh如果直接成功你可能会觉得这节没什么好看的。但如果报错多半就集中在环境变量、启动方式、端口监听这三件事上。3.1 setenv.sh环境变量不要到处写网上有教程让你把JAVA_HOME写进/etc/profile这能用但特别脏。更规范的做法是在Tomcat的bin目录下新建一个setenv.sh文件因为catalina.sh脚本启动时会自动检测并加载它。这个文件对每台机器的个性化配置做了统一收纳以后换机器只需要拷一个文件就够了。export JAVA_HOME/usr/lib/jvm/java-11-openjdk-amd64 export CATALINA_HOME/opt/tomcat9 export CATALINA_BASE/opt/tomcat9 # 内存参数建议也放这里后面第6章会细讲 CATALINA_OPTS-Xms512m -Xmx1024m -XX:MetaspaceSize256mCATALINA_HOME是Tomcat安装位置CATALINA_BASE是实例位置。单实例时两者一致多实例时BASE指向具体实例目录这一点在第7章再展开。3.2 startup.sh的本质是catalina.sh的壳执行startup.sh时它会调catalina.sh start。真正的Java进程是在后台拉起的标准输出会重定向到logs/catalina.out。所以启动完不要干等要养成看日志的习惯/opt/tomcat9/bin/startup.sh tail -f /opt/tomcat9/logs/catalina.out如果启动失败catalina.out里会有明确的异常线索。想在前台直接观察日志和进程状态可以用/opt/tomcat9/bin/catalina.sh run这在调试阶段特别好用。关停时一定要用/opt/tomcat9/bin/shutdown.sh不要养成kill -9的习惯这会导致一些临时文件和PID文件残留下次启动可能报端口被占用。3.3 端口到底有几个都听谁的Tomcat的默认端口配置在conf/server.xml里主要就这几个端口用途默认状态8080HTTP请求监听默认开放8005SHUTDOWN关闭命令端口本地监听9之后屏蔽部分风险8009AJP协议端口9默认有10.1后默认关闭8443HTTPS监听默认未配置很多NAT场景下你从外网访问8080打不开第一反应是防火墙第二反应是安全组但其实还有一个地方经常被忽略——server.xml里Connector的address属性。如果你的机器有多块网卡或者要对外提供服务建议明确定义监听地址Connector port8080 protocolHTTP/1.1 address0.0.0.0 connectionTimeout20000 redirectPort8443 /address0.0.0.0表示监听所有网卡如果是127.0.0.1就只能本机访问。这个字段写错的时候最迷惑因为在本机curl怎么都通出去就不通。4. 管理端不是默认就开放Tomcat 9以后的安全边界Tomcat自带的webapps目录里默认有ROOT、docs、examples、manager、host-manager这五个应用。生产环境部署时docs和examples是纯累赘建议直接删除或改名省得被扫描器当靶子。4.1 Manager应用的默认限制Tomcat 9开始manager和host-manager这两个管理应用默认做了严格的地址限制只有本机127.0.0.1能访问。如果你在浏览器里访问/manager/html大概率看到一个403页面上面写着“Your client is not allowed to access internal request”。这个限制藏在conf/Catalina/localhost/manager.xmlContext privilegedtrue antiResourceLockingfalse docBase${catalina.home}/webapps/manager Valve classNameorg.apache.catalina.valves.RemoteAddrValve allow127\.\d\.\d\.\d|::1|0:0:0:0:0:0:0:1 / /Context如果想要远程管理需要把allow改成你的IP段例如allow192.168\.1\.\d。但我要多说一句在公网环境里把Manager开到公网IP和把钥匙挂在门口没有区别。即使你设置了tomcat-users.xml里的用户密码也不建议直接暴露。实在要用至少做两件事一是只放通特定来源IP二是把默认的8080端口改掉。4.2 管理用户千万别用默认账号很多教程顺手教你在tomcat-users.xml里加manager-gui角色和用户初衷是方便但风险特别大。Tomcat的默认用户列表是空的一旦你加了一个弱口令用户就相当于给攻击者递了一把开Manager后台的钥匙。而且Manager后台不止能看JSP状态还能上传部署WAR包这等于直接给了WebShell。我现在的习惯是tomcat-users.xml里只保留需要的最小权限密码至少20位随机。还要把conf/tomcat-users.xml的权限收一下chmod 600 /opt/tomcat9/conf/tomcat-users.xml这条命令看着平淡但能挡掉同机其他低权限用户读你的密码文件。5. 用systemd接管Tomcat进程服务化配置和两个容易翻车的细节手动startup.sh适合开发和临时测试生产环境里还是建议用systemd托管。好处是开机自启、崩溃自动拉起、日志走journald和别的服务一起用systemctl status tomcat统一管理。5.1 服务文件这样写在/etc/systemd/system/tomcat.service里创建文件[Unit] DescriptionApache Tomcat 9 Afternetwork.target [Service] Typeforking Usertomcat Grouptomcat EnvironmentJAVA_HOME/usr/lib/jvm/java-11-openjdk-amd64 EnvironmentCATALINA_HOME/opt/tomcat9 EnvironmentCATALINA_BASE/opt/tomcat9 ExecStart/opt/tomcat9/bin/startup.sh ExecStop/opt/tomcat9/bin/shutdown.sh TimeoutSec60 Restarton-failure [Install] WantedBymulti-user.target配置好后执行systemctl daemon-reload systemctl enable --now tomcatTypeforking表示启动命令会fork出子进程后返回这和startup.sh的行为匹配。如果用Typesimplesystemd会认为主进程没有保持在前台状态判断容易出错。5.2 坑一systemd启动成功但Tomcat没起来这是典型的“服务状态看着没问题实际进程死活找不到”的诡异场景。原因是startup.sh在后台启动了Java进程但脚本本身很快就退出了systemd可能以为启动失败。排查时先看journalctl -u tomcat -e再单独手动跑一次startup.sh对比日志基本能定位出是JAVA_HOME没读到还是权限问题。5.3 坑二shutdown.sh执行了Java进程没退干净另一个常见问题是systemctl stop tomcat后ps -ef | grep java还能看到残留进程。原因是systemd默认用KillModecontrol-group停止服务时会向整个控制组发SIGTERM但shutdown.sh的优雅关停走的是8005端口的SHUTDOWN指令两个机制配合不好的话主进程没等子进程退干净就结束了。我的处理办法是把服务文件里的KillMode改成mixed并加上合理的TimeoutStopSecKillModemixed TimeoutStopSec60这样既能保证shutdown.sh有足够时间做优雅关停又不会让残留进程一直挂在那里。遇到过几次“端口明明释放了但8080还是占用”的怪现象基本都是这个原因。6. 日志不切割会磁盘爆炸catalina.out处理与JVM内存参数Tomcat的日志体系里catalina.out是Java进程的标准输出所有System.out打出来的内容、未捕获异常、JVM崩溃信息都会跑到这里。问题是这个文件默认不会自己轮转日志量一大它能把磁盘撑满在线服务直接不可用。6.1 logrotate轮转配置Linux自带的logrotate是好帮手不需要装额外工具。在/etc/logrotate.d/tomcat里创建文件/opt/tomcat9/logs/catalina.out { daily rotate 7 compress missingok copytruncate create 0640 tomcat tomcat }几个参数的含义说一下daily每天切一次rotate 7保留7份旧日志compress对旧日志做gzip压缩copytruncate是先复制文件内容再清空原文件这样Tomcat进程不需要重启。这一点很重要因为直接move掉文件的话Tomcat仍然往旧的inode写数据磁盘空间根本不会释放。6.2 JVM参数内存不是越大越好Tomcat的启动参数主要写在setenv.sh里的CATALINA_OPTS上。生产环境我一般这样起步CATALINA_OPTS-Xms1024m -Xmx1024m -XX:MetaspaceSize256m -XX:MaxMetaspaceSize512m-Xms和-Xmx写同一个值避免了JVM频繁扩容缩容带来的性能抖动这在服务端是很常见的一种做法。比如物理机内存16G留4G给系统和其他进程Tomcat堆给4G其他进程堆给2G剩下的留给文件缓存就比较均衡。GC的选型上Java 8默认的ParallelGC适合吞吐量优先服务延迟敏感的场景可以尝试G1。我的经验是4G以内的堆Parallel和G1差距没有想象的夸张真正影响稳定性的是别把-Xmx设得太大——堆一旦进入swapGC就会变成灾难。线上见过一台8G内存的虚拟机硬是给Tomcat配了6G堆结果高峰期整机swap不断Full GC频繁到每秒一次这种问题加机器比调参数更解决问题。7. 单机跑多个Tomcat实例用CATALINA_BASE做到隔离和共存同一个机器上部署多个Tomcat的需求其实不少跑不同版本的JDK应用、隔离不同团队的项目、或者做灰度部署。直接复制一份完整的Tomcat目录当然能跑但升级维护时要改好几份很累。正确的做法是把CATALINA_HOME和CATALINA_BASE拆开。7.1 什么是CATALINA_BASECATALINA_HOME是Tomcat的安装目录放bin、lib这些公用文件。CATALINA_BASE是实例目录每个实例只需要自己的conf、logs、temp、webapps、work。这样一份Tomcat程序可以被多个实例共享升级程序只需要替换HOME各实例的配置完全不动。手动创建实例的步骤mkdir -p /opt/tomcat-instance/demo1 cp -r /opt/tomcat9/conf /opt/tomcat-instance/demo1/ cp -r /opt/tomcat9/logs /opt/tomcat-instance/demo1/ cp -r /opt/tomcat9/temp /opt/tomcat-instance/demo1/ cp -r /opt/tomcat9/webapps /opt/tomcat-instance/demo1/ cp -r /opt/tomcat9/work /opt/tomcat-instance/demo1/ chown -R tomcat:tomcat /opt/tomcat-instance/demo1启动时指定BASECATALINA_BASE/opt/tomcat-instance/demo1 /opt/tomcat9/bin/catalina.sh start7.2 多实例必须处理的三个点同一个Tomcat程序开多个实例最麻烦的不是启动脚本而是资源配置冲突。一是端口冲突。每个实例的conf/server.xml里8080、8005、8009这三个端口都要改成不同的值。我有一个实例A用了8080、8005、8009实例B用了8081、8006、8010这个对应关系最好写进实例目录下一个简短的README里不然半年后回来看根本记不住哪个端口是谁的。二是日志隔离。日志默认输出到当前BASE的logs目录这点Tomcat做得很老实。唯一要留意的是日志切割和监控要分别指向每个实例的目录。三是环境变量的作用域。多个实例启动时如果直接在shell里export CATALINA_BASE会串到下一个实例。用一行命令来启动比较拽而且不易错sudo -u tomcat CATALINA_BASE/opt/tomcat-instance/demo1 /opt/tomcat9/bin/catalina.sh start多次踩坑之后我自己最深的体会是Tomcat部署这件事八成的问题不是Tomcat本身而是环境变量、权限、端口和日志这些周边细节。把这几个点管明白安装部署就是一条直线。最后分享一个小技巧。排查Tomcat问题的时候我习惯先看setenv.sh里配了什么、再看server.xml改了什么、最后才看catalina.out。很多所谓“环境问题”最后追进去都是配置文件被改得很乱。如果你也不想半年后被自己留下的配置坑到建议从第一天起就把Tomcat的配置文件纳入版本管理任何一个改动都在注释里写清楚原因。等哪天真的线上出问题你会感谢当初那个愿意多写两行注释的自己。