☰
Linux环境Tomcat安装配置与部署实战:从JDK到systemd全指南
2026/9/26 7:33:32 网站建设 项目流程

1. 装前必读:Tomcat和JDK的版本匹配是第一道坎

1.1 先弄明白Tomcat在Linux里扮演什么角色

很多人第一次接触Tomcat是在Windows的课堂上,装了之后双击startup.bat,浏览器里看到一个猫,就以为完事了。但换到Linux服务器上,情况完全不一样:没有图形界面,没有一键安装包,甚至没有JAVA_HOME,你连启动脚本都会直接报错。这也是为什么“Linux环境下Tomcat安装与配置”看起来只是click点来,实则每一步都踩版本、权限和环境变量的坑。

先弄清楚Tomcat到底是干嘛用的。Tomcat是一个Servlet容器,也叫Web应用服务器,它负责把你写好的Java Web应用跑起来。你写的Servlet、JSP、Filter,单靠JDK是无法直接跑成Web服务的,必须有一个类似Tomcat这样的容器来接收HTTP请求,加载类,执行逻辑,再返回响应。生产环境里,Tomcat经常和Nginx搭配使用:Nginx在前面接收80端口流量,Tomcat在8080被Nginx反向代理,这就是最常见的Java后端部署架构之一。

准备把Tomcat部署到Linux,得先把目标搞清楚:是给本地开发用,还是给测试服务器用,还是直接面向生产?这三类场景的配置差距比想象中大。本地开发重快捷,测试服务器重可重复,生产环境重安全和服务化托管。我下面所有操作步骤都兼顾三种场景,但会在关键位置指出生产环境建议,你照着做基本不会跑偏。

1.2 JDK选择与JAVA_HOME配置,很多人挂在第一步

Tomcat本身是用Java写的,所以没有JDK一切免谈。这里最容易犯的错误是先装了Tomcat再装JDK,或者JDK版本和Tomcat严重不匹配。官方文档里每个Tomcat主版本都标定了最低JDK版本,这里给你一张对照表,建议直接按表选型。

Tomcat版本最低JDKServlet规范Jakarta命名空间
Tomcat 9.0JDK 8以上Servlet 4.0javax.*
Tomcat 10.0JDK 8以上Servlet 4.0jakarta.*
Tomcat 10.1JDK 11以上Servlet 6.0jakarta.*
Tomcat 11JDK 17以上Servlet 6.1jakarta.*

选择逻辑很简单:如果项目还在用Spring Boot 2.3之前的版本,或者老项目跑在javax.*包名上,就选Tomcat 9;如果你用的是Spring Boot 3、Spring Framework 6这类新生态,直接上Tomcat 10.1或Tomcat 11。别听网上“越新越好”的瞎指挥,Tomcat 10彻底换成了jakarta.*命名空间,老项目拷进去编译不过、启动报ClassNotFoundException的案例我见过不下十次。

JDK安装我推荐用系统包管理器装OpenJDK,这是Linux上最省心的方式。Debian/Ubuntu系:

sudo apt update sudo apt install -y openjdk-11-jdk

CentOS/RHEL系:

sudo yum install -y java-11-openjdk-devel

装完先确认路径,不要靠脑子记。不同发行版、不同版本,JDK路径差异很大,我见过有人硬写/usr/lib/jvm/java-8-openjdk结果目录不存在,后面所有启动都失败。

which java readlink -f $(which java)

Ubuntu上典型输出是/usr/lib/jvm/java-11-openjdk-amd64/bin/java,去掉最后的/bin/java就是你需要的JAVA_HOME。配置环境变量建议写到/etc/profile.d/tomcat.sh,不要直接改/etc/profile,独立文件更便于维护。

sudo tee /etc/profile.d/jdk.sh <<'EOF' export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 export PATH=$PATH:$JAVA_HOME/bin EOF source /etc/profile.d/jdk.sh java -version

看到类似openjdk version "11.0.22"的输出,JDK这部分就算过了。注意我在这里把环境变量独立成一个文件,而不是塞进/etc/profile,因为将来更新JDK版本,你只需要改这一个文件,不会污染系统全局配置。

2. 下载与安装:比想象中更需要注意的几个细节

2.1 从哪里下载、选哪个安装包

