Linux 下 Tomcat 部署全套实战:从 JDK 环境到生产配置

Linux 下 Tomcat 部署全套实战:从 JDK 环境到生产配置 1. 从零开始走一遍这套部署流程到底在解决什么问题1.1 适合谁读读完能干什么我刚接触 Linux 部署 Tomcat 那会儿以为解压 Tomcat、把 WAR 包扔进 webapps 目录就算完事结果被 JDK 版本不兼容、端口被占用、内存溢出、日志刷屏这些事轮番教育。折腾过几轮之后才明白Linux 下部署 Tomcat 项目这件事看起来是一条命令能解决的实际上牵扯到环境准备、版本匹配、目录权限、系统服务托管、日志管理等多个环节。任何一个环节没想明白项目上线之后都会变成半夜爬起来的理由。这篇教程的目标很朴素从一台裸机开始完成 Linux 系统安装装好 JDK下载并启动 Tomcat再把项目的老式 WAR 包或者新式 Spring Boot 应用部署进去。我会把每一步背后的原因讲清楚而不只是甩给你一串命令。适合刚接手 Linux 服务器、第一次部署 Java Web 项目的后端开发也适合准备把本地开发环境迁移到云端服务器的大三学生。不涉及复杂的集群和容器编排先把单机部署这条路走通。1.2 技术选型Linux 发行版和 Tomcat 版本怎么定部署之前先解决两个为什么Linux 发行版选哪个我个人的建议是如果是自己练手优先选 Ubuntu 22.04 LTS 或者 Debian 12如果是公司已有的服务器通常以 CentOS 7 或者 RHEL 系为主。CentOS 7 已经在 2024 年 6 月正式停止维护新项目不太推荐再往上面搭环境但存量服务器实在太多所以下文涉及包管理器的命令我会两边都给出你根据自己的环境挑着用。Tomcat 版本选哪个这里有个很多新人踩过的大坑Tomcat 10 开始Java Servlet 的包名从javax.*改成了jakarta.*。如果项目里用的是旧写法javax.servlet直接扔进 Tomcat 10 跑是跑不起来的。所以老项目老老实实用 Tomcat 9新项目再去考虑 Tomcat 10/11。JDK 方面Tomcat 9 通常搭配 JDK 8 或者 JDK 11Tomcat 10.1 往上建议 JDK 11 以上。选型原则总结成一句话跟着项目的依赖走而不是跟着最新版本走。2. 准备运行环境虚拟机、系统镜像与网络初始化2.1 系统镜像下载和虚拟机创建在没有物理服务器的情况下虚拟机是最快的搭建方式。我通常用 VMware Workstation个人使用也可以考虑 VirtualBox。两种工具的差别不大核心步骤一致。系统镜像下载地址需要注意去发行版的官方站点或者校内镜像站下载不要去第三方下载站。以 Ubuntu 为例官方站是 ubuntu.com/download/server国内镜像站可以用清华 tuna 或者阿里云 mirrors。下载 ISO 的时候注意看版本号Server 版没有图形界面适合当服务器用如果只是想体验桌面版可以选 Ubuntu Desktop。创建虚拟机的流程简单走一遍新建虚拟机选择稍后安装操作系统分配至少 2 核 CPU、2GB 内存磁盘给 20GB。装系统的时候语言选英语或者中文都行分区直接用默认的“使用整个磁盘”不需要手动分区。设置一个普通用户账号记得别选 root 登录生产环境用 root 是很大的隐患。2.2 安装完系统后必须做的网络配置系统装好以后第一件事是确认网络通不通。虚拟机默认网络连接方式通常是 NAT这种方式下虚拟机可以访问外网但外部无法直接访问虚拟机里的服务。部署 Tomcat 后做测试时这个问题会暴露出来。Ubuntu 下查看当前 IP 用ip addr如果看不到明显的 IP 或者想配置静态 IP可以看/etc/netplan/目录下的 YAML 文件。修改后执行sudo netplan apply。CentOS 7 下则可能使用nmcli或者直接编辑/etc/sysconfig/network-scripts/ifcfg-ens33这类文件CentOS 8 以上也转向了 NetworkManager。# Ubuntu 22.04 示例文件 /etc/netplan/01-netcfg.yaml network: version: 2 ethernets: ens33: dhcp4: no addresses: [192.168.1.100/24] routes: - to: default via: 192.168.1.1 nameservers: addresses: [223.5.5.5, 114.114.114.114]改完以后执行sudo netplan apply。有几点经验要记住别在服务器上配一个和网关冲突的静态 IP否则排查思路会变成一团乱麻另外虚拟机克隆或者重新生成 MAC 地址后旧配置里写的 IP 可能会失效。2.3 为什么建议用 SSH 而不是直接在虚拟机页面上操作可能有人觉得都打开虚拟机窗口了直接在终端敲命令不就行了问题在于服务器部署完成后你大部分时间是不愿意一直开着虚拟机的图形界面的。我更推荐的方式是在宿主机上用 SSH 客户端连过去比如 Windows 下用 Windows Terminal 或者 MobaXtermmacOS/Linux 下直接用系统终端。前提是安装 openssh-server# Ubuntu / Debian sudo apt update sudo apt install -y openssh-server # CentOS 7 sudo yum install -y openssh-server openssh-clients sudo systemctl start sshd sudo systemctl enable sshd连接时用ssh 用户名IP比如ssh tomcat192.168.1.100。使用 SSH 的好处不只是方便后续排错、查看日志、上传 war 包都能在一个稳定的连接里完成也方便你复用自己熟悉的本机工具。3. JDK 是 Tomcat 绕不过去的前置条件3.1 JDK 版本与 Tomcat 版本的匹配关系Tomcat 是用 Java 写的所以它的运行离不开 JDK。我之前见过一位同事把 Tomcat 10.1 配上 JDK 8Tomcat 启动倒是没报错但部署的项目一访问就 500查了半天才发现是版本匹配问题。Tomcat 各版本与 JDK 的最低匹配关系大致如下Tomcat 版本最低 JDK 版本说明Tomcat 9.0JDK 8老项目首选兼容javax.*Tomcat 10.0 / 10.1JDK 8 / 11包名改为jakarta.*旧项目需改造Tomcat 11JDK 17较新不建议新手直接上如果你只是在本机随便跑个 demo用 OpenJDK 完全够。如果你从 Oracle 官方下载 JDK需要登录账号才能拿到比较麻烦。所以下面的步骤我用 OpenJDK 演示。3.2 OpenJDK 安装与环境变量配置用包管理器安装是最省心的方式# Ubuntu / Debian 安装 JDK 11 sudo apt update sudo apt install -y openjdk-11-jdk # CentOS 7 安装 JDK 8 sudo yum install -y java-1.8.0-openjdk-devel安装完成后需要设置JAVA_HOME。很多人的误区是以为java -version能输出版本号就够了实际上 Tomcat 的启动脚本会主动找JAVA_HOME或者JRE_HOME。如果不设置就可能在启动时报Neither the JAVA_HOME nor the JRE_HOME environment variable is defined。先找到 Java 的安装路径# 查看 java 命令所在路径 which java # 通常输出 /usr/bin/java # 继续看真实路径 readlink -f /usr/bin/java在 Ubuntu 下通常显示/usr/lib/jvm/java-11-openjdk-amd64/bin/java所以JAVA_HOME就是/usr/lib/jvm/java-11-openjdk-amd64。接下来把环境变量写进/etc/profile或者单独的文件/etc/profile.d/java.sh我习惯用后者卸载重装时容易清理。sudo tee /etc/profile.d/java.sh /dev/null EOF export JAVA_HOME/usr/lib/jvm/java-11-openjdk-amd64 export PATH$JAVA_HOME/bin:$PATH EOF # 让配置在当前会话生效 source /etc/profile.d/java.sh3.3 验证 JDK 是否真的可用配置完之后重新登录或者source一下然后输入echo $JAVA_HOME java -versionJAVA_HOME应该输出刚才设置的路劲java -version能看到版本号。如果java -version显示了 OpenJDK 的版本但echo $JAVA_HOME是空的最常见的原因是你当前的 shell 没有重新加载 profile。执行source /etc/profile或者重新开一个 SSH 会话即可。这里说一个实际的辅助技巧可以用update-alternatives --config java查看系统里已经安装的所有 Java 版本。如果系统里同时存在 JDK 8 和 JDK 11一定要确认默认的java指向哪个版本。Tomcat 启动时用的是默认版本一旦指错项目行为可能很诡异。4. Tomcat 下载安装解压就能用但要理解目录结构4.1 下载 Tomcat 的准备工作和官网地址上的“坑”Tomcat 官网是 tomcat.apache.org。首页会列出多个大版本比如 Tomcat 9、Tomcat 10.1、Tomcat 11。注意官网提供的下载包里区分了tar.gzLinux 用和zipWindows 用别下错。点进对应版本的页面后你会看到几个下载入口有Binary Distributions、Source Code Distributions等。我们要的是Binary Distributions下的tar.gz包而不是src源码包。很多新手第一次点进去看到一堆目录就发懵其实就是照着apache-tomcat-9.0.10X.tar.gz这个文件名找即可。下载前先记一下这个文件的完整下载链接一会儿用wget在服务器上下载。cd /opt # 以 Tomcat 9.0.98 为例实际版本号以官网为准 wget https://dlcdn.apache.org/tomcat/tomcat-9/v9.0.98/bin/apache-tomcat-9.0.98.tar.gz tar -zxvf apache-tomcat-9.0.98.tar.gz # 做一个软链方便后续升级 ln -s /opt/apache-tomcat-9.0.98 /opt/tomcat使用 dlcdn.apache.org 这个地址时要注意Apache 的 CDN 经常会把旧版本归档到 archive.apache.org如果你遇到 404可以改用 https://archive.apache.org/dist/tomcat/tomcat-9/ 下面找对应版本。4.2 解压后的目录结构每个目录是什么职责解压完以后先别急着启动把目录结构看明白后面很多问题都能少走弯路。目录/文件作用备注bin/启动、停止脚本startup.sh、shutdown.sh、catalina.shconf/核心配置目录server.xml、tomcat-users.xml都在这里lib/Tomcat 运行需要的 jar 包一般不用动logs/日志输出目录catalina.out是最重要的日志webapps/部署 Web 应用的地方WAR 包扔进来即可work/JSP 编译后的 class 存放目录出问题时可以清空重启bin/startup.sh和bin/shutdown.sh是日常用的命令。catalina.sh则是更底层的脚本调用它也能启动和停止还能在前台运行 Tomcat方便直接看日志输出# 前台运行适合调试 /opt/tomcat/bin/catalina.sh run4.3 启动 Tomcat 并完成第一个验证启动 Tomcat 之前检查一下是否已经装好了 JDK/opt/tomcat/bin/startup.sh正常输出会包含Tomcat started。然后验证端口的监听情况ss -lntp | grep 8080看到LISTEN状态说明 Tomcat 已经起来了。在虚拟机上执行curl http://localhost:8080如果能返回一大段 HTML里面包含Apache Tomcat/9.0.x默认首页就正常了。如果想从宿主机浏览器访问需要确保虚拟机的网络模式是桥接或者做了端口转发同时检查防火墙。出现Tomcat started但端口没监听时不要慌先去logs/catalina.out看报错这是最直接的排除路径。5. 把项目部署到 Tomcat三种方式的对比与实操5.1 方式一webapps 下放 WAR 包最简单假设你已经有一个打包好的项目比如demo.war。把这个文件放到webapps目录下cp demo.war /opt/tomcat/webapps/Tomcat 默认会在启动时扫描webapps目录发现新的 WAR 包后自动解压并以 WAR 包名作为上下文路径来发布。比如你放的demo.war访问路径就是http://localhost:8080/demo/。如果你希望直接通过根路径访问需要把 WAR 包改名为ROOT.war覆盖原来的默认首页。这种方式的优点是省事缺点是每次更新项目时旧版本解压出来的目录可能会残留无关文件。所以更新时建议先把旧的解压目录和 WAR 包一起删掉再放新的 WAR 包进去。5.2 方式二server.xml 配置 docBase常用但要注意恢复机制如果你希望项目目录放在webapps外面或者用自定义路径访问可以在conf/server.xml里的Host节点下增加Context配置。下面是一个例子项目 WAR 包放在/data/projects/demo.war让它在根路径/下访问Host namelocalhost appBasewebapps unpackWARstrue autoDeploytrue Context docBase/data/projects/demo path reloadabletrue / /Host这种方式适合项目文件不想混在 Tomcat 自带的 webapps 目录里的场景。需要注意修改server.xml后必须重启 Tomcat 才能生效而且server.xml写错了会导致整个 Tomcat 启动失败所以修改前先备份cp /opt/tomcat/conf/server.xml /opt/tomcat/conf/server.xml.bak5.3 方式三Manager 管理界面适合测试环境Tomcat 自带一个 Manager 管理界面能在线部署和卸载 WAR 包确实方便但默认是关闭的。需要先编辑conf/tomcat-users.xml添加一个具有manager-gui角色的用户tomcat-users role rolenamemanager-gui/ user usernameadmin passwordyour-password rolesmanager-gui/ /tomcat-users然后在浏览器里访问http://localhost:8080/manager/html登录后可以上传 WAR 包并部署。这个功能我不建议在生产环境开启因为一旦密码泄露等于把整个应用的管理权交给了别人。即使要开也一定记得修改默认端口、限制来源 IP。5.4 部署后的目录权限问题部署完成后一个被很多新手忽略的点是文件权限。Tomcat 进程用什么用户运行就要保证那个用户对webapps和日志目录有写权限。如果你用普通用户tomcat运行 Tomcat但项目文件是用 root 解压到/opt/tomcat/webapps/下的可能出现“Tomcat 能启动项目也能访问但日志无法写入或者某些临时文件创建失败”的诡异问题。我习惯的做法# 创建专门运行 Tomcat 的用户 sudo useradd -r -s /bin/false tomcat # 把整个 Tomcat 目录属主改成 tomcat sudo chown -R tomcat:tomcat /opt/tomcat后面用这个用户来启动 Tomcat就避免了很多权限隐性问题。6. Tomcat 的端口、内存与日志配置6.1 修改端口时的注意事项默认端口是 8080如果和别的服务冲突需要改。配置文件在conf/server.xml找到这一段Connector port8080 protocolHTTP/1.1 connectionTimeout20000 redirectPort8443 /把port改成比如9090然后重启。注意redirectPort8443是 HTTPS 的端口一般不用改。改端口后访问地址也要同步变化这一点在云端服务器上特别容易被忽略因为安全组的入站规则也要一起调整。还有一个容易踩的坑Tomcat 修改端口后如果日志里出现Address already in use八成是端口号被操作系统里某个进程占用了这时候先ss -lntp | grep 9090看一眼别急着反复重启。6.2 JVM 内存参数应该放在哪里Tomcat 启动时需要一个 JVM默认内存大小往往不够项目使用。我见过不少项目访问一多就报java.lang.OutOfMemoryError: Java heap space就是因为没有手动调整内存参数。内存参数分两种JAVA_OPTS和CATALINA_OPTS。两者的区别在于JAVA_OPTS对所有启动路径生效CATALINA_OPTS只在 Tomcat 启动时生效。更推荐使用CATALINA_OPTS避免停止或执行其他命令时也加载过大内存配置。推荐的做法是在bin目录下新建一个setenv.sh文件Tomcat 启动脚本会自动读取它sudo tee /opt/tomcat/bin/setenv.sh /dev/null EOF export CATALINA_OPTS-Xms512m -Xmx1024m -XX:MetaspaceSize128m -XX:MaxMetaspaceSize256m -Djava.awt.headlesstrue EOF sudo chmod x /opt/tomcat/bin/setenv.sh说明一下几个参数-Xms是堆初始大小-Xmx是最大堆大小建议设置成一样的值这样 JVM 启动时就直接把堆申请到位减少运行时扩容带来的性能抖动。-XX:MaxMetaspaceSize是 JVM 元空间上限旧项目如果用到大量 CGLIB 代理或者反射元空间不足也会报内存溢出。机器只有 2GB 内存的话堆设置到 1GB 已经比较高再高有可能把系统内存吃紧反而触发其他问题。6.3 日志输出和排查定位日志是排错的主要线索来源。Tomcat 的主要日志文件文件记录内容catalina.outTomcat 控制台输出、启动日志、未捕获的异常localhost.log应用启动初期访问日志和报错manager.logManager 操作记录localhost_access_logHTTP 访问日志默认关闭可在 server.xml 里开启查看日志时先用tail -f观察实时输出tail -f /opt/tomcat/logs/catalina.out如果项目部署后立即 500多半是应用自身的异常直接看catalina.out里的堆栈即可。如果发现日志文件长得奇快无比检查是不是代码里的日志框架和 Tomcat 的日志框架冲突最常见的表现是log4j与java.util.logging的桥接问题。7. Linux 上的常见部署故障处理7.1 访问的时候一直 404Tomcat 启动正常curl http://localhost:8080也能看到首页但访问你的项目路径时 404。这种情况我见得非常多原因通常就两类。第一类WAR 包没有被正确解压。看看webapps目录下有没有生成和你 WAR 包同名的目录。如果没有去查logs/catalina.out看是不是 WAR 包本身损坏或者解压报错。第二类上下文路径和你以为的不一样。比如 WAR 包叫demo-1.0.warTomcat 会默认把上下文路径设置为/demo-1.0而不是/demo。如果项目里配置了 context-path也要一起对照否则访问/demo/自然 404。另外还有一种很坑的情况webapps/ROOT下的默认应用没有被清理而你的项目又在根路径下会出现访问根路径看到 Tomcat 默认首页看不到你的项目。处理办法是删掉webapps/ROOT目录然后把自己的 WAR 包改名成ROOT.war重新部署。7.2 端口被占用启动时报Address already in use或者BindException处理思路是# 找出占用 8080 端口的进程 ss -lntp | grep 8080 # 或者 lsof -i :8080 # 结束进程生产环境先确认能不能杀 sudo kill -9 PID有些情况下你会发现端口已经被起了另一个 Tomcat 实例比如之前启动过一次但没关干净。这时候不要盲目 kill先确认是不是同一个项目的旧进程避免误杀。这里有一个容易忽略的细节如果你的服务器本身就是一台机器上的多个网卡Tomcat 默认绑定0.0.0.0所有网卡的 8080 端口都会被占用。如果只允许特定 IP 访问请去配置防火墙而不是让 Tomcat 绑定单个 IP虽然 Tomcat 也支持address属性。7.3 内存溢出尤其是 PermGen 和 MetaspaceJava 8 以前的老项目经常出现java.lang.OutOfMemoryError: PermGen space改用元空间后更多出现Metaspace溢出。如果你在日志里看到这类报错优先检查setenv.sh里面的-XX:MaxMetaspaceSize是否设置得太小或者项目代码是否存在频繁动态生成类的逻辑。如果是堆内存溢出日志一般是Java heap space。先确认-Xmx配置再看是不是存在内存泄漏。简单排查可以使用jmap导出堆转储# 找到 Tomcat 进程 PID ps -ef | grep tomcat # 导出堆快照 jmap -dump:formatb,file/tmp/tomcat_heap.hprof PID然后可以用 MAT 或者 VisualVM 分析。这一步对于新手可能有点难但至少要能看懂日志里是哪种内存溢出才能在搜索引擎里找到正确方向。8. 生产环境的一些建议和踩坑后的结论8.1 别用 root 跑 Tomcat这里想单独强调一下。很多人图省事装完系统一直用 root 操作Tomcat 也用 root 启动。这在测试环境问题不大生产环境风险很高万一应用被上传了恶意 jsp 文件root 权限意味着服务器完全失守。搭环境的时候多花两分钟创建专用用户sudo useradd -r -s /usr/sbin/nologin tomcat sudo chown -R tomcat:tomcat /opt/tomcat sudo -u tomcat /opt/tomcat/bin/startup.sh用nologin作为登录 shell用户不能直接登录系统但可以运行 Tomcat 和相关命令。8.2 清理默认应用与防火墙Tomcat 下载后自带的默认应用有docs、examples、manager、ROOT等。如果不需要 Manager 在线管理功能生产环境建议直接删掉或者改名rm -rf /opt/tomcat/webapps/docs /opt/tomcat/webapps/examples /opt/tomcat/webapps/manager /opt/tomcat/webapps/host-manager同时检查防火墙。Ubuntu 下用ufwCentOS 7 下用firewalld# Ubuntu sudo ufw allow 8080/tcp # CentOS 7 sudo firewall-cmd --zonepublic --add-port8080/tcp --permanent sudo firewall-cmd --reload防火墙是部署中很容易忽略的环节。我见过太多人检查了 JDK、检查了 Tomcat、检查了项目最后发现是云服务器安全组没放行端口。顺序排查时把防火墙和安全组放在端口监听之后一起检查。8.3 推荐用 systemd 托管 Tomcatstartup.sh手动启动的方式适合开发环境生产环境建议用 systemd 把 Tomcat 注册成服务好处是开机自启、崩溃自动拉起、日志统一管理。在/etc/systemd/system/tomcat.service下创建服务文件大致内容如下[Unit] DescriptionApache Tomcat Web Application Container Afternetwork.target [Service] Typeforking Usertomcat Grouptomcat EnvironmentJAVA_HOME/usr/lib/jvm/java-11-openjdk-amd64 EnvironmentCATALINA_PID/opt/tomcat/temp/tomcat.pid EnvironmentCATALINA_HOME/opt/tomcat EnvironmentCATALINA_BASE/opt/tomcat ExecStart/opt/tomcat/bin/startup.sh ExecStop/opt/tomcat/bin/shutdown.sh RestartSec10 Restartalways [Install] WantedBymulti-user.target然后执行sudo systemctl daemon-reload sudo systemctl enable --now tomcat sudo systemctl status tomcat这样以后重启机器Tomcat 会自动启动不用再手动执行脚本。8.4 最后说点实际的这套流程走下来本质上就是把“在一个全新的 Linux 环境里跑起一个 Java Web 项目”这件事拆成了四个部分操作系统、JDK、Tomcat、项目包。每一部分都有独立的验证方法不许跳步操作系统看网络和 SSHJDK 看java -version和JAVA_HOMETomcat 看端口和默认首页项目看访问路径和日志。只要每一层都验证通过整体部署通常就不会出大问题。如果是部署 Spring Boot 的可执行 jar其实不需要 Tomcat 外层容器java -jar一条命令即可但如果项目是以 WAR 包形式提供或者公司规范要求必须用外部 Tomcat那上面这套流程就是完全适用的。我实际操作中的个人体会是环境问题占了故障的小半剩下的多是项目配置和版本兼容像 Tomcat 10 的javax与jakarta问题还有 JDK 版本和依赖冲突问题布局排起来非常繁琐。所以装环境的时候稍微多花一点时间把版本确认好把配置写在setenv.sh这类工程化位置后面能省下几倍的时间。