CentOS搭建Java与Tomcat:从环境准备到生产加固全攻略
CentOS服务器上搭Java和Tomcat这活儿看起来就两条命令的事真上手时坑一点也不少。我从最早在自己电脑上照着网文一步步敲到现在能给生产环境做部署方案中间走过的弯路、踩过的坑攒了不少今天全倒出来聊一聊。这篇文章就是给准备在CentOS上从零搭建Java Web运行环境的同学看的不管是自己练手、公司内网部署还是接手一台陌生的服务器照着这个思路走能少折腾好几个晚上。我会把安装过程拆成几段来讲装之前要确认什么、JDK怎么选怎么装、环境变量怎么配才不出错、Tomcat怎么规划和启动以及部署完成之后建议顺手做掉的长稳配置。文章里的命令我都按企业里最常见的CentOS 7.x和CentOS 8.x分别备注遇到版本差异会单独说清楚。1. 动手前先确认环境版本、架构、用户和目录都要想清楚很多教程上来就让你yum install java或者直接扔一个安装包链接结果装到一半才发现系统是64位还是32位都没搞清或者GCC版本不对白白浪费时间。我在一台CentOS机器上踩过最无语的坑是前人装过一个老版本的MySQL自带的OpenJDK导致我后续配环境变量时各种串台。所以拿到一台机器第一件事不是下载而是把系统的底细摸清楚。1.1 确认CentOS版本和CPU架构装Java之前先确认几件事命令如下cat /etc/redhat-release uname -m/etc/redhat-release会告诉你具体是CentOS 7还是8uname -m会告诉你架构x86_64就是64位aarch64就是ARM架构。为什么这个重要因为JDK的安装包分平台下载错了直接装不上或者装了跑不起来报错还特别奇怪。另外要看系统里是不是已经有Java了避免版本冲突java -version which java如果系统里已经带了OpenJDK但版本和你后续项目需要的不一致建议先记录下位置后面配置环境变量时处理干净不然两个Java互相抢优先级启动Tomcat时用的JDK不是你想用的那个排查起来相当折磨人。1.2 用专用账号跑Tomcat别用root凑合这是我强烈建议你提前做的事。很多新手图省事直接root解压、root启动Tomcat短期没事时间长了问题就来了。Tomcat跑在root下万一Web应用有漏洞被上传了Webshell攻击者拿到的就是root权限整台服务器直接裸奔。正确的做法是创建专用运行账号useradd -M -s /sbin/nologin tomcat-M表示不创建家目录-s /sbin/nologin表示禁止这个账号登录Shell很好体现了最小权限的思路。这个账号只用来启动Tomcat进程、读写必要目录不登录不交互即使应用被攻破能造成的危害也小得多。这个习惯我在生产环境用了很久建议从一开始就养成。1.3 规划目录结构目录规划不做好后面升级、迁移、日志清理都难受。我在某服务器上见过把Tomcat解压到/tmp下的操作重启一次机器整个环境人间蒸发真是哭笑不得。这里给出一个实践下来比较顺手的结构/app/ # 应用总目录 /app/jdk # JDK安装目录 /app/tomcat # Tomcat安装目录 /data/tomcat # Tomcat数据目录存放日志、webapps等 /data/logs # 独立日志目录为什么要单独建数据目录因为Tomcat自带的目录结构里logs和webapps都在解压目录下而解压目录属于程序目录升级时要整体替换。如果把日志和部署包和程序混在一起升级时要么把日志弄丢要么为了保日志还得先复制来复制去操作繁琐还容易出错。所以拆开是值得的这也是我实践中吃过亏之后学乖的。2. JDK安装系统源不一定合适手动解压更可控CentOS自带的软件源里确实有OpenJDK敲一条yum install -y java-1.8.0-openjdk就能装完。但这里面有几个坑一是默认源里的JDK版本不一定跟你们项目要求一致二是OpenJDK的安装路径比较分散JAVA_HOME找起来费劲后续配置容易出错。所以我个人更推荐手动下载JDK压缩包再解压虽然多几步操作但路径完全可控升级也方便。2.1 选JDK 8还是JDK 11、17版本选择没有标准答案主要看你们的项目跑在什么环境里。老旧项目、Spring Boot 2.x早期版本、大量使用JSP的老系统JDK 8最稳。新项目、Spring Boot 2.5、Spring Cloud 2021JDK 11、17都很常见性能和生态都更现代。如果是做新系统从零起步可以认真考虑JDK 17毕竟它是LTS版本支持周期长而且近年主流的中间件、框架都已经全面兼容17。具体的版本建议去官方页面或国内镜像站下载需要下载tar.gz格式的压缩包不要下载.rpm因为我们这里选择解压方式安装。例如下载JDK 8的Linux x64版本时选择类似jdk-8uXXX-linux-x64.tar.gz的文件。版本编号里的uXXX是更新版本号数字越大表示补丁越新建议选以该系列里较新的版本。2.2 解压安装与目录规范下载完上传到服务器刚才规划好的目录然后解压mkdir -p /app/jdk tar -zxvf jdk-8uXXX-linux-x64.tar.gz -C /app/jdk cd /app/jdk mv jdk1.8.0_XXX jdk8这里把解压出来的目录改名为jdk8是为了去掉版本号以后路径好记。如果想升级只需要重新解压新版本再改软链接指向新目录就行ln -s /app/jdk/jdk8 /app/jdk/default后面环境变量就指向/app/jdk/default升级时把软链接切换一下指向新版本目录其他什么都不用动。这个思路在服务器多、环境多的时候特别省事。3. JAVA_HOME环境变量配置三个经典坑JDK装完环境变量是关键。配置不对java -version不管用Tomcat也不认而且报错信息时常含糊能让你排查半天。环境变量本来是想配给所有用户用的实际上很容易只对root生效普通用户拿不到这是常见的第一个坑。3.1 全局配置文件的正确写法在/etc/profile.d/下新建一个专门文件比如java.shvi /etc/profile.d/java.sh写入如下内容export JAVA_HOME/app/jdk/default export PATH$JAVA_HOME/bin:$PATH export CLASSPATH.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar写完后chmod x /etc/profile.d/java.sh source /etc/profile.d/java.sh这里要解释一下为什么放在/etc/profile.d/而不是直接改/etc/profile。CentOS的登录Shell会遍历/etc/profile.d/下所有以.sh结尾的脚本并执行把独立的配置拆成一个专属文件比在/etc/profile里追加一大段更清晰也不会影响其他人往这个文件里写内容。CLASSPATH这行现在很多教程其实已经不太写了因为JDK 8以后某些场景下不设置也能正常工作。但如果你们的项目里有老代码还在用javac直接编译运行的保留这个变量会省去一些麻烦所以我习惯写上。3.2 非登录Shell的环境变量坑/etc/profile只对登录Shell生效。如果你用脚本通过ssh执行远程命令或者某些自动化工具通过非交互方式调用Java可能加载不到这个环境变量。解决办法是在~/.bashrc里也加一份echo export JAVA_HOME/app/jdk/default ~/.bashrc echo export PATH$JAVA_HOME/bin:$PATH ~/.bashrc这里要提醒一句如果Tomcat进程是通过systemd启动的它不会读/etc/profile也不会读~/.bashrc而是直接从service文件里读环境变量。所以稍后配置Tomcat自启时我会在service文件里显式写上JAVA_HOME的路径这就绕开了所有Shell环境问题。3.3 验证环境变量的必做检查配置完之后要做三次验证java -version which java echo $JAVA_HOME三条结果都符合预期才算配好。java -version显示你安装的版本which java指向/app/jdk/default/bin/javaecho $JAVA_HOME打印正确的路径。这三条对应一个比一个仔细任何一条异常都说明环境变量或者文件权限有问题。如果which java显示的路径不是/app/jdk/default/bin/java多半是PATH顺序有问题或者系统里有残留的旧Java排查优先级很高。我在外面排查过不少机器都是旧版OpenJDK占了/usr/bin/java导致新装的JDK完全没生效。4. Tomcat安装与目录规划程序目录和部署内容要分离JDK装好了下面就到Tomcat。Tomcat的下载和解压比JDK还简单但目录规划这步我希望你认真对待。Tomcat默认的目录结构和功能我先拿一张表给你理清楚再去讲怎么调整。4.1 Tomcat目录结构与账目对照Tomcat解压后默认目录目录/文件作用建议bin启动、停止脚本保留在程序目录conf核心配置server.xml、web.xml等建议单独备份libTomcat依赖的Jar包保留在程序目录logs日志输出重定向到数据目录temp临时文件系统自动管理webappsWeb应用部署目录建议重定向或单独管理workJSP编译后的临时目录系统自动管理如果直接解压完就用所有东西塞在一个目录里用是能用但当我第一次做版本升级时因为webapps和logs都在程序目录里差点把线上系统停了半天。所以我的做法是程序目录只放Tomcat的二进制和配置模板部署包和日志分开存放。4.2 手动安装Tomcat的操作路径mkdir -p /app/tomcat tar -zxvf apache-tomcat-9.0.XX.tar.gz -C /app/tomcat cd /app/tomcat mv apache-tomcat-9.0.XX tomcat9然后建立软链接ln -s /app/tomcat/tomcat9 /app/tomcat/default数据目录单独创建并调整权限mkdir -p /data/tomcat/{logs,webapps} chown -R tomcat:tomcat /data/tomcat接下来修改Tomcat的配置文件让它把日志写到数据目录。在conf/server.xml里找到Host标签下的Context或者在conf/Catalina/localhost/下创建配置文件来重定向。一个简洁的思路是修改conf/catalina.properties里的java.io.tmpdir和日志相关路径参数但最常见的做法还是直接改启动脚本里的CATALINA_OUTvi /app/tomcat/tomcat9/bin/catalina.sh找到CATALINA_OUT的赋值把输出指向export CATALINA_OUT/data/tomcat/logs/catalina.out注意这个文件只是定义了标准输出和错误输出的定向Tomcat自己用Log4j等框架产生的日志还需要在conf/logging.properties里调整路径。把logging.properties里的${catalina.base}/logs改成/data/tomcat/logs即可或用sed批量替换。4.3 Tomcat专属账号的权限分配前面创建的tomcat账号在这里正式派上用场chown -R tomcat:tomcat /app/tomcat chown -R tomcat:tomcat /data/tomcat权限分配有个细节值得注意Tomcat安装目录本身虽然给了tomcat账号写权限但生产环境为了安全可以把conf目录改成只读启动时Tomcat会读配置不需要频繁写。真要改配置的时候用root先编辑再chmod回只读这套流程在高安全要求的服务器上很实用。5. 启动Tomcat并验证端口、防火墙、权限和日志一处都不能漏环境变量配好权限也调好了下面到了最让人兴奋也最容易卡壳的环节启动。我试过第一次启动Tomcat时端口一直不通结果发现是防火墙后来又遇到权限不足日志里报Permission denied路径查了半天才发现是logs目录的属主不对。所以我把这块容易出问题的点都整理出来跟着顺序走能避坑。5.1 环境检查和首次启动前要做的配置启动前先确认这样几件事第一JAVA_HOME是否在Tomcat的启动环境里可见。如果之前没有定义全局环境变量或者用tomcat账号启动时加载不到可以在Tomcat的bin/setenv.sh里显式指定这是Tomcat官方支持的附加脚本启动时如果存在会被自动加载mkdir -p /app/tomcat/tomcat9/bin cat /app/tomcat/tomcat9/bin/setenv.sh EOF JAVA_HOME/app/jdk/default CATALINA_HOME/app/tomcat/tomcat9 CATALINA_BASE/app/tomcat/tomcat9 CATALINA_OUT/data/tomcat/logs/catalina.out JAVA_OPTS-Xms512m -Xmx1024m -XX:MetaspaceSize128m -XX:MaxMetaspaceSize256m EOFJAVA_OPTS这里给的是一个基础模板Xms是初始堆内存Xmx是最大堆内存Metaspace是JDK 8以后存放类元数据的空间。具体数值要根据机器的物理内存和应用负载来调整一般来说初始堆和最大堆建议设为一样避免运行中动态扩容带来的性能抖动。第二检查端口是否被占用ss -lntp | grep 8080如果8080被占用需要修改conf/server.xml里的Connector port8080改成其他端口或者先查清是什么进程占用的再做处理。第三确认防火墙放行。CentOS 7和8默认可能开着firewalld可以用以下命令放行Tomcat端口firewall-cmd --zonepublic --add-port8080/tcp --permanent firewall-cmd --reload如果用的是云服务器除了系统防火墙云平台的安全组规则也要一并检查。这部分经常被忽略系统防火墙明明放行了端口还是不通我遇到过好几次最后都是云平台安全组的锅。5.2 首次启动的完整验证流程启动脚本在bin目录下启动前先切到tomcat账号su -s /bin/bash tomcat -c /app/tomcat/tomcat9/bin/startup.sh或者用脚本加上daemon方式启动。如果 startup.sh 没有执行权限先chmod x补上。启动后马上验证进程和端口ps -ef | grep tomcat ss -lntp | grep 8080如果进程在但端口没起基本是配置或权限问题。接着看日志tail -f /data/tomcat/logs/catalina.out tail -f /app/tomcat/tomcat9/logs/localhost.log日志里常见的报错我列一张表方便对照报错关键词原因处理办法Permission denied数据目录或临时目录没有tomcat账号的写权限检查并修正属主和权限Port 8080 already in use端口被其他进程占用改Tomcat端口或停掉占用进程JAVA_HOME is not defined correctlyTomcat找不到JDK路径检查setenv.sh和JAVA_HOME配置Directory ... does not exist指定的日志或部署目录不存在提前创建并设置权限ZIP file too large / corruptwar包损坏重新上传并校验MD5这些坑我早期全都踩过一遍最有迷惑性的是JAVA_HOME is not defined correctly。明明在命令行里echo $JAVA_HOME是正确的但Tomcat启动脚本就是报错。原因就是Tomcat的启动脚本是独立进程加载不到你当前Shell的环境变量或者在catalina.sh脚本开头解析路径时因为路径里有软链接而解析失败。解决办法就是在setenv.sh里写死路径不要依赖全局环境变量。5.3 通过访问验证Web环境是否真正可用Tomcat启动后在浏览器里访问http://服务器IP:8080能看到Tomcat默认首页说明基础环境OK。接着部署一个最简单的测试应用来验证JSP和Servlet容器都正常工作比如在webapps下放一个test/目录里面写个index.jsp内容就一行% new java.util.Date() %访问时能看到动态生成的日期说明JSP引擎在跑JDK和Tomcat的配合没有大问题。这一步别看简单它能帮你在投入真实业务前快速排除一批环境层级的问题强烈建议不要省略。5.4 一个容易误导的启动错误重复执行startup.sh这个坑我要单独拿出来讲。Tomcat启动时如果startup.sh被执行了两次并不会报错因为Tomcat会检测到已有实例第二次执行可能不会把新进程拉起来但也没有明显的报错输出。很多人这时候就以为启动不了开始四处排查配置结果一看进程原来第一个实例早就跑起来了。所以启动时先看进程再判断端口不要一看到脚本执行完没输出就慌了。实际上在部分Tomcat版本里重复执行startup.sh还会出现端口被占用导致启动失败但不影响原实例的情况容易让人以为之前启动了东西把端口占了其实占端口的正好是上一次启动的Tomcat自己。6. 让环境长期稳定运行开机自启、JVM参数与安全加固手动启动的Tomcat重启机器后就没了而且JVM内存不调并发一上来就频繁Full GC接口一个接一个超时不对应用做最小权限加固一个上传功能的漏洞就能让整个服务器被攻破。这些等到出事再处理就晚了所以建议装完环境之后趁热把这些事情一并做完。6.1 配置systemd开机自启在生产上Tomcat不应该用startup.sh裸奔而是交给systemd托管这样开机自动启动异常退出还能自动拉起。写一个service文件vi /etc/systemd/system/tomcat.service内容大致如下[Unit] DescriptionApache Tomcat Web Application Container Afternetwork.target [Service] Typeforking Usertomcat Grouptomcat EnvironmentJAVA_HOME/app/jdk/default EnvironmentCATALINA_HOME/app/tomcat/tomcat9 EnvironmentCATALINA_BASE/app/tomcat/tomcat9 EnvironmentCATALINA_OUT/data/tomcat/logs/catalina.out ExecStart/app/tomcat/tomcat9/bin/startup.sh ExecStop/app/tomcat/tomcat9/bin/shutdown.sh Restarton-failure RestartSec10 [Install] WantedBymulti-user.target写完后执行systemctl daemon-reload systemctl enable tomcat systemctl start tomcat systemctl status tomcat我在这里特别把JAVA_HOME、CATALINA_HOME等环境变量写进了service文件原因前面提到过systemd启动的进程不会读Shell的profile文件如果不在这里写启动脚本会因找不到JAVA_HOME而失败。另外Typeforking是因为Tomcat的startup.sh脚本启动后会返回但实际Tomcat进程会在后台持续运行这个类型就是为这类启动方式设计的。6.2 JVM参数的基本调优机器是多少内存JVM的堆就应该给多少没有固定公式但有几个基本思路。比如一台4G内存的服务器分配给Tomcat的堆内存建议不超过2G要给系统本身和后续可能的数据库、缓存留余地。基础配置可以改成JAVA_OPTS-server -Xms1g -Xmx1g -XX:MetaspaceSize256m -XX:MaxMetaspaceSize512m -XX:UseG1GC-server选择server模式-Xms和-Xmx一致避免动态扩容。如果机器是2G内存那-Xms、-Xmx给512m比较稳妥。设置不宜过大否则系统可用内存被掏空操作系统频繁使用交换分区性能更差。再补一个实用的JVM监控参数方便出问题时从日志里定位-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/logs/tomcat_heapdump.hprof这样JVM内存溢出时能留下堆内存快照后续用分析工具排查确实好使。这个参数我在生产事故排查中帮了不少忙属于花很小成本换很大便利的配置。6.3 常见安全问题与基本加固装完环境顺手加固一遍比自己出事后慌慌张张补漏洞要强太多。这里的加固集中在几个方面都是实操中验证有效的做法一是管理端口的访问限制。Tomcat的管理后台manager、host-manager默认是绑在本地的不向外提供服务通过修改webapps/manager/META-INF/context.xml里的allow字段来控制访问来源。如果业务不需要管理后台最简单的是直接把manager和host-manager这两个目录从webapps下移走或改名不给攻击者留下可趁之机。二是去掉默认的示例应用。Tomcat自带的examples、docs、ROOT这些默认应用如果没有实际用途建议一并清理减少暴露面。三是隐藏版本号。Tomcat默认会在错误页面和响应头里暴露版本号攻击者可以根据版本号查找已知漏洞。修改conf/server.xml里的Connector添加serverApache Tomcat/9这样响应头里的Server字段就不会把精确版本号泄露出去了。四是不要用admin这种默认用户名。配置Tomcat用户时密码强度和用户名复杂度都要注意如果不需要用户认证功能可以完全不配tomcat-users.xml。6.4 日志轮转与磁盘空间长期运行的服务器上Tomcat日志增长快得超出想象。catalina.out如果不做轮转会在几个月内挤爆磁盘。最简单的方式是用系统自带的logrotatevi /etc/logrotate.d/tomcat内容示例/data/tomcat/logs/catalina.out { daily rotate 7 compress missingok copytruncate }daily表示每天轮转一次rotate 7保存7份compress压缩旧日志copytruncate在Tomcat不重开文件描述符的情况下也能正常轮转。这一点很关键因为Tomcat的catalina.out是长时间保持打开的文件直接用mv改名再建新文件Tomcat还会继续往原文件描述符里写日志会写到已改名的文件里磁盘空间照样涨。copytruncate先复制再清空避开了这个问题。我处理过一台线上机器就是没配日志轮转catalina.out涨到十几个G把根分区挤到100%数据库都连不上了。配上logrotate之后这类风险就基本解除了。实测下来把开机自启、JVM参数和日志轮转这几件事做掉Tomcat的环境算是真正站稳了之后日常要操心的就只有业务本身而不用反复折腾底层环境了。