☰
Linux服务器Tomcat部署实战:从环境配置到systemd管理
2026/10/15 17:45:44 网站建设 项目流程

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 -version

uname -m的输出如果是x86_64,说明是64位x86架构;如果是aarch64,说明是ARM架构,后面下载JDK和Tomcat时都要选对应的架构版本。有些发行版虽然自带OpenJDK,但版本不一定满足Tomcat要求,这就是为什么我习惯先确认系统,再决定用包管理器装还是手动解压JDK。

1.2 JDK版本不是越新越好,而是和Tomcat匹配

Tomcat本身是Java程序,所以安装Tomcat之前必须有可用的JDK。注意是JDK不是JRE,Tomcat运行JSP页面时要动态编译,只给JRE会在访问JSP时报错。

JDK和Tomcat的版本对应关系,建议直接记下面这张表:

Tomcat版本最低Java版本说明
Tomcat 8.5.xJava 7+老项目常用,已不建议新装
Tomcat 9.0.xJava 8+兼容性好,经典稳定
Tomcat 10.0.xJava 11+包名从javax改为jakarta
Tomcat 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-jdk

RHEL/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下载到/tmp:

cd /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.sh
  • conf:核心配置,server.xml、web.xml、context.xml
  • logs:运行日志,catalina.out是主输出文件
  • webapps:默认部署目录,war包扔这里就自动发布
  • work:JSP编译后的class缓存目录
  • temp:运行时的临时文件

还有一件很重要的事:不要用root用户直接跑Tomcat。Tomcat如果被攻破,root权限意味着攻击者可以直接控制整台机器。我一般创建一个无登录权限的系统用户来跑Tomcat:

sudo 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 run

Tomcat启动脚本有两个入口: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 8080

8080是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. 把应用真正放进Tomcat:war包部署与路径映射

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" reloadable="false" />

保存后,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再扔进webapps:

cp report.war /opt/tomcat/webapps/ROOT.war

这样Tomcat会自动把应用部署在根路径下。如果项目是纯前端静态站点,比如一个编译好的dist目录,也一样处理:

<Context path="" docBase="/data/frontend" reloadable="false" />

不过这里说句实话:如果只是静态站点,我更建议前面用Nginx,Tomcat专注Java动态应用。静态资源和动态接口分开部署,性能更好,运维也更清爽。Tomcat强撑静态访问不是不能,只是没必要。

4.4 权限问题:部署目录所有权

很多新手把war包传到/opt/tomcat/webapps后,发现Tomcat能访问但文件解压失败,或者应用上传文件时提示没有权限。这通常是因为webapps目录属主不是tomcat这个运行用户。

我之前遇到过一个案例,运维把war包用root账号传到webapps,Tomcat也顺利解压了,但应用里有个“上传图片”的接口,需要往应用的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] Description=Tomcat Web Application Server After=network.target [Service] Type=simple User=tomcat Group=tomcat Environment="JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64" Environment="CATALINA_HOME=/opt/tomcat" Environment="CATALINA_BASE=/opt/tomcat" Environment="CATALINA_PID=/opt/tomcat/tomcat.pid" ExecStart=/opt/tomcat/bin/catalina.sh run ExecStop=/opt/tomcat/bin/shutdown.sh Restart=on-failure RestartSec=5 TimeoutStopSec=60 [Install] WantedBy=multi-user.target

这里最关键的是ExecStart用了catalina.sh run而不是startup.sh。原因是startup.sh启动后会把JVM进程放到后台,systemd的Type=simple无法正确追踪主进程,服务停止时容易出现“进程还挂着”的情况。catalina.sh run让Tomcat在前台运行,systemd管理起来最干净。

User=tomcat和Group=tomcat确保进程以低权限用户运行。Restart=on-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.sh
export JAVA_OPTS="-Xms1024m -Xmx1024m -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m -XX:+UseG1GC -Dfile.encoding=UTF-8"

-Xms和-Xmx分别代表堆内存初始值和最大值。生产环境我建议两者设置相同,避免JVM运行时频繁扩容、缩容导致性能抖动。MetaspaceSize是元空间大小,默认值有时候太小,部署复杂应用会触发频繁Full GC,手动设置一个合理值能让应用更稳。

配置文件保存后需要重启Tomcat才生效。可以先systemctl stop,再start,确保环境变量被重新加载。

6.2 连接器参数:线程数、超时与队列

Tomcat默认的连接器配置可以跑通,但撑不住实际并发。server.xml里HTTP连接器大概长这样:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxThreads="300" acceptCount="100" minSpareThreads="50" />

几个参数的含义要理解:

  • maxThreads:最大处理线程数,决定了Tomcat能同时处理多少请求,不是越大越好,受CPU和内存限制
  • acceptCount:请求队列长度,当线程都忙时,新请求排队等待,队列满后才会拒绝连接
  • connectionTimeout:连接建立后等待读取请求的超时时间,单位毫秒
  • minSpareThreads:保持空闲的最小线程数,减少突发流量时的线程创建开销

如果是小规模内部系统,maxThreads=200通常够用。如果对外服务、并发较大,需要结合压测结果去调,不要拍脑袋设成万级。

顺带提一句字符编码。Java应用中文乱码,很多时候和Tomcat连接器的URI编码有关。可以在Connector上增加:

URIEncoding="UTF-8"

同时在JAVA_OPTS里加上-Dfile.encoding=UTF-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。

应用404:war包解压了但访问应用路径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-manager

ROOT要不要删,取决于你是否需要根路径内容。一般我会把默认ROOT也清掉,避免别人访问到Tomcat默认欢迎页。

另一个容易被忽略的是Tomcat关闭端口。默认server.xml里是这样的:

<Server port="8005" shutdown="SHUTDOWN">

这个端口和关闭指令如果暴露在外网,远程攻击者可能通过发送SHUTDOWN指令把Tomcat关闭。建议至少改成:

<Server port="8005" address="127.0.0.1" shutdown="only-tomcat-shutdown">

把监听地址限制为127.0.0.1,同时把关闭字符串换成一个不容易猜到的值。改完以后记得重启Tomcat,再测试一下shutdown.sh是否还能正常停止服务。如果shutdown.sh失效,说明新指令和脚本默认值对不上,需要同步调整环境变量或脚本参数。

最后再说一个我自己的体会:Tomcat部署这件事,最怕的不是不会敲命令,而是敲完命令不验证。下载后不校验、启动后不看日志、改了配置不重启、部署完不访问真实接口,这些问题几乎是新手翻车的同一条路径。把每一步的验证动作养成习惯,后面维护起来会轻松很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询