Nginx反向代理Tomcat完全指南:从零配置到生产实践

发布时间:2026/10/16 5:56:29
Nginx反向代理Tomcat完全指南:从零配置到生产实践 说句实话Java Web应用部署到Tomcat之后我最不愿意做的就是把8080端口直接甩给用户。开发阶段自己访问无所谓一旦要上线给别人用端口、域名、会话、静态资源这些问题全挤到一起。最后几乎所有项目都收敛到了同一个方案让Nginx做反向代理前端统一走80/443端口Tomcat安静地待在后面。这篇文章就把我从零配置Nginx反向代理访问Tomcat的整个过程写清楚包括安装、配置、验证方法以及我实际部署中踩过的几个坑。1. 为什么要用Nginx来访问Tomcat先想明白这笔账1.1 直接暴露Tomcat的三个痛点很多刚接触部署的同学会觉得多此一举Tomcat本身就能处理HTTP请求为什么非要再架一层Nginx等你真正把服务暴露出去就会体会到裸奔式的8080访问在真实场景里处处难受。第一个痛点是端口不友好。Tomcat默认监听8080普通用户访问网站根本不带端口号如果非要带个:8080既难记又显得不专业。更关键的是不做代理的话防火墙就得把8080这个端口敞开暴露面也变大了。第二个痛点是并发和静态资源。Tomcat毕竟是Java应用服务器处理动态请求是它的强项但像图片、CSS、JavaScript这类静态文件它每来一个请求都要走一遍Servlet逻辑性能和Nginx差距很大。Nginx处理静态资源的效率非常高让Nginx接管这些请求Tomcat的线程就能腾出来专心跑业务。如果流量再大一点Tomcat单机的并发上限也会成为瓶颈。第三个痛点是横向扩展。当一台Tomcat扛不住的时候自然要加第二台、第三台。这些实例需要有一个统一入口做请求分发Nginx天生就是干这个的。1.2 反向代理和正向代理的区别这里要区分两个概念。正向代理是代理在客户端一侧客户端通过代理去访问它无法直接访问的外部资源最典型的例子就是企业内网统一出口。反向代理则相反它代理的是服务端客户端访问的其实是代理服务器代理再把请求转发给后面真正干活的服务。Nginx在这里扮演的反向代理角色可以理解成公司前台的接待员。访客不需要知道后勤团队在哪个工位、哪个房间只需要告诉前台找谁然后前台负责把人领到正确的部门。对外来说访客只和前台打交道这就是反向代理的核心逻辑。明白这一点后面所有的配置都围绕同一件事展开Nginx负责听门铃监听80/443Tomcat负责干活处理业务请求中间通过上下游关系串联起来。2. 请求流转链路一个HTTP请求是怎么从Nginx到达Tomcat的2.1 一次完整访问的路径拆解用户浏览器输入http://example.comDNS把域名解析到Nginx所在服务器的IP请求到达Nginx监听的80端口。Nginx根据location规则找到对应的proxy_pass配置把请求转发给Tomcat的8080端口。Tomcat处理完业务逻辑把响应原路返回给NginxNginx再返回给浏览器。整个过程中浏览器感知到的始终是Nginx这个“前台”。Tomcat就好像躲在工位后面干活的后勤人员它只和Nginx通信不和用户直接打交道。关键点在于Nginx不是把请求“跳转”过去而是“代为转发”。跳转是浏览器重新发一次请求地址栏会变转发则是Nginx把请求原封不动或者稍加修改地转给Tomcat浏览器地址栏始终是example.com。2.2 location匹配规则决定哪些请求交给TomcatNginx的location指令用来匹配请求的URI反向代理最常用的写法是这样一个块location / { proxy_pass http://127.0.0.1:8080; }location /能匹配所有未命中的请求也就是所有请求都转发给Tomcat。如果服务器上同时跑着多个应用可以用不同的路径区分比如location /app1/转发到8081端口的Tomcatlocation /api/转发到8082的Spring Boot服务。这里有一个新手容易忽略的点location后面的匹配方式和proxy_pass后面的地址写法会组合出完全不同的转发结果这个细节我专门在第四章展开讲。2.3 proxy_set_header为什么要手动设置这几个请求头Nginx默认转发请求时会带上它自己的一些信息但有几个头是必须手动设置的否则Tomcat拿到的信息是错的HostTomcat部署了虚拟主机或靠域名区分应用时必须把原始请求的Host传过去否则Tomcat看到的是127.0.0.1:8080这类内部地址。X-Real-IP客户端真实IP。不加这个头Tomcat日志里记录的永远是Nginx服务器IP。X-Forwarded-For记录完整的代理链路常用于日志分析和风控。X-Forwarded-Proto标记原始请求是HTTP还是HTTPS做重定向时特别有用。一个标准的反代配置片段长这样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; }可以这么理解这几个头相当于快递单上的关键字段。收件人、寄件人、物品信息不全Tomcat拿到请求也不知道该怎么处理。配置好之后Tomcat看到的请求就像用户直接访问它一样。3. 环境准备安装Nginx与Tomcat时容易埋雷的细节3.1 安装Nginx我用的是Ubuntu系统一条命令就能装apt update apt install nginx -yCentOS/RHEL系列则需要注意先启用EPEL源yum install epel-release -y yum install nginx -y装完先确认版本和运行状态nginx -v systemctl start nginx systemctl enable nginx很多人装完Nginx就急着写配置结果配了半天不生效。其实装完第一件事应该先访问一下服务器IP能看到Nginx默认欢迎页就说明基础层没问题。如果连欢迎页都打不开后面配置反代也无从谈起。3.2 安装TomcatTomcat不像Nginx那样靠系统包管理器装通常直接下载二进制压缩包解压使用。先去官网下载Tomcat对应版本的tar.gz包解压到指定目录然后设置环境变量export JAVA_HOME/usr/lib/jvm/java-11-openjdk-amd64 export CATALINA_HOME/opt/tomcat启动和停止用官方脚本/opt/tomcat/bin/startup.sh /opt/tomcat/bin/shutdown.sh这里有一个我踩过多次的坑启动Tomcat要看日志而不是看控制台输出。Tomcat的启动日志在logs/catalina.out如果启动失败控制台往往只打一堆Java异常堆栈的摘要真正的原因要靠日志文件来定位。比如端口被占用、JAVA_HOME没设对都会导致启动脚本走了一半就退出。验证Tomcat是否启动成功curl -I http://127.0.0.1:8080如果有一个正常的HTTP响应头返回说明Tomcat已经就绪。3.3 防火墙、SELinux和云安全组这是配置过程中最隐蔽的拦路虎。Nginx监听80端口如果系统防火墙没放行外部请求根本到达不了Nginx。以firewalld为例firewall-cmd --permanent --add-port80/tcp firewall-cmd --reload如果是云服务器除了系统防火墙还要检查云控制台的安全组规则确保80和443端口在入站规则里放行。更隐蔽的是SELinux。CentOS系统默认开启SELinux它会限制Nginx这样的进程去发起网络连接。你配置全对、防火墙也放行了Nginx依然报502。解决办法是放行Nginx的网络连接权限setsebool -P httpd_can_network_connect 1这个坑特别经典我在第五章的故障排查里会详细讲怎么定位。4. 最小可用配置proxy_pass斜杠语义与请求头透传4.1 nginx.conf的基本结构Nginx的主配置文件是/etc/nginx/nginx.conf核心结构分四层events块配置连接处理比如worker_connections。http块配置全局HTTP参数包含日志、压缩、超时等。server块对应一个虚拟主机通过listen和server_name区分。location块定义URI匹配规则及处理方式。初学者最容易犯的错误是在http块里看到一条指令下不去手也不敢改。建议先不去动全局配置所有的反代逻辑都写在server和location里全局参数按默认来跑通之后再去优化。4.2 最小反向代理配置新建一个配置文件比如/etc/nginx/conf.d/tomcat-proxy.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; } }proxy_pass http://127.0.0.1:8080;的意思是把当前location匹配到的请求转发到本机的8080端口。这个配置最简单也最直白。检查配置并重载nginx -t nginx -s reload只要第一次配置就通过nginx -t说明语法没问题。接着用浏览器访问http://example.com如果能看到Tomcat默认页面说明反代已经通了。4.3 proxy_pass末尾斜杠的命运分歧这是Nginx配置里最值得细品的一个细节没有实际配过的人很难理解它的微妙之处。看这张表location 规则proxy_pass 写法用户访问Tomcat实际收到的路径location /app/proxy_pass http://127.0.0.1:8080;/app/login/app/loginlocation /app/proxy_pass http://127.0.0.1:8080/;/app/login/loginlocation /proxy_pass http://127.0.0.1:8080;/login/loginlocation /proxy_pass http://127.0.0.1:8080/;/login/login规律很简单proxy_pass末尾带不带斜杠决定了location匹配到的路径要不要保留。不带斜杠完整URI原样转发到后端。带斜杠location匹配到的部分会被“剪掉”用斜杠后面的路径替换。所以如果应用部署在Tomcat的根路径ROOT用location /最省心斜杠带不带都无所谓反正/本身就被剪成空了。如果Tomcat下部署了多个项目用location /project/配合不带斜杠的proxy_pass能保持路径结构一致。我在实际项目里遇到过好几次“首页打得开一登录就404”的情况最后查下来全是这个斜杠导致的路径错位。配置的时候脑子里一定要过一遍后端希望接收的是什么路径4.4 为什么要透传Host和客户端IP配置里那几行proxy_set_header不是装饰品。Tomcat的应用如果基于域名做虚拟主机比如同一个Tomcat上部署了多个站点它会依赖Host来判断该把请求路由到哪个应用。没有透传HostTomcat会默认按IP地址走路由就乱了。客户端IP更是必需品。用户登录失败、越权操作、日志审计这些都需要真实的客户端IP。如果不设置X-Real-IPTomcat日志里记录的永远是127.0.0.1出了问题排查都不知道谁干的。5. 高频故障排查从502到404再到会话丢失5.1 502 Bad Gateway完整排查链路配完反代第一次访问就遇到502这是最常见的情况。完整的排查思路应该是这样的不要跳到结论。第一步查看Nginx错误日志。tail -f /var/log/nginx/error.log日志里如果出现connect() failed (111: Connection refused) while connecting to upstream说明Nginx根本连不上Tomcat。第二步确认Tomcat是不是还活着。ss -tnlp | grep 8080如果没有任何输出说明Tomcat挂了。再查进程ps aux | grep tomcat常见原因是Tomcat没启动或者启动失败。看/opt/tomcat/logs/catalina.out确认失败原因。第三步确认Tomcat端口真的可访问。curl -I http://127.0.0.1:8080如果返回正常头Tomcat没问题如果connection refused那就是Tomcat自身的问题和Nginx配置无关。第四步如果是CentOS系统检查SELinux。getenforce返回Enforcing时先试用排查命令setsebool -P httpd_can_network_connect 1这个操作放行Nginx向上游发起网络连接的权限对反向代理场景几乎是必需的。曾经有一个生产环境的服务Nginx日志一直报无权限连接排查了很久才想到是SELinux在拦关掉后立刻恢复。上面的步骤走完502基本能解决。5.2 页面能打开但登录接口404斜杠与上下文路径的纠缠502解决之后可能又碰到另一种情况首页正常打开但点击登录后请求404。这时候先看浏览器地址栏和Network面板。如果请求路径是/login但Nginx转发后变成了/login/或者/app/login路径和Tomcat里Servlet的映射对不上就会出现404。这种问题绝大多数出在proxy_pass和location的斜杠组合上。按照第四章的表格理一遍以Tomcat的ROOT部署为例正确写法是location / { proxy_pass http://127.0.0.1:8080; }如果部署的是带上下文路径的项目比如/myapp那么location /myapp/ { proxy_pass http://127.0.0.1:8080; }这里proxy_pass不要带末尾斜杠这样/myapp/login会原样转发到http://127.0.0.1:8080/myapp/loginTomcat能正确识别上下文路径。排查这类问题有个技巧直接看Tomcat的access log。tail -f /opt/tomcat/logs/localhost_access_log.*.txtTomcat实际收到了什么路径一目了然。对比一下配置前的预期路径就能很快定位是哪一层路径被改写了。5.3 登录后一直掉会话Host头与Cookie域名的坑还有一种很恼人的故障接口全部正常但登录成功之后刷新页面就变回未登录状态。用户每次操作都被重定向到登录页。原因通常出在Cookie上。Tomcat为用户生成会话CookieJSESSIONID时会根据请求的Host头生成Cookie的Domain属性。如果Nginx没有透传HostTomcat生成的Cookie域名可能是127.0.0.1浏览器拿到之后要么拒绝写入要么下次请求根本不带这个Cookie。解决办法就是在proxy_set_header Host $host;。加上这个头之后Tomcat感知到的是用户在浏览器里输入的真实域名生成的Cookie就能正常种到浏览器里。如果配置了多个Tomcat实例做负载均衡还会遇到一个衍生问题用户第一次请求落在Tomcat A会话建在A上第二次请求被Nginx转发到了Tomcat BB没有这个会话又把用户踢回登录页。这种情况可以在upstream里暂时用ip_hash做会话保持保证同一个IP的请求总是落到同一台后端upstream tomcat_cluster { ip_hash; server 127.0.0.1:8080; server 192.168.1.101:8080; }不过ip_hash只是过渡方案等会话共享方案比如Redis集中管理Session成熟后还是要去掉它让负载均衡更纯粹一点。5.4 Tomcat重定向跳回8080端口proxyName与proxyPort这个坑特别隐蔽而且一旦踩了用户感知特别差。场景是这样的用户访问http://example.com/loginNginx转发给TomcatTomcat处理完返回一个302重定向让浏览器跳转到http://example.com:8080/dashboard。浏览器收到这个重定向地址直接把端口加上去了用户看到地址栏变成8080如果8080端口没对外放开还会直接打不开。为什么Tomcat会返回带8080的地址因为Tomcat在构造重定向URL时会读取请求里的端口信息。请求是Nginx转发的到了Tomcat这边确实是8080端口它不知道外面还有一个Nginx挡在前面。解决办法是在Tomcat的conf/server.xml里给Connector加上代理信息Connector port8080 protocolHTTP/1.1 proxyNameexample.com proxyPort80 /这样Tomcat构造重定向时就会用example.com和80端口。如果前面还挂了HTTPS就把proxyPort改成443另外加上securetrue schemehttps让Tomcat知道原始请求是加密的。5.5 大文件上传超时proxy_read_timeout和client_max_body_size这个不算是故障但几乎每个上线项目都会碰到。默认的Nginx配置里client_max_body_size通常是1MB如果应用里有图片上传、文件导入功能超过1MB的请求会直接被Nginx拦截返回413错误。在后端接口调试正常的情况下前端却一直报413基本就是这个原因。解决方案是在location或server块里调整client_max_body_size 50m; proxy_read_timeout 300s; proxy_send_timeout 300s;proxy_read_timeout也要一起调有些接口处理大文件需要很长时间Nginx默认60秒等不到后端响应就会断开导致请求被截断。6. 生产级配置HTTPS、负载均衡与静态资源分离6.1 HTTPS卸载Nginx把加密这件事揽下来生产环境没有理由继续用裸HTTP。Nginx配置HTTPS特别方便证书申请下来之后新建一个443的server块server { listen 443 ssl; server_name example.com; ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; location / { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Forwarded-Proto $scheme; } }“HTTPS卸载”的意思是Nginx对外承担TLS加密解密工作Tomcat不需要配置证书内部仍然走HTTP。Tomcat的负担减轻了证书的续期和更新也只操作Nginx一处省事得多。HTTP强制跳转HTTPS也顺手做了server { listen 80; server_name example.com; return 301 https://$host$request_uri; }6.2 负载均衡多台Tomcat的流量分发流量上来之后单台Tomcat不够用了用upstream定义一组后端实例upstream tomcat_cluster { server 127.0.0.1:8080 weight1; server 192.168.1.101:8080 weight1; keepalive 16; } server { listen 80; server_name example.com; location / { proxy_pass http://tomcat_cluster; proxy_http_version 1.1; proxy_set_header Connection ; proxy_set_header Host $host; } }默认的负载均衡策略是轮询。加了weight参数可以调整权重让配置高的机器多承担一些请求。keepalive 16的作用是复用Nginx到Tomcat之间的TCP连接减少频繁建立连接的开销对吞吐量有明显帮助。注意proxy_http_version 1.1和空的Connection头是配合keepalive必须写的。我之前第一次配keepalive时漏了这两行结果是连接根本不复用性能没有提升还多了不少TIME_WAIT连接。6.3 静态资源分离让Nginx自己缓存静态文件Tomcat处理静态文件的效率远不如Nginx所以生产环境通常把静态资源请求直接交给Nginx不让它往后端转。最简单的做法是按目录或文件后缀匹配location ~* \.(css|js|jpg|png|gif|ico|svg)$ { root /opt/myapp/static; expires 7d; }这里要区分root和alias。root会把location的匹配路径也拼进文件路径比如请求/static/css/app.css实际查找的是/opt/myapp/static/static/css/app.css这往往会404。alias则会把匹配部分替换掉location /static/ { alias /opt/myapp/static/; }请求/static/css/app.css实际查找/opt/myapp/static/css/app.css。我的建议是不要在同一个项目里一会儿用root一会儿用alias容易搞得自己都记不清路径规则。固定一个约定比如静态资源统一放/static/目录代码里就按这个目录引用Nginx用alias对应到磁盘目录整个链路就清晰了。6.4 值得顺手打开的性能开关一下几个参数是花很少力气就能得到明显提升的配置worker_processes auto; worker_connections 2048; sendfile on; tcp_nopush on; keepalive_timeout 65; gzip on; gzip_types text/plain text/css application/javascript application/json;sendfile on让Nginx直接调内核的sendfile系统调用发送文件绕开用户态拷贝静态文件响应速度提升明显。gzip on压缩文本类资源减少传输体积。这几个开关加在http块里即可对整体性能影响很大。7. 上线后的验证方法与维护习惯7.1 配置变更的常规流程改Nginx配置最怕的是改完直接reload出问题想回退又找不到原文件。我自己的习惯是三步走第一步备份cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.bak第二步检查语法nginx -t第三步平滑重载nginx -s reload7.2 验证反向代理是否生效配置完成后几个快速验证命令# 检查Tomcat是否只被Nginx访问本机8080不应对外开放 ss -tnlp | grep 8080 # 通过Nginx访问应用 curl -I http://127.0.0.1/ # 查看响应头里的Server字段确认是Nginx curl -I http://127.0.0.1/ | grep Server最直接的方式是看浏览器里的Network面板。第一次加载后所有资源应当来自域名本身而不是:8080端口。如果地址栏出现端口号基本就是5.4节的重定向问题没处理干净。7.3 日志怎么看才算会看日志是排障的第一现场。Nginx的访问日志默认在/var/log/nginx/access.log错误日志在/var/log/nginx/error.log。在反向代理场景里我会把上游响应时间加进日志格式这样一眼就能看出瓶颈在哪一步log_format main $remote_addr - $remote_user [$time_local] $request $status $body_bytes_sent $http_referer $http_user_agent $http_x_forwarded_for $upstream_response_time $request_time;排查问题的时候先看$upstream_response_time和$request_time的差值。如果两者差距很大说明时间耗在Nginx到客户端这一段如果$upstream_response_time本身很高那问题在Tomcat或数据库调Nginx没有意义。7.4 最后补充一点维护经验反向代理配好只是开始真正考验人的是日常维护。我的体会是systemctl restart nginx能少用就少用reload可以做到不断连接地应用配置而restart会短暂中断所有流量线上操作时这是有实际影响的差别。另外Tomcat的server.xml里的端口号、证书路径、上下文路径这些关键信息最好写一份简单的部署文档放在配置文件旁边。配置Nginx反向代理不是背指令而是理解请求这条链路每一跳在干什么。把斜杠语义、请求头透传、超时和缓冲这些点吃透再遇到各种诡异故障排错的思路就会清晰很多。希望这篇文章里的踩坑记录能帮你少走几步弯路。