我第一次练手Java Web时,是直接把Tomcat的8080端口扔给浏览器的,后来才明白:真实项目里,站在用户和Tomcat之间的,永远还有一个Nginx。这个“练习-部署nginx和部署tomcat”的完整笔记,就是把自己从“能启动一个Tomcat”带到“能像生产环境那样,用Nginx负责入口、Tomcat负责Java业务”的整个过程。如果你和我一样,已经会用IDEA启动一个Spring Boot工程,或者刚学完Servlet,但脑子里对“下载nginx之后里面是什么”“location配置到底怎么生效”“静态资源要不要塞进Tomcat”这些事完全没有概念,这篇文章应该能帮你把整条链路理顺。内容覆盖Tomcat下载安装与多实例端口改造、IDEA 2026里找不到Tomcat Server的坑、Nginx下载与location工作流机制、多站点自定义域名、并发与日志排查、Docker和K8s部署,最后加一个给Ollama加反向代理鉴权的热门玩法。
1. 把nginx和tomcat放一起,到底练什么?
1.1 这套组合在生产环境的真实分工
先想清楚一个问题:为什么生产环境里很少让Tomcat直接面对用户?
Tomcat是Servlet容器,它的本职是跑Java的Web应用,处理JSP、Servlet、Spring MVC那一套动态逻辑。但它的静态资源处理能力、抗并发连接能力,并不是强项。Nginx恰恰相反,它是高性能的Web服务器和反向代理,处理静态文件、HTTP连接、负载均衡、SSL终结都非常利索。
正常情况下,一次请求的链路是这样:
用户浏览器请求到达Nginx(80或443端口) -> Nginx判断请求是静态还是动态 -> 静态文件(css、js、图片)由Nginx直接返回 -> 动态请求通过proxy_pass转发给后端Tomcat(8080端口) -> Tomcat处理完返回给Nginx -> Nginx再回给用户。
这套分工看着简单,但它解决了很多实际问题:Tomcat不用再纠结静态资源缓存和并发连接,可以把线程池全留给业务逻辑;Nginx可以同时代理多台Tomcat,做到负载均衡;在Nginx层还可以统一做HTTPS证书、限流、鉴权。所以,“部署nginx和部署tomcat”这个练习练的不是“把两个软件装起来”,而是练“一台入口代理 + 一台业务容器的协作关系”。
1.2 练习目标规划:从单机到多站点
我把这个练习拆成四个阶段,每一个阶段都是后面一步的基础:
- 单机阶段:Tomcat能独立启动并访问,Nginx能独立启动并访问。
- 串联阶段:Nginx把请求反向代理给Tomcat,用户只访问Nginx,但页面是Tomcat出来的。
- 扩展阶段:在Nginx里配置多个server块,用不同端口或不同自定义域名区分站点,做到一个Nginx管理多个Tomcat。
- 排错与进阶阶段:解决HTTPS证书报错、并发“老是用超”、日志定位、容器化部署,以及给大模型服务Ollama加反向代理鉴权。
这个练习不需要昂贵的生产服务器,一台Windows笔记本、一台Mac,或者一台Linux虚拟机就够了。Tomcat和Nginx都是解压即用的软件,唯一需要提前装好的依赖就是JDK。
2. 先搞定tomcat:版本坑、安装步骤、多实例端口改造
2.1 版本选择:不要见到新版本就上
Tomcat的版本选择是第一个坑。很多人一上来就下最新版,结果项目跑不起来。Tomcat 10之后有个巨大的变化:Java EE的包名从javax.*换成了jakarta.*,老项目如果用的还是javax.servlet.*,丢进Tomcat 10里直接编译报错。
我的建议是:如果JDK是8,项目是传统的Servlet或Spring MVC,直接选Tomcat 8.5系列;如果JDK是11或17,恰好项目也是新写的,用Tomcat 9更稳。Tomcat 10类的版本留给真正做过包名迁移的团队去考虑。
| 版本 | Servlet规范 | 包名 | 适合场景 |
|---|---|---|---|
| Tomcat 8.5 | Servlet 3.1 | javax.* | JDK8老项目,最稳 |
| Tomcat 9 | Servlet 4.0 | javax.* | JDK8/JDK11兼容性好 |
| Tomcat 10/11 | Servlet 5.0/6.0 | jakarta.* | 新项目或已迁移项目 |
下载地址在tomcat.apache.org,找到对应版本的Core下的zip或tar.gz即可。记住,不要下src包和deployer包,那是源码和打包工具,普通部署用不到。
2.2 单实例部署与验证
安装前先确认一件事:有没有配好JAVA_HOME环境变量。Tomcat启动脚本是靠JAVA_HOME去找Java的,不是靠PATH。Windows下可以在命令行跑一句echo %JAVA_HOME%,Linux下是echo $JAVA_HOME,如果输出为空,先去安装JDK并配置环境变量。
Tomcat解压后,目录结构有几个关键点需要认识:
bin:启动脚本,Windows下是startup.bat,Linux下是startup.sh。conf:核心配置文件,重点是server.xml和web.xml。webapps:部署Web应用的地方,war包扔进去会自动解压。logs:运行日志,排错主要看这里。
Windows下进入bin目录双击startup.bat,会弹出一个命令行窗口并显示Tomcat的启动日志。启动成功后,浏览器访问http://localhost:8080,看到那只猫的页面就算通了。
这里我多说一句:很多人会遇到Tomcat启动出现一闪而过的情况,也就是双击startup.bat后窗口瞬间关闭。这不是正常现象,说明启动失败了。正确排查姿势是打开cmd,进入bin目录,手动执行:
catalina.bat run这样错误信息会留在控制台里,不会闪退。最常遇到的三个原因:一是没配JAVA_HOME,二是8080端口被占用(比如IDEA里某个服务还开着),三是CATALINA_HOME环境变量指错了目录。
2.3 多实例部署的三个端口改造
练习到后面,你会发现一个Tomcat不够用:两个项目想同时跑,又不想挤在同一个Tomcat里。Tomcat支持多实例,做法听起来很粗暴——再复制一份Tomcat目录,改几个端口。
复制一份之后千万不能直接启动,否则肯定报端口冲突。一个Tomcat实例在网络上有三个端口的配置集中在conf/server.xml:
| 端口 | 作用 | 默认值 |
|---|---|---|
| 8005 | shutdown关闭命令监听端口 | 8005 |
| 8080 | HTTP连接器,浏览器访问入口 | 8080 |
| 8009 | AJP 1.3协议连接端口 | 8009 |
修改规则很简单:三个端口都要和第一个实例错开。比如第一个实例是8005/8080/8009,第二个实例可以改成8006/8081/8010,第三个改成8007/8082/8011。
为什么要改三个而不是只改8080?如果只改8080,两个实例的8005端口就冲突了,而且8009端口也冲突。万一不小心用shutdown脚本关一个,另一个也会被误伤。多实例的正确玩法就是彻底隔离端口。改完之后,分别启动,访问http://localhost:8081和http://localhost:8082,对应不同的实例。
2.4 IDEA 2026找不到Tomcat Server的排查与解决
这个坑来自一个很热门的搜索词:IDEA 2026本地部署Tomcat9没找到Tomcat Server。很多人从官网下了Tomcat,想在IDEA的Run Configuration里直接加一个Tomcat Server运行项,结果发现找不到这个选项。
第一类原因最扎心:IDEA的Community社区版根本没有Tomcat内建集成,只有Ultimate旗舰版才有。如果你用的是社区版,与其找替代方案,不如直接装一个官方推荐的Smart Tomcat插件,插件市场里搜“Smart Tomcat”,安装后重启,在Run Configuration里就能看到Smart Tomcat选项,配置里指定Tomcat路径和工作目录即可。
第二类原因是你确实是旗舰版,但忘记先配置应用服务器。正确做法是:
- 打开
Settings -> Build, Execution, Deployment -> Application Servers。 - 点加号,选择
Tomcat Server。 - 在
Tomcat Home那一栏填入Tomcat目录路径,IDEA会自动识别版本号。 - 确认后,在
Run Configuration里就有Tomcat Server -> Local选项了。
第三类原因是新版IDEA对Tomcat的入口做了调整,如果你在应用服务器里配好了还是看不到,检查IDEA版本升级后插件是否被禁用。另外有一个细节:部署时选war包或war exploded时要指定Application context,这些都会影响访问路径。
3. nginx部署开跑:下载启动和location工作流机制
3.1 下载与启动(Windows和Linux的差异)
Nginx的官方下载地址是nginx.org,打开首页的download链接就能看到版本列表。我们练习就用主线稳定版即可,比如1.24或1.26系列。Windows下直接下载nginx-1.x.x.zip,解压出来就能用,不需要安装。这里顺便回答一个搜索词:nginx是不是免费的?是,它和Tomcat一样免费开源,放心用。
Nginx目录结构不大,核心是这几个:
conf/nginx.conf:主配置文件。html:默认静态页面存放目录。logs:访问日志和错误日志默认目录。temp:运行时临时文件。
Windows下的启动方式和Tomcat不太一样。进入解压目录,命令行执行:
start nginx这句会启动一个后台进程。之后要验证状态,访问http://localhost,看到“Welcome to nginx!”页面就是成功了。注意Nginx默认监听80端口,80被占用会导致启动失败,最常见的占用者是IIS或另一个Nginx。
停止和重载的命令分别是:
nginx -s stop nginx -s reloadstop是快速停止,reload是平滑重载。为什么改配置文件后大家都推荐用reload?因为它是主进程读取新配置、重新拉起worker进程,已建立的连接不会被中断,用户体验上是无感的。这在生产环境里是一个特别重要的习惯。
Linux下安装更简单,Ubuntu/Debian系跑apt install nginx,CentOS/RHEL系跑yum install nginx。作为练习,系统包安装足够了。想自己编译安装的话,就是从官网拿源码,跑./configure --prefix=/usr/local/nginx && make && make install,生产环境脚本化部署时更常见。
3.2 location匹配规则与优先级
Nginx配置里最容易被绕晕的就是location,但我可以用一句话先破题:location匹配的总体逻辑是“先精确,再前缀,再正则,谁匹配最长用谁”。
先看常见配置长什么样:
location / { proxy_pass http://127.0.0.1:8080; } location = / { root html; index index.html; } location ^~ /api/ { proxy_pass http://127.0.0.1:8081; } location ~ \.jsp$ { proxy_pass http://127.0.0.1:8080; }每条规则前面的符号代表了不同的匹配方式:
=表示精确匹配。请求URI必须和路径完全一致才会命中,命中后立即使用,不再查后面的规则。- 不带符号的普通前缀匹配。比如
location /,它匹配所有以/开头的URI。多个普通前缀匹配时,采用最长匹配原则,比如/api/user会优先匹配/api/而不是/。 ^~表示前缀匹配但一旦命中就不再查正则。它比普通前缀高一级,相当于“我就要这个前缀,后面不用折腾了”。~和~*表示正则匹配,前者区分大小写,后者不区分。正则按配置顺序从上往下执行,第一个匹配成功的生效。
所以完整的工作流是这样的:先找有没有=精确匹配,有就直接用;没有就找最长普通前缀匹配(包括^~),如果最长前缀是^~开头的,直接定案;如果最长前缀只是普通前缀,还会回头去按顺序跑正则,一旦正则匹配成功,正则胜出;如果正则全部没匹配上,才用之前那个最长普通前缀。
这个机制必须吃透,因为后面所有反向代理、动态静态分流都靠它。举个真实的例子:你配置了location /把全部请求转发给Tomcat,又配了location ^~ /static/想接静态文件,结果因为普通前缀没有^~,请求/static/app.js还是被location /转发走了,静态资源全部404。这种问题不看规则根本猜不到原因。
3.3 动态静态分流:核心反代配置模板
理解了location机制,就可以写一份真正的反代配置。下面这个server块是练习阶段最常用的模板:
server { listen 80; server_name localhost; 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; } location ~* \.(css|js|png|jpg|jpeg|gif|ico)$ { root html; expires 7d; } }第一段把动态请求全转发给8080端口的Tomcat。第二段用正则匹配静态文件后缀,由Nginx直接从html目录返回,不经过Tomcat。expires 7d是让浏览器缓存静态文件7天,这是Nginx处理静态资源的一大优势。
里面的proxy_set_header很多人会漏写,但每个都很关键。Host $host会把原始请求的Host头传给后端Tomcat,如果不传,Tomcat收到的Host可能是127.0.0.1:8080,那样它在生成重定向链接时就会返回一个错误的地址。X-Real-IP和X-Forwarded-For是告诉后端“真实的用户IP是谁”,这样应用日志里记的不是Nginx的IP,而是访问者的IP。
改完配置文件,一定先跑一句:
nginx -t它会检查配置文件语法,显示test is successful后再执行nginx -s reload。这个习惯能帮你少踩很多坑。
4. 多端口、多站点、自定义域名:server块实战
4.1 hosts绑定自定义域名
本地练习时没有真实域名,但我们可以让操作系统“骗”自己:用hosts文件把自定义域名指向本机IP。Windows的hosts路径是C:\Windows\System32\drivers\etc\hosts,Linux/Mac是/etc/hosts,用管理员权限或sudo编辑,加两行:
127.0.0.1 app1.test.local 127.0.0.1 app2.test.local保存后,ping一下app1.test.local,如果解析到了127.0.0.1就成功。这里不要用localhost本身,因为多站点场景下Nginx需要通过server_name匹配域名,如果所有站点都用localhost,就分不清到底该访问哪一个。
一句话解释为什么Nginx能靠域名区分站点:用户在浏览器地址栏输入域名后,浏览器会把这个域名放在HTTP请求的Host头里发给Nginx,Nginx拿Host和server_name做比对,命中哪个server块就进入哪个块。这就是虚拟主机的基本原理。
4.2 多站点配置示例与宝塔面板端口修改
假设我们有两个Tomcat实例,一个跑订单系统,一个跑后台系统。在Nginx的conf/nginx.conf的http块里,可以并列写两个server:
server { listen 80; server_name app1.test.local; location / { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } server { listen 80; server_name app2.test.local; location / { proxy_pass http://127.0.0.1:8082; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }保存后nginx -t再nginx -s reload,浏览器分别访问http://app1.test.local和http://app2.test.local,你会发现虽然端口都是80,但Nginx已经根据域名把请求分到不同的Tomcat上了。
除了按域名区分,还可以按端口区分。把两个server块的listen分别改成8081和8082,server_name用相同的域名也行。这正好就是搜索热词里“多端口nginx 开发环境多站点自定义域名配置”的场景:一个Nginx,多个端口,多个域名,对应多个后端。
如果你用的是宝塔面板这类服务器管理面板,要注意区分“面板端口”和“Nginx站点端口”。宝塔面板自身的默认访问端口是8888,这和Nginx服务本身没关系。想在宝塔里把某个Nginx站点改成非80端口,入口是“站点设置 -> 配置文件”,找到listen 80改成listen 8080,保存后到软件商店里的Nginx管理里点重载。另外,宝塔面板自身的8888端口要改的话,是在面板设置里改,不是改Nginx配置,两件事别混在一起。
4.3 虚拟机场景的访问链路
很多人会把练习环境搭在虚拟机上,比如Windows本机装一个VMware,虚拟机里跑Linux和Nginx。这时访问链路会多一层网络问题。
虚拟机网络模式建议直接用NAT模式,然后在VMware的设置里把宿主机的某个端口映射到虚拟机的80端口。比如设置宿主机8080端口转发到虚拟机80端口,这样本机浏览器访问http://localhost:8080就能打到虚拟机的Nginx。
如果不想做端口转发,更直接的办法是让虚拟机和宿主机在同一网段,也就是桥接模式,虚拟机有独立的局域网IP,例如192.168.1.100。然后修改Windows的hosts:
192.168.1.100 dev.test.localNginx里的server_name配成dev.test.local,这样浏览器访问http://dev.test.local时,请求会发到虚拟机的Nginx,Nginx再根据规则把请求转发给虚拟机内的Tomcat,或者转发给宿主机上跑的Tomcat(前提是两个环境网络互通)。
我用这种模式练习过,最常踩的坑是防火墙。虚拟机Linux里如果没开放80端口,或者Windows防火墙禁了入站连接,浏览器就会一直转圈或提示拒绝连接。所以配置完Nginx后,先做连通性测试,用curl -I http://localhost在虚拟机里确认Nginx本身正常工作,再用宿主机访问虚拟机的IP,一层层排查,很快就能定位是防火墙还是Nginx配置的问题。
5. 反代高频故障排查:https证书报错与并发/超时问题
5.1 net::err_cert_common_name_invalid的完整排查链路
搜索热词里有一个非常具体的报错:net::ERR_CERT_COMMON_NAME_INVALID。这个错误几乎都出现在HTTPS反向代理场景里。
现象是:访问Nginx的HTTPS地址时,浏览器提示“服务器证书与此站点不匹配”之类的警告。根本原因是Nginx上使用的SSL证书里包含的域名(CN或SAN),和你实际访问的域名对不上。
举个例子:证书是给a.example.com签发的,你却在访问b.example.com,Nginx虽然能把SSL握手做完,但浏览器一比对证书里的域名和地址栏域名,发现不一致,就直接拦下来了。
排查链路建议按这套来:
- 确认实际访问的域名是什么,确认Nginx配置里
server_name是否就是访问的域名。 - 检查证书文件本身签发给了哪些域名,用命令:
openssl x509 -in your_cert.pem -noout -text | grep -E "Subject:|DNS:"输出里的DNS:后面的列表就是证书绑定的域名清单。 3. 如果名字不匹配,要么申请新证书,要么把访问域名改成证书里存在的域名。 4. 如果本地练习没有正式证书,自签证书一定要让客户端信任它,用mkcert这类工具生成一个本机CA并导入系统信任区,比手动导入一年自己签的.p12省心得多。
顺带说一句HSTS的问题。如果你之前访问过这个域名,浏览器强制HTTPS了,即使你后面把证书换成匹配的也可能缓存了旧状态。处理办法是在开发阶段不启用HSTS,或者用无痕窗口测试。
5.2 最大并发链接数到底怎么算
“nginx最大并发链接数老是用超”这个搜索词挺有意思,很多人压测到某个临界点后,Nginx就开始报错,但不知道瓶颈在哪。
Nginx能处理的连接数理论上由两个参数决定:
worker_processes 4; # 多少个worker进程 worker_connections 1024; # 每个worker能打开的连接数上限最大连接数约为worker_processes * worker_connections。但这里有个细节:HTTP请求到来时,一个客户端连接会占掉Nginx侧一个连接;如果是反向代理,Nginx还要向后端Tomcat建立一个连接,所以一层代理场景下,实际能扛住的客户端连接数,大约是worker_processes * worker_connections / 2。如果打开了keep-alive长连接,连接长时间不释放,实际可用连接数会更紧张。
提高并发上限,有三个层面要做:
- Nginx配置层:把
worker_processes设为auto,让Nginx按CPU核心数自动分配;worker_connections适当提高到2048或4096。 - 操作系统层:Nginx的连接数受系统文件描述符
ulimit -n限制,Linux下查看当前值用ulimit -n,临时调大到65535用ulimit -n 65535,永久修改看/etc/security/limits.conf。不调这个,Nginx配置再高也白搭。 - 后端层:Tomcat的
server.xml里maxThreads默认是200,acceptCount默认是100。如果Tomcat线程池先被耗尽,Nginx就算连接数再大,转发过去的请求也会在后端排队直至超时。
经常被忽视的一点是,别把并发算成纯Nginx的事,整条链路的短板是Tomcat。Nginx可以在连接层扛住海量请求,但Tomcat处理不过来时,Nginx的日志里就会大量出现upstream timed out或connection refused。
5.3 “并发老是用超”和代理超时怎么调理
“老是用超”这个描述,通常是压测脚本里报连接超时或者请求超时。我会把现象和原因放在一起看:
- Nginx的error.log里出现
connect() failed (111: Connection refused) while connecting to upstream,说明Nginx和后端Tomcat建立连接时被拒绝了。先检查Tomcat还活着没,再看端口对不对。如果是压测中途出现,很可能是Tomcat的acceptCount满了,背压不过来,系统直接拒了新的TCP连接。 - error.log里出现
upstream timed out (110: Connection timed out) while reading response from upstream,说明Nginx已经连接上后端,但Tomcat在超时时间内没返回数据。这时要看的是proxy_read_timeout,默认60秒,后端接口处理时间超过60秒就会触发。
和超时相关的三个Nginx参数:
proxy_connect_timeout 5s; # 和后端建立TCP连接的超时 proxy_read_timeout 60s; # 等待后端响应体的超时 proxy_send_timeout 60s; # 向后端发送请求体的超时我的经验是:connect超时不能调太大,本地或者内网环境超过5秒还不通,基本就是网络或端口有问题,调大只会掩盖故障。read超时则要根据业务接口的最慢耗时来定,比如接口要跑一分半钟,60秒就得调到120秒。但比调参更重要的是找到为什么这么慢。有一次压测一直报超时,我翻了一下午Nginx日志,最后还是从Tomcat的catalina.out里看到是数据库连接池被打满,慢SQL把线程拖死了。Nginx只是把问题暴露出来的那个人,它不是病根。
6. Windows下查看nginx访问日志:工具与姿势
6.1 日志文件格式和理解
在Windows上做练习时,Nginx日志默认生成在解压目录的logs文件夹里,两个文件最重要:access.log负责记录每一次请求,error.log负责记录报错。
Nginx默认的日志格式是combined,一条访问日志长这样:
127.0.0.1 - - [14/Jan/2026:10:23:45 +0800] "GET /api/order HTTP/1.1" 200 198 "http://app1.test.local/" "Mozilla/5.0 ..."拆开看就是:客户端IP、用户标识(通常是-)、请求时间、请求方法、请求URI和协议版本、状态码、响应体大小、来源页面、用户浏览器User-Agent。查问题时先看状态码段,500开头的服务端问题,404是路径问题,499是客户端提前断开。
error.log可以设置级别,生产里一般用error级别,排查疑难问题可以临时调到debug,但Nginx官方文档也提醒过,debug级别日志量巨大,只能局部开启,别长期开着。
6.2 靠谱的查看工具
Windows没有Linux下那么好用的tail -f,但有几种替代玩法都亲测可用:
- PowerShell自带实时跟踪,效果和Linux tail几乎一样:
Get-Content logs\access.log -Tail 50 -Wait加-Wait之后,新产生的日志行会实时刷出来。我压测时基本就靠它盯error.log,同时开两个PowerShell窗口,一个盯错误,一个盯访问。
- Notepad++这类编辑器打开大文件会卡,不建议用来调试实时日志;如果只是翻历史日志,可以用LogExpert,它对GB级日志文件加载速度不错,还能按列排序。
- 想做统计报表,推荐GoAccess。它有Windows版本,直接生成HTML报告:
goaccess logs\access.log --log-format=COMBINED -o report.html打开后能看到请求IP排名、状态码分布、访问时间热力图,比肉眼一个一个翻快得多。
排查问题时我会用一条命令快速筛选500状态码:
Select-String -Path logs\access.log -Pattern " 500 "看到的IP和时间点再回头配合error.log和Tomcat的日志对时间轴,基本能把问题锁定在五分钟内。
7. 容器化部署:docker镜像与k8s套路
7.1 用tomcat:8.5-jdk8-corretto跑一个容器
容器化已经是部署的主流形态,练习里加一步Docker很值。Docker Hub上Tomcat官方镜像的tag非常多,热词里提到的tomcat:8.5-jdk8-corretto就是一个很经典的选择。
为什么要推荐它而不是tomcat:latest?因为latest标签的镜像基础JDK版本可能已经变成17甚至21,老项目在JDK8下编译的代码跑在新JDK上很可能出问题。tomcat:8.5-jdk8-corretto意思是Tomcat 8.5版本,配的是Amazom Corretto JDK 8。Corretto是JDK 8的一个长期支持发行版,兼容性很稳。
跑起来只需要两步:
docker pull tomcat:8.5-jdk8-corretto docker run -d --name tomcat-demo -p 8080:8080 tomcat:8.5-jdk8-corretto-d是后台运行,-p 8080:8080把宿主机的8080端口映射到容器内的8080端口。容器起来后,用docker exec -it tomcat-demo bash进入容器,会看到熟悉的Tomcat目录结构。
有个细节容易踩坑:这个镜像的webapps目录里并没有默认的示例应用,所以浏览器访问8080可能看到的是404或空目录。想部署自己的war包,可以挂载volume:
docker run -d --name tomcat-demo -p 8080:8080 -v D:/projects/webapp.war:/usr/local/tomcat/webapps/webapp.war tomcat:8.5-jdk8-corretto生产环境不会用docker exec进去乱改文件,war包要么打进镜像,要么通过挂载或CI/CD流水线放进去。
7.2 docker-compose编排nginx+tomcat
如果想把Nginx和Tomcat一起用Docker跑,建议直接用docker-compose,一条命令拉起两个服务。下面是一份最小可用的docker-compose.yml:
version: '3' services: web: image: nginx:1.25 ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro backend: image: tomcat:8.5-jdk8-corretto ports: - "8080:8080"在nginx.conf里配置反向代理时,proxy_pass的目标不能写127.0.0.1:8080了,因为在同一个docker-compose网络里,两个容器之间要通过服务名通信:
location / { proxy_pass http://backend:8080; }backend这个服务名在compose启动时会被写进容器的DNS解析里,Nginx容器可以通过它找到Tomcat容器的IP地址。这个“容器间用服务名互访”的思路,和虚拟机里用IP互访是两种不同的习惯,初次接触容易迷糊,但理解了容器网络之后就顺了。
启动命令:
docker compose up -d然后用docker compose logs -f看日志。练习完成后docker compose down一键清理,非常省心。
7.3 k8s里部署nginx
Kubernetes(K8s)是容器编排层面的东西,部署Nginx是它的入门必修课。部署一个Nginx需要三层资源:Deployment管副本,Service管访问入口,Ingress管域名路由。
Deployment是核心,它声明了“我要跑几个Nginx副本”:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.25 ports: - containerPort: 80replicas: 2表示集群里会有两个Nginx Pod,负载均衡和故障转移由K8s自动调度。
有Deployment还不够,Pod的IP是漂移的,需要Service提供一个稳定的虚拟IP和DNS名字:
apiVersion: v1 kind: Service metadata: name: nginx-service spec: selector: app: nginx ports: - port: 80 targetPort: 80 type: NodePortNodePort类型的Service会在集群每个节点上开一个固定端口,外部通过节点IP:端口访问。
如果集群里有Ingress Controller,可以把域名和Service绑定:
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: nginx-ingress spec: rules: - host: app.test.local http: paths: - path: / pathType: Prefix backend: service: name: nginx-service port: number: 80这里面容易混淆的是:K8s生态里不仅有“以Deployment方式部署Nginx”,还有一个专门的Nginx Ingress Controller,后者是整个集群的流量入口。简单理解,前者是业务,后者是网关。我们练习时先用Deployment方式跑通,后续再研究Ingress Controller也不迟。
8. 进阶玩法:用nginx给ollama反代并注入apikey
8.1 为什么本地大模型服务也需要反代
最近很多人在本地跑大模型,用的工具是Ollama。Ollama默认监听的是127.0.0.1:11434,也就是只能本机访问。问题是,如果你想让局域网里的其他电脑,或者像Cherry Studio这类客户端来访问,怎么办?
有些人图省事直接带参数启动:
ollama serve --host 0.0.0.0这样确实能让局域网访问了,但11434端口完全裸奔,局域网里任何人知道你的IP就能随便调你的模型,还能删模型、改配置,这就像把家门钥匙挂在门外,只差贴个“欢迎光临”的纸条。
正确做法是在前面加一层Nginx反代,由Nginx统一负责端口暴露和鉴权,Ollama继续监听本机回环地址。请求链路变成:
客户端 -> Nginx(判断Authorization头是否合法) -> 合法才转发到127.0.0.1:11434 -> Ollama
这样即使Nginx暴露在局域网里,没有正确的API Key的人也会在Nginx层直接被拦下,连Ollama的入口都摸不到。
8.2 配置与验证
在Nginx的conf/nginx.conf里加上一个server,监听11435端口(假设我们不想暴露Ollama的默认11434),转发目标指向本机的11434:
server { listen 11435; location / { if ($http_authorization != "Bearer ollama-secret-2026") { return 401; } proxy_pass http://127.0.0.1:11434; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这段配置的逻辑是:所有请求必须先带Authorization: Bearer ollama-secret-2026的请求头,不带或带错就返回401;通过后才转发给Ollama。
这里要说一句关于if的争议。Nginx社区有句名言叫“if is evil”,意思是location里的if在某些场景下行为诡异,比如和proxy_pass组合时可能出现意外跳转。但在这个简单鉴权场景里,if返回401是最直接可行的写法,只要不把if拿去处理复杂的rewrite就问题不大。
验证步骤:
- 用
nginx -t检查配置,nginx -s reload重载。 - 不带Key访问:
curl http://localhost:11435/api/tags应该收到401。 3. 带Key访问:
curl -H "Authorization: Bearer ollama-secret-2026" http://localhost:11435/api/tags能返回Ollama的模型列表。
在Cherry Studio这类客户端里,把API Base地址填成http://你的Nginx地址:11435,API Key填ollama-secret-2026,连接测试就能通过。生产环境如果要让外部网络访问,还要在这一层加上HTTPS,否则密钥和内容都是明文传输。
我做完这个练习最深的体会是:很多安全问题不需要复杂的防火墙策略,一个轻量级的反代入口配合简单的鉴权规则,就能把风险面收得很小。Nginx的价值从来不只在于“把请求转过来”,而是在转发这件事上衍生出的网关能力:鉴权、限流、证书、路由,全都可以在它这一层解决。