Tomcat官方提供三种发布形式:源码包、tar.gz二进制包、以及Windows专用zip包。在Linux上我们只需要tar.gz二进制包,它已经把编译好的类库全部打好了,解压即用。源码包是用来自己编译的,普通部署根本不用碰。还有一位“二进制包”:官方发行版都会在目录里提供一个apache-tomcat-9.0.xx.tar.gz和一个apache-tomcat-9.0.xx-src.tar.gz,认准不带-src的那个。

去Apache官网找到对应版本的二进制发行档案,或者直接用wget下载。注意最好通过官方推荐的站点下载,确保包完整性,下载完一定要做SHA512校验。Apache提供了.sha512文件,在校验时可以拿它做对比。

wget https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.89/bin/apache-tomcat-9.0.89.tar.gz wget https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.89/bin/apache-tomcat-9.0.89.tar.gz.sha512 sha512sum -c apache-tomcat-9.0.89.tar.gz.sha512

如果输出OK,说明下载文件完整,可以继续。这一步看似多余,但生产环境部署包被篡改的事情不是没发生过,养成校验习惯只有好处。另外,版本号尽量用当前稳定版,不要用测试版,除非你明确知道自己需要某个新特性。

2.2 创建独立用户和服务目录,别把tomcat跑在root下

这是我和很多初学者反复强调的点:Tomcat不要用root启动。原因很简单,Web服务直接面对外部流量,一旦应用被攻破,攻击者拿到的是root权限,整个服务器都跟着投降。正确的做法是创建用途单一的系统用户,再把Tomcat目录交给这个用户。

sudo mkdir -p /opt/tomcat sudo useradd -r -s /sbin/nologin -d /opt/tomcat tomcat

-r表示创建系统用户,-s /sbin/nologin表示不允许该用户登录系统。这个用户唯一的工作就是运行Tomcat进程。然后再把下载好的tar.gz解压到/opt/tomcat,并且要把解压结果整理成标准目录结构。

2.3 解压、目录归属与基础验证,跑通第一个启动命令

