
1. 先搞清楚一件事Tomcat“404”到底是谁报的遇到Tomcat报404第一反应别急着改代码。我先问自己一个问题这个404是Tomcat自己吐出来的还是应用页面里自己写的响应两个来源不一样排查方向完全不同。Tomcat自带的404错误页长什么样默认是一个大白页上面写着HTTP Status 404 – Not Found Type Status Report Description The origin server did not find a current representation for the target resource or is not willing to disclose that one exists.如果你看到的是这种说明请求确实打到了Tomcat上但Tomcat在它的路由表里找不到对应的资源。如果看到的是项目里的自定义404页面那说明请求进了应用但应用内部的路由没匹配上。另外还有一种容易混淆的情况浏览器报404但Tomcat日志里干干净净。这种时候请求压根就没到Tomcat要么是前面挡了Nginx要么是防火墙拦了要么是端口不对。所以排查的第一步永远是划分责任边界——到底是谁在说404。我自己的排查习惯是先看三个东西浏览器地址栏里的URL到底长什么样Tomcat日志文件里有没有对应的请求记录访问的端口是不是Tomcat实际监听的端口这三个信息对不上后面全是瞎猜。2. URL与部署路径不匹配80%的404都死在这2.1 访问路径的计算公式十个人九个算错Tomcat的URL路径不是随便拼的它遵循一个固定公式http://IP:端口/上下文路径/资源路径上下文路径默认等于Web应用目录的名字比如部署了一个myweb.war默认上下文路径就是/myweb资源路径应用内部web.xml或者Spring MVC之类的框架配的路由最常见的翻车现场是这样的把myweb.war丢进webapps然后访问http://localhost:8080/index.jsp结果404。因为正确地址是http://localhost:8080/myweb/index.jsp——少了上下文路径这一层。反过来也常见IDEA里配了Application context为/部署的时候一切正常但同一个项目拷到Linux服务器上直接丢war包部署访问路径就从/变成了/项目名之前能打开的链接全404了。2.2 如何确认上下文路径方法有两种。第一种是看Tomcat控制台启动日志启动的时候会打出这么一行INFO: Deploying web application archive [myweb.war]然后紧跟着会有一行类似于INFO: Deployment of web application archive [myweb.war] has finished in [1,234] ms只看这一行还不够需要访问http://localhost:8080/manager/text/list配置了Tomcat Manager的话它会列出所有应用和对应的上下文路径。或者最简单的看conf/server.xml里Host节点下有没有手动配过Context。有些老项目喜欢在server.xml里写死Context路径在path属性里清清楚楚。2.3 一个容易踩的坑ROOT应用的特殊性把war包改名为ROOT.war部署或者把解压目录命名为ROOT访问的时候就不需要上下文路径直接http://localhost:8080/就能访问首页资源。这时候很多人会误以为“我不用写路径就能访问”于是后面再部署第二个应用时愣是以为也可以不写路径然后404。我实际碰到过最折腾的一次一个前后端分离项目前端静态文件放在了ROOT下面后端API打成api.war。前端同事访问http://xxx:8080/api/user/list一直404查了半天发现后端包名被人改成了backend.war上下文路径变成了/backend前端代码里的/api前缀全部对不上。2.4 动手排查的固定套路碰到URL相关的404我按这个顺序查在浏览器直接访问http://IP:8080/确认Tomcat默认首页能开访问http://IP:8080/项目名/看Tomcat默认的目录列表或者应用首页确认访问的端口是8080还是改过端口conf/server.xml里搜Connector port确认项目实际部署到了哪个目录别webapps和webapp两个目录搞混确认war包是否完整解压有时候war包损坏解压到一半卡住虚目录建了但里面没东西访问必404注意Tomcat 8.5以后默认不支持在server.xml里加Context节点加了会导致Tomcat启动失败。很少见有人用这个方式了但如果接手老项目遇到需要知道有这么回事。3. 静态资源404明明文件就在那里偏偏访问不到3.1 WEB-INF目录的“隐形结界”很多初学者把静态资源丢到了WEB-INF目录下然后访问http://localhost:8080/项目名/WEB-INF/css/style.css结果404。这不是文件不存在是Tomcat的安全机制故意不让你访问。WEB-INF目录是整个Web应用的“内部区域”容器对外屏蔽了它。放在里面的东西只有服务端代码能通过forward或ServletContext.getResourceAsStream()去拿浏览器直接访问一律404。这个设计是为了防止JSP源码、配置文件、类文件被外人直接下载。所以静态资源正确的放置位置是项目根目录/ ├── WEB-INF/ │ ├── web.xml │ └── classes/ └── css/ └── js/ └── images/放在和WEB-INF平级的目录下浏览器才能直接访问。3.2 servlet-mapping把静态资源“吃掉”了还有一种坑比较隐蔽。项目里用了Spring MVC这类框架在web.xml里配置了servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping/这个模式匹配所有请求包括.css、.js、.png这些静态资源。如果框架内部没有配置静态资源放行Spring MVC需要配mvc:resources或者用WebMvcConfigurer.addResourceHandlers那这些请求全会被当成控制器路由处理找不到对应的Handler就404。这里的404有可能是Tomcat返回的也有可能是框架内部自己丢出来的表现上都是资源加载不出来。页面能打开但样式全丢了图片裂了控制台刷一堆404。排查方法很简单把URL里的静态资源路径直接粘到浏览器单独访问看返回什么再临时去掉servlet-mapping的/映射改成*.do这种后缀匹配看问题是否消失。3.3 URL大小写和编码问题Linux和Windows在处理大小写时完全不同。Windows的文件系统不区分大小写但Linux区分。开发的时候在Windows上写style.css部署到Linux访问Style.cssWindows没问题Linux上就是404。还有一个容易被忽略的问题URL里的中文路径。比如上传图片后图片名是中文的存储到了磁盘但访问URL里没做URL编码浏览器直接发中文过去Tomcat对中文路径的处理会因为URIEncoding配置不同而表现不同。conf/server.xml的Connector上可以加Connector port8080 protocolHTTP/1.1 URIEncodingUTF-8 /不加的话Tomcat 8之前默认用ISO-8859-1解析URI中文路径基本必乱码乱码后匹配不到文件就是404。3.4 浏览器缓存造成的伪404这种问题最坑人——后端文件明明在但浏览器就是访问404。原因可能是浏览器缓存了一个404响应或者缓存了旧的路径。特别是某些反向代理服务器比如Nginx对404结果做了缓存后端修好了但缓存还留着一个404。我处理过一个真实案例前端小哥反馈某个静态文件404了但我用curl访问完全正常浏览器里怎么刷新都是404。最后发现是Nginx开了proxy_cache对这个URL缓存了错误状态。清了缓存立马恢复了。排查的时候别忘了一条原则用curl测试永远比浏览器靠谱curl拿到的是服务器实时响应浏览器拿到的是缓存。4. 明明应用启动正常Tomcat却死活4044.1 应用启动成功不等于路由匹配成功这种问题最折磨人因为Tomcat能正常启动——日志里看不到任何异常但访问应用里的任何URL都是404。遇到这种情况我先查的不是Tomcat而是应用内部。以Spring Boot打war包部署到Tomcat为例有这么几个经典原因Spring Boot的启动类找不到。按照规范SpringBootServletInitializer的子类应该放在包结构的顶层如果包扫描的根路径不对Spring容器里一个Controller都注册不上所有请求全部404Servlet版本不匹配。Spring Boot 3.0以上基于Jakarta EE 9类名从javax.servlet变成了jakarta.servletTomcat 8.5和Tomcat 9不兼容这类应用部署上了也起不来或者起一半就报错缺少必要的Servlet容器初始化器。有些老项目使用的Servlet版本和Tomcat支持的版本不一致导致部署时上下文创建成功但Servlet全部没注册判断应用内部到底有没有注册路由看日志最直接。Tomcat启动日志里有一段类似于INFO: Deploying web application archive [xxx.war] 信息: Servlet dispatcherServlet mapped to [/]如果只有Deploying没有Servlet mapped的记录那404就是必然的。4.2 web.xml里的欢迎页配置失效访问http://localhost:8080/项目名/Tomcat会查找欢迎页welcome-file然后自动把请求重定向到欢迎页。如果web.xml里这样配置welcome-file-list welcome-fileindex.html/welcome-file /welcome-file-list但是项目根目录下根本没有index.html那访问根路径就是404。有些人把首页写成了index.jsp但web.xml里写的是login.html找了一圈资源就是不匹配。还有一种情况index.html在但放在了WEB-INF/views下面比如模板引擎的前端页面目录Tomcat直接访问不到根路径自然404。4.3 过滤器或拦截器拦截之后没放行某些过滤器会拦截所有请求做权限校验比如检查Session里有没有登录标记。如果校验失败代码直接response.sendError(404)而不是sendRedirect到登录页。这种情况的表现就是所有URL都404包括理应存在资源路径但Tomcat的日志里能看到请求确实打了进来且过滤器处理了请求。这时候看响应头会有端倪。Tomcat原生404的响应里会有Content-Type: text/html;charsetutf-8而且Body是Tomcat的错误页面模板。如果发现Body内容是自己写的文本那就说明404不是Tomcat丢的是应用层故意返回的。5. IDEA和Eclipse这类IDE里Tomcat 404的重灾区5.1 “根路径”和“项目路径”错位在IDEA里跑Tomcat访问路径是由Run/Debug Configurations里的Deployment选项卡决定的。很多人从网上下的教程配置的时候Application context填的是/然后项目里的链接却是/项目名/xxx或者反过来项目链接写死了/但IDEA里配置的上下文路径是/项目名。打开页面必有一半链接是404。这一块最容易乱我建议养成一个习惯IDEA的Application context和正式环境的上下文路径保持一致。比如正式环境是/order-system本地开发也配/order-system省得开发和测试环境之间来回踩404。5.2 部署的war包是旧的代码改了不生效IDEA预览的404还有一种特殊场景代码改了重启Tomcat后依然404。原因是部署的时候Deployment里配置的是war包模式IDEA没把最新的class文件打包进去或者打包到了out目录但Tomcat使用的还是原来的临时文件。遇到这种情况别慌按顺序做Build→Rebuild Project把target或out目录下残存的旧包删干净在Deployment配置里换成war exploded模式这种模式直接加载编译输出目录不依赖打包war exploded在调试阶段最实用因为修改静态资源或JSP后不需要重启Tomcat刷新页面就能看到效果而且不会出现“代码改了但404没变”的尴尬情况。5.3 Tomcat的CATALINA_BASE指向混乱IDEA启动Tomcat时不会直接用你下载解压的那个Tomcat而是复制一份配置生成一个临时实例放在当前项目的./.idea相关的目录或者系统的临时目录下。它的CATALINA_BASE指向的是临时实例CATALINA_HOME指向原始Tomcat。问题就出在这里有些人在原始Tomcat的webapps目录下放了war包因为服务器上就是这么干的但IDEA启动的临时实例的webapps目录是空的应用根本没部署进去访问必404。验证方式启动后看IDEA控制台的日志搜索CATALINA_BASE然后去那个目录下检查webapps文件夹里有没有项目。没有的话说明IDEA用自己配置的部署方式管理应用不读你的webapps目录。6. Linux服务器上部署Tomcat常见4046.1 端口没起来还硬访问Linux上tomcat跑起来了ps -ef | grep tomcat能查到进程但访问404注意有时候是连接被拒绝有时候是404两者不同。我遇到最常见的情况是Tomcat启动“看起来很成功”实际端口没监听上。最典型的场景8080端口被别的进程占用了Tomcat启动时日志里报了Port already in use但Tomcat没有退出因为8005关闭端口还能用或者8009端口没问题。这时候进程还在但HTTP服务没起来访问http://IP:8080要么连接超时要么404。排查一条命令就够ss -tlnp | grep 8080如果这一行什么都没有说明Tomcat根本没监听这个端口。再去看启动日志里的异常。6.2 防火墙和后端服务冲突Linux服务器上Tomcat正常监听8080curl http://localhost:8080也正常返回页面但其他机器访问同样地址就是404。这种情况十有八九是防火墙或者云安全组策略问题。注意防火墙拦截通常表现为连不上但某些配置下比如端口转发做了但转发目标错误会出现请求能到达某个代理层代理层返回404。另外还有一种情况服务器上装了Nginx监听80端口转发规则配错了把请求转到了一个不存在的路径或者错误的后端。这种404的响应头里会有Server: nginx字样一看就知道不是Tomcat返回的。碰到这种去查Nginx的proxy_pass配置重点看有没有带/这个斜杠的有无决定了转发路径是拼接还是替换。6.3 war包解压权限和目录归属Linux下用root用户启动Tomcatwar包部署后解压出来的文件归属是root。之后切换到普通用户重启Tomcat普通用户对root创建的文件没有写权限日志文件也可能写不进去导致启动异常或者部署失败表现就是访问404。还有一个容易忽略的地方temp目录权限异常也会导致JSP编译失败JSP没编译成功访问那个JSP就404。查Tomcat的logs目录下的localhost.xxx.log里面通常能看到真实的异常栈。6.4 专项日志里到底哪里会写404很多人不会看Tomcat日志这里分享一个我的方式。Tomcat的日志目录下有这几个典型文件catalina.out主要运行日志启动、停止、报错信息都在这localhost.[日期].log应用部署时的详细日志包括Servlet映射、异常栈manager.[日期].logManager应用的操作日志access_log如果配置了记录所有HTTP访问请求排查404最有用的是启用access_log它会记录资源的实际访问状态码。在server.xml的Host节点下加Valve classNameorg.apache.catalina.valves.AccessLogValve directorylogs prefixlocalhost_access_log suffix.txt pattern%h %l %u %t %r %s %b %{Referer}i %{User-Agent}i /然后重启Tomcat访问一次404的URL去logs/localhost_access_log.*.txt里看最后一行。%s就是HTTP状态码%r是请求方法和路径。配合localhost.[日期].log里的异常栈基本能定位到是Servlet没映射、Filter挡了还是静态资源路径不对。7. 高频404场景速查与常规解法我把这些年实际遇到的高频404场景整理成了一张表每一行对应的都是真实经历过的案例可以直接对照场景典型表现主要原因快速解法刚部署war包访问根路径404直接访问/没有响应访问/项目名/正常上下文路径没算进去URL加上项目名或将war改名为ROOT.war页面能开CSS/JS全部404控制台刷资源加载失败框架拦截了静态资源配置静态资源放行如Spring MVC的mvc:resources用户上传的图片访问404部分图片能打开部分打不开中文文件名未编码、路径拼接错误统一文件名规则使用UUID命名检查URL编码IDEA里启动后直接404控制台显示Tomcat已启动但访问任何页面都404Application context配置错了或部署方式不对修改Run Configuration里的Deployment配置核对上下文路径修改代码后重启依旧404明明改了Controller但访问还是404打的包是旧的或classes未编译Rebuild Project改用war exploded模式服务器重启后Tomcat正常但访问404进程在但访问不了任何页面端口被其他服务占用或Tomcat没成功监听ss -tlnp确认端口监听查看启动日志Nginx代理后404直接访问Tomcat端口正常走域名404代理转发路径配置错误核对proxy_pass的/是否有歧义Spring Boot打war部署到Tomcat后全部404应用启动成功日志无异常但所有路由404启动类位置不对或Servlet初始化器缺失确保启动类在顶层包SpringBootServletInitializer已继承删除了某个接口后前端还在请求接口单独访问404其他接口正常前端代码没同步更新更新前端代码或后端保留兼容接口返回404页面之前先做兼容访问WEB-INF下的资源404文件确实在项目里存在Tomcat默认禁止外部访问WEB-INF把静态资源移到WEB-INF之外这张表覆盖了90%的日常场景。剩下的10%基本都是特殊框架配置或多层代理导致得拿着日志逐步追踪。8. 一套能复用的标准排查流程不想每次碰到404都从头猜我把整套排查思路固化成一条命令链按顺序执行绝大多数404都能在五分钟内定位。第一步先确认请求有没有到Tomcattail -f /usr/local/tomcat/logs/localhost_access_log.*.txt然后访问一次404的URL日志里如果出现请求记录说明请求到了Tomcat没有记录说明请求被上层拦了去查Nginx、防火墙、负载均衡。第二步确认请求对应的资源在文件系统里存不存在ls -l /usr/local/tomcat/webapps/项目名/如果静态资源看文件具体在哪如果动态路由看浏览器渲染后webapps/项目名/WEB-INF/classes下有没有对应的编译后class文件。这一步能排除“文件都没了”的低级问题。第三步看localhost.[当天日期].logtail -n 100 /usr/local/tomcat/logs/localhost.$(date %Y-%m-%d).log这里会有Servlet初始化、映射注册、异常信息。404之前通常能找到线索。第四步看有没有框架层面的拦截Spring MVC检查DispatcherServlet的映射配置Shiro/Spring Security检查权限拦截规则Filter是否对URL做了路径改写如果这时候还没定位到那就得祭出大杀器——开调试日志# 修改conf/logging.properties提高日志级别 org.apache.catalina.core.ContainerBase.[Catalina].level FINE将Tomcat自身日志调到FINE级别重启后请求的处理链路会完整打印出来能看到请求被哪个Servlet处理、匹配了哪个映射、为何没找到资源。最后分享一个经验很多人折腾半天最后发现只是浏览器地址栏少打了个/或者路径里多打了空格。而且有个特别反直觉的现象——在Windows的IDEA里怎么测都好的项目一丢到Linux就404多半不是Tomcat的问题要么是文件名大小写要么是路径分隔符写死成\了。9. 每次都要嘱咐一遍的排错底线处理Tomcat 404这几年我自己最大的体会是别慌别乱动配置。很多人一看到404上来就把web.xml改一通、把server.xml改一通结果原本能跑的也跑不了了。正确的做法是先记录下当前状态再做任何修改每一步动手前想清楚“我要验证什么假设”。有一次凌晨三点陪客户排查404客户一口咬定是Tomcat配置有问题坚持要我改端口、改虚拟目录。我没急着动先curl -I了一下发现响应头里写着Server: nginx/1.18.0压根不是Tomcat。最后查出Nginx配置里proxy_pass少了个斜杠一分钟改完问题解决。这就是为什么我反复强调先确认响应是谁返回的再动手改配置。还有一次Novice同事说“Tomcat可以正常运行但是报404”我上去看Tomcat首页都能开但项目里所有JSP都404。最后发现是项目结构整个乱了src/main/webapp下的文件全被误删webapps/项目名下只剩一个空的WEB-INF目录。文件都没了404是理所当然的。这种低级错误比复杂的配置错误出现频率高得多。Debug Tomcat 404本质上是一个信息问题——请求是谁处理的、资源是否存在、路由是否正确匹配到、权限是否拦截了。把这些问题回答完404一般都会主动现形。