Linux上装Tomcat这件事网上教程一抓一大把步骤看起来也简单下载压缩包、解压、启动浏览器打开8080就完事了。可真到了生产环境你大概率会遇到各种“教程没说”的问题。我自己这些年处理过不少类似的部署需求从JDK版本不匹配导致的启动报错到服务看起来起来了但应用刷新超时再到服务器重启后Tomcat根本没跟着起来每一个坑都不大但排查起来都挺耗时间。这篇内容就把我从零开始装Tomcat的完整思路写出来重点不是“敲几条命令”而是每一步为什么这么做、哪些地方容易翻车、怎么做才能少给后面留隐患。适合刚接触Linux部署的开发者也适合帮别人维护过几次服务器、但仍然习惯照着老文章操作的同学。1. 部署前必须处理的两件事系统环境确认与JDK安装1.1 先确认系统架构和发行版再动手很多教程默认你的服务器是CentOS 7、x86_64架构用的还是yum源。但这两年我接触到的新环境里Ubuntu、Debian、国产化系统越来越多CPU架构也不再是x86_64一家独大ARM架构的服务器很常见。如果你按老文章里的Oracle JDK下载链接直接装很可能发现“无法执行二进制文件”因为下载的是x86_64包跑在aarch64系统上自然起不来。所以拿到一台新服务器第一步不是急着装JDK而是先看系统底细# 查看CPU架构 uname -m # 查看操作系统发行版 cat /etc/os-release # 查看已安装的Java版本如果有 java -versionuname -m的输出如果是x86_64说明是64位x86架构如果是aarch64说明是ARM架构后面下载JDK和Tomcat时都要选对应的架构版本。有些发行版虽然自带OpenJDK但版本不一定满足Tomcat要求这就是为什么我习惯先确认系统再决定用包管理器装还是手动解压JDK。1.2 JDK版本不是越新越好而是和Tomcat匹配Tomcat本身是Java程序所以安装Tomcat之前必须有可用的JDK。注意是JDK不是JRETomcat运行JSP页面时要动态编译只给JRE会在访问JSP时报错。JDK和Tomcat的版本对应关系建议直接记下面这张表Tomcat版本最低Java版本说明Tomcat 8.5.xJava 7老项目常用已不建议新装Tomcat 9.0.xJava 8兼容性好经典稳定Tomcat 10.0.xJava 11包名从javax改为jakartaTomcat 10.1.xJava 11长期稳定版本新项目推荐Tomcat 11.0.xJava 17较新适合新特性和容器化场景这里要说一个容易踩坑的点Tomcat 10和Tomcat 9虽然看起来只是大版本号不同但内部包名从javax.servlet迁到了jakarta.servlet。如果你有一个老应用在Tomcat 9下运行得好好的直接扔到Tomcat 10里多半会报ClassNotFoundException或NoClassDefFoundError因为应用里引入的旧命名空间找不到。所以新装环境时我一般先和项目方确认应用是javax的还是jakarta的再决定用哪个Tomcat大版本。1.3 环境变量配置的推荐做法JDK安装方式无外乎两种通过系统包管理器装或者手动解压二进制包。对于大多数部署场景我建议直接用系统自带的OpenJDK包省去手动维护路径的麻烦。Debian/Ubuntu系sudo apt update sudo apt install -y openjdk-11-jdkRHEL/CentOS系sudo yum install -y java-11-openjdk-devel装完以后需要找到JAVA_HOME。这步很多人直接写固定路径比如/usr/lib/jvm/java-11-openjdk-amd64但不同系统、不同版本路径不一样。最稳妥的方式是用命令找# 查看java执行文件的真实路径反推JAVA_HOME readlink -f $(which java)比如输出/usr/lib/jvm/java-11-openjdk-amd64/bin/java那么JAVA_HOME就是去掉末尾/bin/java后的路径。环境变量不建议一股脑写进/etc/profile那样会污染系统全局配置升级或换版本时还容易误改。我习惯在/etc/profile.d/下新建一个独立文件sudo tee /etc/profile.d/tomcat-env.sh EOF export JAVA_HOME/usr/lib/jvm/java-11-openjdk-amd64 export CATALINA_HOME/opt/tomcat export CATALINA_BASE/opt/tomcat export PATH$PATH:$CATALINA_HOME/bin EOF source /etc/profile.d/tomcat-env.sh把JAVA_HOME和CATALINA_HOME集中放一起后续Tomcat升级只要改这一处重新登录即可生效非常省事。2. 下载Tomcat的正确姿势版本选择、目录规划与包校验2.1 选稳定版还是新特性版Tomcat官网提供很多版本有Alpha、Beta、Stable。生产环境我从来不用测试版和刚发布的大版本而是选当前主线的稳定版。比如现在新项目喜欢用Tomcat 10.1.x因为兼容Jakarta标准且修复了较多安全漏洞。如果你维护的是老应用老老实实用Tomcat 9.0.x不要为了“新”而强行升大版本。下载方式很直接浏览器打开Tomcat官网的二进制下载目录找到apache-tomcat-10.1.x.tar.gz复制下载链接。服务器上我用wget或curl下载到/tmpcd /tmp curl -L -O https://example.com/path/to/apache-tomcat-10.1.x.tar.gz上面的example.com是示意实际操作替换成官网复制的下载地址即可。不要下载zip包Linux环境用tar.gz最省事解压后会自动带上执行权限。2.2 目录结构与专用运行用户Tomcat解压后建议放到/opt/tomcat而不是/root或/home/user。原因很简单/opt是Unix系统专门放第三方软件的目录权限清晰路径固定。以后写systemd服务、做备份、升级都方便。Tomcat的标准目录结构里最需要记住的是这几个bin启动、停止脚本比如startup.sh、shutdown.sh、catalina.shconf核心配置server.xml、web.xml、context.xmllogs运行日志catalina.out是主输出文件webapps默认部署目录war包扔这里就自动发布workJSP编译后的class缓存目录temp运行时的临时文件还有一件很重要的事不要用root用户直接跑Tomcat。Tomcat如果被攻破root权限意味着攻击者可以直接控制整台机器。我一般创建一个无登录权限的系统用户来跑Tomcatsudo useradd -r -s /bin/false tomcat sudo mkdir -p /opt/tomcat # 解压后把文件移动到/opt/tomcat再调整属主 sudo tar -xzf /tmp/apache-tomcat-10.1.x.tar.gz -C /tmp sudo mv /tmp/apache-tomcat-10.1.x/* /opt/tomcat/ sudo chown -R tomcat:tomcat /opt/tomcat-s /bin/false表示该用户不能登录shell只用来运行服务符合最小权限原则。2.3 压缩包校验与解压落地下载Tomcat后直接解压虽然大多数时候没问题但网络传输断断续续可能导致包损坏解压时不会报错运行起来却各种莫名奇妙。所以下载完我习惯先做一次校验# 下载官网配套的sha512文件 curl -L -O https://example.com/path/to/apache-tomcat-10.1.x.tar.gz.sha512 sha512sum -c apache-tomcat-10.1.x.tar.gz.sha512如果输出OK说明包完整再解压。如果没有sha512文件至少看文件大小和官网标记是否一致不要嫌麻烦这一步有时候能省下半小时的排错时间。解压本身很简单但要注意/opt/tomcat如果是空目录mv时要把隐藏文件也移过去。我一般先解压到/tmp再进入解压后的目录执行移动避免把外层目录再套一层。3. 首次启动与状态确认别被“Tomcat started”骗了3.1 启动脚本的两种方式startup.sh与catalina.sh runTomcat启动脚本有两个入口startup.sh和catalina.sh run。刚上手的人通常运行startup.sh因为它会在后台启动Tomcat终端可以继续操作。/opt/tomcat/bin/startup.sh但这里有个隐蔽问题startup.sh启动的成功输出是Tomcat started.它只代表“启动脚本执行了”不代表JVM真的起来了更不代表8080端口已经可以接受连接。如果JDK路径配错、端口被占用、或配置文件有语法错误stdout上照样可能打出“Tomcat started.”然后进程立刻退出。所以我调试阶段更推荐用前台方式运行/opt/tomcat/bin/catalina.sh run前台运行的好处是日志直接刷在当前终端任何启动异常会清清楚楚打出来。等你确认一切正常再切换回系统服务管理后面会讲systemd方式。3.2 确认启动成功的几个层次Tomcat启动后我从三个层次确认它真的“活着”第一层看进程是否存在jps -l # 或者 ps -ef | grep tomcat如果能看到类似org.apache.catalina.startup.Bootstrap的Java进程说明JVM起来了。第二层看端口监听ss -lntp | grep 80808080是Tomcat默认HTTP端口出现LISTEN说明连接器已经绑定成功。如果端口被其他进程占用会在这里看到别的PID那就需要处理端口冲突。第三层发起真实HTTP请求curl -I http://127.0.0.1:8080返回HTTP/1.1 200之类的结果才说明能对外提供服务。这一步能直接暴露“端口通了但应用其实没响应”的情况。3.3 停掉服务也有讲究停止Tomcat时我推荐用官方关闭脚本/opt/tomcat/bin/shutdown.sh它会向Tomcat发送关闭指令让Tomcat优雅退出正在处理的请求有机会完成。少用kill -9直接杀进程强行杀掉会导致未完成的请求被丢弃还可能留下临时锁文件。shutdown.sh本身依赖Tomcat的关闭端口默认8005。如果server.xml里把这个端口改了或禁用了shutdown.sh会失效。另外一个常见问题是进程卡住长时间停不掉这时候可以先看日志确认是不是有请求长时间阻塞再考虑手动终止。4. 把应用真正放进Tomcatwar包部署与路径映射4.1 war包投放webapps是最直观的部署方式Tomcat最经典的部署方式就是“把war包扔进webapps”。假设你的应用包叫report.war丢到/opt/tomcat/webapps/后Tomcat会自动解压成report目录访问路径就是http://服务器IP:8080/report这种方式的优点是无脑、简单适合快速上线。但要注意文件名决定访问路径。如果你把report.war改名为demo.war访问路径就变成/demo应用内部如果写死了上下文路径反而会出问题。所以war包名称在部署前就要和项目方约定清楚。4.2 不想把应用放进webapps用Context映射有些场景下应用文件不想放在/opt/tomcat/webapps下比如独立数据盘/data、或需要快速切换不同版本。这时候可以用Context映射。推荐做法是在Tomcat的host配置目录下新建一个独立的xml文件sudo mkdir -p /opt/tomcat/conf/Catalina/localhost sudo vim /opt/tomcat/conf/Catalina/localhost/report.xml文件内容Context path/report docBase/data/report reloadablefalse /保存后Tomcat会自动加载这个配置访问http://IP:8080/report时实际资源从/data/report里取。好处是应用升级时不用动Tomcat部署目录只需要替换/data/report下的文件。注意conf/Catalina/localhost中的文件名就是访问路径。想通过/abc访问文件名就写成abc.xml和Context path属性保持一致。这里很容易搞混我踩过不止一次。4.3 根路径访问与前端静态站点的处理如果希望应用直接通过http://IP:8080/根路径访问而不是带上下文名最省事的办法是把war包改名为ROOT.war再扔进webappscp report.war /opt/tomcat/webapps/ROOT.war这样Tomcat会自动把应用部署在根路径下。如果项目是纯前端静态站点比如一个编译好的dist目录也一样处理Context path docBase/data/frontend reloadablefalse /不过这里说句实话如果只是静态站点我更建议前面用NginxTomcat专注Java动态应用。静态资源和动态接口分开部署性能更好运维也更清爽。Tomcat强撑静态访问不是不能只是没必要。4.4 权限问题部署目录所有权很多新手把war包传到/opt/tomcat/webapps后发现Tomcat能访问但文件解压失败或者应用上传文件时提示没有权限。这通常是因为webapps目录属主不是tomcat这个运行用户。我之前遇到过一个案例运维把war包用root账号传到webappsTomcat也顺利解压了但应用里有个“上传图片”的接口需要往应用的upload目录写文件结果一直报Permission denied。原因就是解压出来的文件属主是root而Tomcat进程属于tomcat用户对root拥有的目录没有写权限。解决办法是把部署目录属主统一交给tomcat用户sudo chown -R tomcat:tomcat /opt/tomcat/webapps sudo chown -R tomcat:tomcat /data/report如果是从普通用户目录上传文件还可以考虑设置umask但最直接的就是调属主。5. 用systemd管理Tomcat开机自启与进程守护5.1 为什么建议摆脱裸脚本启动很多老教程让你把startup.sh写进/etc/rc.local实现开机自启这在新式Linux系统里并不可靠。systemd已经是主流服务管理方式把它和Tomcat绑定不只是为了开机启动更关键的是进程意外退出时systemd可以自动拉起服务崩溃能通过systemctl status看到退出状态日志可以统一交给journald结合journalctl查看启停操作变成systemctl start/stop/restart不用记一堆脚本路径我的建议是从部署第一天就把systemd服务建好不要先用裸脚本跑通再补。5.2 编写一个能用的tomcat.service在/etc/systemd/system/tomcat.service里写如下内容[Unit] DescriptionTomcat Web Application Server Afternetwork.target [Service] Typesimple Usertomcat Grouptomcat EnvironmentJAVA_HOME/usr/lib/jvm/java-11-openjdk-amd64 EnvironmentCATALINA_HOME/opt/tomcat EnvironmentCATALINA_BASE/opt/tomcat EnvironmentCATALINA_PID/opt/tomcat/tomcat.pid ExecStart/opt/tomcat/bin/catalina.sh run ExecStop/opt/tomcat/bin/shutdown.sh Restarton-failure RestartSec5 TimeoutStopSec60 [Install] WantedBymulti-user.target这里最关键的是ExecStart用了catalina.sh run而不是startup.sh。原因是startup.sh启动后会把JVM进程放到后台systemd的Typesimple无法正确追踪主进程服务停止时容易出现“进程还挂着”的情况。catalina.sh run让Tomcat在前台运行systemd管理起来最干净。Usertomcat和Grouptomcat确保进程以低权限用户运行。Restarton-failure表示非正常退出时自动重启这也是我给Tomcat加的第一道保险。5.3 重载、启用开机自启并验证配置写好后按顺序执行sudo systemctl daemon-reload sudo systemctl enable tomcat sudo systemctl start tomcat查看状态systemctl status tomcat看到Active: active (running)才算真正启动成功。接下来测试重启时的自启行为sudo reboot # 重启后用systemctl status tomcat确认状态这一步很多人会忘。手动systemctl start只代表当前启动enable是注册开机自启两个动作缺一不可。5.4 升级Tomcat时的服务操作顺序以后要升级Tomcat版本不能直接把新包覆盖/opt/tomcat。正确顺序是先停服务备份conf和webapps再替换主体文件最后恢复配置并起服务。用systemd管理后整个过程就是sudo systemctl stop tomcat sudo cp -r /opt/tomcat/conf /opt/tomcat/conf_backup sudo cp -r /opt/tomcat/webapps /opt/tomcat/webapps_backup # 替换主体文件 sudo systemctl start tomcat我最开始图省事直接把新版本tar.gz解压覆盖旧目录结果server.xml被重置成默认配置端口和应用全部失效。所以升级前备份配置目录是底线。6. 部署后必做的优化与排查内存、日志、端口与权限6.1 把JVM参数写在setenv.sh里很多教程会让你直接改catalina.sh里的JAVA_OPTS这是危险操作。下次升级Tomcat时catalina.sh会被新文件覆盖你的参数全丢。Tomcat原生支持bin/setenv.sh这个文件每启动时自动读取专门用来放自定义环境变量。我一般这样写sudo vim /opt/tomcat/bin/setenv.shexport JAVA_OPTS-Xms1024m -Xmx1024m -XX:MetaspaceSize256m -XX:MaxMetaspaceSize512m -XX:UseG1GC -Dfile.encodingUTF-8-Xms和-Xmx分别代表堆内存初始值和最大值。生产环境我建议两者设置相同避免JVM运行时频繁扩容、缩容导致性能抖动。MetaspaceSize是元空间大小默认值有时候太小部署复杂应用会触发频繁Full GC手动设置一个合理值能让应用更稳。配置文件保存后需要重启Tomcat才生效。可以先systemctl stop再start确保环境变量被重新加载。6.2 连接器参数线程数、超时与队列Tomcat默认的连接器配置可以跑通但撑不住实际并发。server.xml里HTTP连接器大概长这样Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 maxThreads300 acceptCount100 minSpareThreads50 /几个参数的含义要理解maxThreads最大处理线程数决定了Tomcat能同时处理多少请求不是越大越好受CPU和内存限制acceptCount请求队列长度当线程都忙时新请求排队等待队列满后才会拒绝连接connectionTimeout连接建立后等待读取请求的超时时间单位毫秒minSpareThreads保持空闲的最小线程数减少突发流量时的线程创建开销如果是小规模内部系统maxThreads200通常够用。如果对外服务、并发较大需要结合压测结果去调不要拍脑袋设成万级。顺带提一句字符编码。Java应用中文乱码很多时候和Tomcat连接器的URI编码有关。可以在Connector上增加URIEncodingUTF-8同时在JAVA_OPTS里加上-Dfile.encodingUTF-8。两个地方一起改能解决大部分中文路径和表单提交乱码问题。6.3 常见问题排查思路端口占用、启动失败与OutOfMemory部署过程中最常遇到的几个报错我按出现频率列一下端口占用启动报Address already in use:8080用ss -lntp | grep 8080看是谁占用。多数情况是另一个Tomcat实例或别的Web服务占用了端口。找到PID后确认该进程是否还需要不需要就杀掉需要就改Tomcat端口。环境变量缺失启动时提示Neither the JAVA_HOME nor the JRE_HOME environment variable is defined。这说明Tomcat没找到JDK。如果是systemd方式启动确认tomcat.service里的JAVA_HOME是否写对如果是手动脚本启动确认/etc/profile.d/tomcat-env.sh有没有被source。应用404war包解压了但访问应用路径404。先看日志tail -f /opt/tomcat/logs/localhost.$(date %Y-%m-%d).log里面一般会打异常堆栈。最常见的可能是war包结构不对或者应用的web.xml配置有问题。这种问题没法靠调Tomcat参数解决要看应用本身。OutOfMemoryError日志里出现java.lang.OutOfMemoryError: Java heap space基本就是堆内存不够。先确认setenv.sh里的-Xmx是否合理再排查应用是否有内存泄漏。临时扩容可以用jmap看堆内存占用情况但更彻底的解决办法还是得回到应用侧。6.4 默认应用清理与最小权限原则Tomcat装好后webapps目录下默认带着ROOT、docs、examples、manager、host-manager这几个应用。这些默认应用除了占用端口还是潜在的安全暴露面。生产环境建议直接清掉sudo rm -rf /opt/tomcat/webapps/docs \ /opt/tomcat/webapps/examples \ /opt/tomcat/webapps/manager \ /opt/tomcat/webapps/host-managerROOT要不要删取决于你是否需要根路径内容。一般我会把默认ROOT也清掉避免别人访问到Tomcat默认欢迎页。另一个容易被忽略的是Tomcat关闭端口。默认server.xml里是这样的Server port8005 shutdownSHUTDOWN这个端口和关闭指令如果暴露在外网远程攻击者可能通过发送SHUTDOWN指令把Tomcat关闭。建议至少改成Server port8005 address127.0.0.1 shutdownonly-tomcat-shutdown把监听地址限制为127.0.0.1同时把关闭字符串换成一个不容易猜到的值。改完以后记得重启Tomcat再测试一下shutdown.sh是否还能正常停止服务。如果shutdown.sh失效说明新指令和脚本默认值对不上需要同步调整环境变量或脚本参数。最后再说一个我自己的体会Tomcat部署这件事最怕的不是不会敲命令而是敲完命令不验证。下载后不校验、启动后不看日志、改了配置不重启、部署完不访问真实接口这些问题几乎是新手翻车的同一条路径。把每一步的验证动作养成习惯后面维护起来会轻松很多。