sudo tar -zxvf apache-tomcat-9.0.89.tar.gz -C /opt/tomcat --strip-components=1 sudo chown -R tomcat:tomcat /opt/tomcat sudo find /opt/tomcat -type d -exec chmod 755 {} \; sudo find /opt/tomcat -type f -exec chmod 644 {} \; sudo chmod +x /opt/tomcat/bin/*.sh

--strip-components=1的意思是解压时把最外层目录名apache-tomcat-9.0.89剥掉,这样内容直接落在/opt/tomcat下。如果你不用这个参数,就会多套一层目录,启动脚本路径就会变成/opt/tomcat/apache-tomcat-9.0.89/bin/startup.sh,后续配置容易乱。

这里我为什么要单独执行bin目录的加执行权限?因为上面已经把目录设为755、文件设为644,sh文件默认不是可执行状态。虽然启动时可以调用sh startup.sh绕过执行权限,但service方式会直接用ExecStart=/opt/tomcat/bin/startup.sh,不加上执行权限你会看到一整晚都排查不出来的Permission denied。

先在命令行跑一次版本验证。切换到tomcat用户,看设计得正不正规:

sudo -u tomcat /opt/tomcat/bin/version.sh

输出里能看到Server version: Apache Tomcat/9.0.89、JVM Version这些信息,就说明JDK环境和Tomcat本身没问题。

3. 配置文件逐个讲:server.xml、context.xml、tomcat-users.xml

3.1 server.xml里的端口、Host和Executor

Tomcat的核心配置都集中在conf/server.xml。新手最常见的操作就是改端口号,但经常只改了一个端口。要知道Tomcat默认监听三个端口:

端口默认值用途
Shutdown8005接收关闭命令的本地端口
HTTP/1.18080对外HTTP访问端口
AJP/1.38009连接Apache/Nginx等前端服务器的二进制协议

如果80端口被Nginx占用,你需要把HTTP端口改成8080以外的空闲端口;如果8005端口冲突,启动时会立刻报java.net.BindException: Address already in use。下面是修改HTTP连接器的基础示例,同时便宜设置了两个重要参数:

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxThreads="300" minSpareThreads="50" URIEncoding="UTF-8" />

URIEncoding="UTF-8"这个参数对中文URL参数特别重要。Linux系统默认locale如果没设置好,不指定这个参数,请求参数里的中文很容易变成乱码。maxThreads控制最大并发处理线程,生产环境建议结合机器内存调整,默认200有时候遇到活动高峰明显不够,但盲目调到1000会造成线程频繁切换,性能反而更差。

生产环境我会再考虑几点:autoDeploy参数在Host元素上,开发环境可以打开,生产环境建议设置为false,避免Hots插件hot热部署意外把半成品WAR包发布出去;unpackWARs同样在开发机上有用,生产环境大多直接部署展开后的目录,可以保持默认true。

3.2 context.xml里的默认容器配置,影响全局的Session和监听器

conf/context.xml控制着所有应用共享的默认Context容器。这个文件里最常见的问题是JVM参数和Session配置长篇大论。新手一般不需要改它,但有几个点需要你认识一下。

<Manager pathname="" />这个配置是用来禁用Session持久化的。默认情况下,Tomcat在关闭时会把Session状态序列化到work目录下的SESSIONS.ser文件里,下次启动再加载。这样做的本意是平滑重启,但实际开发中经常出现“改完代码重启Tomcat,Session还残留着旧状态”的诡异问题。如果你不需要这个特性,可以把pathname设为空字符串:

<Context> <Manager pathname="" /> <WatchedResource>WEB-INF/web.xml</WatchedResource> </Context>

另外,conf/context.xml里也可以统一配置JarScanner,但一般保持默认即可。频繁改这个文件的风险大于收益。

3.3 开启Manager权限的tomcat-users配置,等五分钟才能操作

Tomcat自带的Manager应用在刚安装完时默认是禁止访问的,你必须先在conf/tomcat-users.xml里添加角色和用户。

<tomcat-users> <role rolename="manager-gui"/> <role rolename="manager-script"/> <role rolename="admin-gui"/> <user username="admin" password="YourStrongPass" roles="manager-gui,manager-script,admin-gui"/> </tomcat-users>

manager-gui允许登录网页端管理界面,manager-script允许通过HTTP命令远程部署WAR包。生产环境不要开启manager-gui,只保留manager-script配合自动化发布工具使用就够了。这个文件如果配置错误,Tomcat启动时不会报错,但你访问/manager会看到403页面,排查思路先看这里。

顺带说一句,很多人修改tomcat-users.xml之后重启,发现还是不生效,原因多半是没看conf/Catalina/localhost/manager.xml这个文件。新版Tomcat默认对该应用做了基于IP地址的访问限制,修改conf/server.xml里<Valve>中的allow配置,或者放行指定IP,否则本机访问可以、远程访问就是403。

4. 让Tomcat跟随系统自启动:systemd方案全记录

4.1 为什么不用服务器里写启动命令,直接挂后台的方式

网上很多老文章教你用nohup /opt/tomcat/bin/startup.sh &让Tomcat后台运行。这在开发机上也许凑合,但生产环境会遇到三个问题:一是Tomcat进程和终端脱钩,没人帮你看护,进程意外退出不会自动拉起;二是服务器重启后需要人工登录再启动;三是root退出后孤儿进程的资源回收容易产生僵尸态。正确做法是写一个systemd服务单元,让系统来管理Tomcat的生命周期。

如果你用CentOS 7以下的旧系统,才需要考虑init.d方式;现在主流Linux发行版都用systemd,所以下文统一按systemd写。

4.2 写一个可长期运行的systemd unit,注意Type要选对

在/etc/systemd/system/tomcat.service里写入下面的配置:

[Unit] Description=Apache Tomcat 9 Web Application Server After=network.target syslog.target [Service] Type=forking 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/temp/tomcat.pid ExecStart=/opt/tomcat/bin/startup.sh ExecStop=/opt/tomcat/bin/shutdown.sh Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target

Type=forking很关键。startup.sh本身会以nohup方式把Tomcat进程丢到后台,然后脚本立即退出,如果设置成Type=simple,systemd会认为主进程已经退出,把整个服务标记为失败。指定forking后,systemd会等待CATALINA_PID指向的进程出现,再判定服务启动成功。

另外,Restart=on-failure也不是随手写上去的。生产环境里进程OOM或被外部kill掉,systemd会等10秒自动重启,能把服务可用性拉高一大截。但注意不要把Restart=always和RestartSec=1一起用,否则如果应用启动必失败,每隔几秒,你的日志里全是错误,磁盘会被刷爆。

4.3 启动、停止、状态与开机自启验证

写完之后,重新加载systemd配置,然后设置开机自启:

sudo systemctl daemon-reload sudo systemctl enable tomcat sudo systemctl start tomcat sudo systemctl status tomcat -l

状态输出里必须看到Active: active (running)。接着用curl验证80行端口:

curl -I http://127.0.0.1:8080

返回HTTP/1.1 200就说明服务已经正常起来了。另外,我通常还会检查一下Tomcat日志中是否出现异常,因为这个阶段systemctl status未必展示所有细节:

sudo tail -f /opt/tomcat/logs/catalina.out

启动成功后,主动重启一次服务器,再确认Tomcat是否真的自动起来了。如果没起来,大概率是JAVA_HOME环境变量没在unit里正确声明,或者/opt/tomcat/logs目录的所有者不是tomcat用户。这两个坑占了自启动失败的八成原因。

5. 部署Web应用与排查启动异常,实践环节最长的体现

5.1 WAR包部署的四种常见方式,按场景选就行

要把应用部署到Tomcat,最直观的方式是把WAR包复制到webapps目录。Tomcat的Host容器默认设置了autoDeploy="true",会定时扫描该目录,发现新的WAR包或目录后自动加载。复制进去之前建议先停止Tomcat服务,再拷贝,否则可能出现文件没拷完就开始解压的问题。

sudo systemctl stop tomcat sudo cp /data/myapp.war /opt/tomcat/webapps/ sudo chown tomcat:tomcat /opt/tomcat/webapps/myapp.war sudo systemctl start tomcat

启动后观察logs/catalina.out,看到Deploying web application archive ... has finished in xxx ms就说明部署成功。此时访问http://ip:8080/myapp就能看到应用。

第二种方式是通过Manager的图形界面部署,适合零散的手动操作。前提是前面已经配置好了manager-gui角色,打开http://ip:8080/manager/html,输入用户名密码,浏览上传WAR包即可。

第三种方式是用Manager的脚本API做远程部署,这个适合写进Jenkins或CI脚本。manager-script角色是必需的:

curl -u admin:'YourStrongPass' \ --upload-file /data/myapp.war \ "http://127.0.0.1:8080/manager/text/deploy?path=/myapp&update=true"

脚本API返回OK - Deployed application at context path /myapp就算部署成功。这个方式可以做到全自动化发布,推荐生产环境配合使用。

第四种方式是在conf/Catalina/localhost/下一个myapp.xml,这个XML直接对外定义Context的docBase,好处是应用可以放在webapps之外的其他目录,方便脚本和老区文件独立管理。

5.2 启动报错别瞎猜,先把日志层级理清楚

Tomcat的日志放在logs目录下,但初学者经常分不清看哪个。简单说:catalina.out是控制台输出,包含主进程运行日志,启动报错第一时间看这里;catalina.YYYY-MM-DD.log是每天生成的详细日志;localhost.YYYY-MM-DD.log里面判断应用加载过程中容器内部的日志;manager.*等模块日志一般不常看。

常见的启动报错基本有这几类:

症状常见原因快速验证方法
Address already in use端口冲突`ss -lntp
Neither JAVA_HOME nor JRE_HOME defined环境变量没生效echo $JAVA_HOME或 systemd unit查看
Permission denied目录属主不对ls -ld /opt/tomcat/logs
ClassNotFoundException javax.servlet...Tomcat版本与应用servlet规范不匹配检查应用web.xml及依赖
The web application... has failed to startweb.xml或Spring初始化出错看localhost.*.log

看到catalina.out里的堆栈异常时,别只截前两行,完整日志中最后一行的Caused by才是根因。尤其是Spring Boot应用Shade这个依赖时,真正的错误经常沉在多级Caused by底下。

5.3 常见问题速查:端口、权限、乱码和IDEA 404都遇到过吧

端口被占:Address already in use: JVM_Bind。先确认端口被谁占用,然后决定是杀掉占用进程还是改Tomcat端口。别上来就kill,先看是nginx还是其他Java进程,改了端口之后所有调用方都要跟着改,成本不小。

ss -lntp | grep 8080

权限问题:Tomcat启动后报无法写logs/catalina.out,几乎都是因为用root解压后,目录所有者为root,而systemd service里User=tomcat写不出日志。解决方法是:

sudo chown -R tomcat:tomcat /opt/tomcat/logs /opt/tomcat/work /opt/tomcat/temp

启动卡住不报错:生产服务器经常遇到Tomcat启动到一半卡住,半天不打印Server startup in。时间多是因为JVM在Linux上获取随机数时,/dev/random阻塞,导致SecureRandom初始化很慢。解决方法是给JVM加参数:

-Djava.security.egd=file:/dev/urandom

中文乱码:看到访问URL里的中文参数乱码,检查server.xml连接器有没有配置URIEncoding="UTF-8";应用响应乱码,检查应用的web.xml中是否设置了request.setCharacterEncoding("UTF-8")或者过滤器。Linux服务器语言包是LANG=C.UTF-8时,通常问题出在应用自身编码,而不是系统。

IDEA配置Tomcat后报404:如果看到“Tomcat 描述 源服务器未能找到目标资源的表示或者是不愿公开一个已经存在的”这种提示,基本是IDEA部署Web应用时的Deployment配置没有把artifact加到Server中。在IDEA里打开Run/Debug Configurations,找到Deployment页签,点击加号添加你要部署的war exploded,并把Application context设置成对应路径,问题就消失了。

6. 上线前的基础加固,走完这几步才能谈生产环境

6.1 禁用默认应用与降权运行,别把一个危险的默认状态丢到公网

Tomcat解压后默认带着docs、examples、manager、host-manager这些自带应用。开发阶段用它们很方便,但上线前必须处理掉,尤其不能让manager暴露到公网。否则攻击者可以尝试弱口令登录管理后台,一旦成功就能上传恶意WAR包,等于把服务器管理权限拱手送出。

我的习惯是把webapps目录清空,只建立ROOT目录,或者干脆把默认目录全部删掉,只保留自己部署的应用。同时把conf/server.xml里的Shutdown端口设为随机的强密码或直接关闭外部访问。Tomcat的8005端口本来只监听本机,但要防止防火墙意外放行了这个端口,建议改成<Server port="-1" shutdown="SHUTDOWN">禁用远程关闭命令。AJP端口如果不用Nginx和Apache做协议对接,建议直接注释掉,既减少攻击面,也避免AJP/1.3端口被被绕过利用的风险。

给Tomcat分配独立用户和独立目录,前面已经做了。额外检查一下/opt/tomcat/conf目录权限,Tomcat用户只有只允许读配置的权限,不放开写权限。Web上传功能如果把文件写到webapps目录,应用存在被写木马的风险,所以生产环境的webapps目录不要给写权限:

sudo chmod -R 555 /opt/tomcat/webapps sudo chmod -R 750 /opt/tomcat/conf

6.2 用Nginx把Tomcat从公网“藏”到内网,整套部署也算完整了

单独把Tomcat的8080端口对公网算是下策。原因有三:一个8080端口天然不适合HTTPS证书绑定和HTTP/2优化;第二个Nginx能挡掉一部分恶意请求;第三Tomcat本身对静态资源的处理速度远不如Nginx。所以生产部署最常见的布局是:Nginx监听80/443,反向代理到内网128.0.0.1:8080的Tomcat。

Nginx的核心配置片段如下,写在/etc/nginx/conf.d/tomcat.conf中:

server { listen 80; server_name example.com; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location ~* \.(css|js|png|jpg|gif|ico|woff2)$ { expires 30d; add_header Cache-Control "public"; proxy_pass http://127.0.0.1:8080; } }

proxy_set_header X-Forwarded-For这一点对Java应用很重要。Tomcat很多业务代码需要通过request.getRemoteAddr()获取真实IP,如果没有Nginx把这些转发头带过去,应用拿到的都是127.0.0.1,后面做日志分析、异地登录检查都会出错。如果只是静态资源走Nginx缓存,必须在资源后缀路径中同样业务请求分开,不要让Nginx把带鉴权的动态页面也缓存掉。

配上HTTPS的话,把443端口监听加上SSL证书,proxy_pass还是指向http://127.0.0.1:8080,由Nginx终止TLS,Tomcat依然走明文内网协议。Tomcat server.xml连接器可以不用动,应用感知的协议信息通过X-Forwarded-Proto传递即可。

这套部署完成后,Tomcat不直接面对公网,安全等级上了一个台阶,同时分担了静态资源压力。整个Linux环境下从安装、配置、托管服务,到部署、镜像,到最后Nginx接入,才算一条完整的链路。

最后再分享一个我长期用的经验:操作完之后,把/opt/tomcat/conf/server.xml改动的段落单独存一个diff记录,尤其是端口、连接器线程数、URIEncoding这几项。Tomcat版本升级时,直接用diff比对旧配置,比重新逐行读文档快得多。Linux部署这类事情,最怕的不是不会配置,而是配完忘了改过什么。留好变更记录,后面遇到诡异问题能省好几个小时。

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

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

立即咨询