ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Nginx多域名多证书多服务配置详解:从虚拟主机到反向代理的完整实践

Nginx多域名多证书多服务配置详解:从虚拟主机到反向代理的完整实践 讲个真实的场景你手上有一台云服务器或者一台虚拟机上面同时跑着博客、接口服务、文件管理、还有一两个内部工具。它们监听在不同的端口每个都想用独立的域名访问每个域名都希望有对应的SSL证书而且最好统一从Nginx这一层进谁要访问就由谁转发不再给每个服务单独开端口、单独开证书。这就是典型的Nginx多域名、多证书、多服务配置需求。这套配置解决的痛点很直接第一不用记一长串IP加端口号域名简洁好记第二证书统一维护不用每个服务各自装一套HTTPS第三服务内部架构可以随意调整只要Nginx入口不变前端用户完全无感知。适合个人开发者、小团队运维也适合本地开发环境模拟多站点。我自己就是从一台裸机、三个服务、四个域名开始踩坑最后整理出一套能直接复制粘贴、改改就能用的配置方案这篇文章就是把最终版本和里面关键的坑全部摊开讲。1. 为什么要做多域名多证书多服务1.1 一台Nginx面对的真实场景先假设一个最常见的组合这也是我早期项目的拓扑一台服务器上跑一个Java写的数据接口监听在127.0.0.1:8080一个Node.js的小工具监听在127.0.0.1:3000还有一个文件上传下载服务监听在127.0.0.1:9000。这三个服务如果直接裸奔在公网上端口号完全暴露安全性和美观度都不行。于是你规划了三个域名api.example.com走Java接口tools.example.com走Node工具files.example.com走文件服务。用户访问的时候输入域名就想直接到达对应服务。Nginx的强项就在这它可以根据域名把请求路由到不同的虚拟主机再根据路径把请求转发到不同的后端端口。这整个过程对外只暴露80和443两个端口其他端口全部锁在内网或者本机回环地址上这才是正经的多服务入口方案。还有一层好处是证书统一。三个服务如果各自实现HTTPS等于要写三套TLS逻辑处理三套证书续期。但让Nginx统一接HTTPS后服务后端只需要监听普通HTTP证书和加密全部由Nginx这层完成。这样后端代码里不需要处理证书开发环境也可以直接跑HTTP减少很多不必要的复杂度。1.2 这套方案的适用人群往大了说这套方案适合四类人一是个人站长手头好几个站点服务器只有一台想省成本二是小团队开发前后端分离前端需要联调多个子域名三是运维入门者想搞清楚虚拟主机、反向代理、证书加载这几件事之间的关系四是本地开发场景想在电脑上用自定义域名访问虚拟机里的多个端口服务就像热词里提到的“本地虚拟机多端口Nginx开发环境多站点自定义域名配置”。说实话这些需求在本质上完全一样区别只是证书来源和域名解析方式。公网环境用云厂商免费证书或者Lets Encrypt本地环境用自签名证书加hosts映射。明白了这个区别后面所有配置都能一套逻辑走通。2. 配置前的总体规划目录、域名与证书2.1 先规划目录结构别急着写代码很多人上来就把所有server块堆在nginx.conf一个文件里短时间没问题等域名一多就乱了。我现在的习惯是每个域名独立一个配置文件证书统一放在一个目录里用域名做子目录隔开。推荐目录结构/etc/nginx/ ├── nginx.conf ├── conf.d/ │ ├── api.example.com.conf │ ├── tools.example.com.conf │ └── files.example.com.conf └── certs/ ├── api.example.com/ │ ├── fullchain.pem │ └── privkey.pem ├── tools.example.com/ └── files.example.com/这种结构的好处是以后要调整某个域名只动一个文件要排查问题直接看对应conf文件就可以了。main配置里用include /etc/nginx/conf.d/*.conf;把全部虚拟主机加载进来。证书目录统一也好管理备份的时候把整个certs目录拷走就行。2.2 证书从哪来、怎么放证书获取有三大主流渠道按使用场景自己选第一种云厂商免费证书。如果你用了云服务器阿里云、腾讯云这些控制台里都能申请免费证书申请的时候填要绑定域名下载时选Nginx格式。这种证书一般有效期一年到期后在控制台重新申请、替换文件、reload一下Nginx即可。优点是操作门槛低web界面点点就行。第二种Lets Encrypt的certbot。服务器上安装certbot后一条命令就能申请并自动配置证书例如certbot --nginx -d api.example.com。这方式适合喜欢自动化的场景证书续期也能用cron自动执行。需要注意申请时要求该域名已解析到这台服务器并且80端口可以被访问验证。第三种自签名证书。本地开发或者内网测试时用用openssl自己生成比如openssl req -x509 -nodes -newkey rsa:2048 \ -keyout privkey.pem \ -out fullchain.pem \ -days 365 \ -subj /CNdev.local自签名的关键是生成时把Common Name和可选的SANSubject Alternative Name填对否则浏览器会报域名不匹配也就是你经常遇到的那种NET::ERR_CERT_COMMON_NAME_INVALID。2.3 证书格式与路径核对Nginx下证书文件主要认PEM格式。申请下来以后一般会得到两个文件一个证书链fullchain.pem或叫bundle一个私钥privkey.pem或key文件。有少数情况会遇到pfx格式需要先转换openssl pkcs12 -in cert.pfx -nocerts -out privkey.pem -nodes openssl pkcs12 -in cert.pfx -clcerts -nokeys -out fullchain.pem放好文件后建议顺手做一次格式核对避免后面加载失败openssl x509 -in /etc/nginx/certs/api.example.com/fullchain.pem -noout -text | grep -A1 Subject Alternative Name这一个命令就能看到证书到底绑定了哪些域名。很多证书配了但不生效的问题就是在这个步骤发现的明明给tools.example.com配了另一张证书结果文件里写的是api.example.com的。还要注意私钥权限建议设置成600或者640属主是nginx运行用户或者root。权限过大Nginx在启动阶段会直接报Permission denied。3. 多域名虚拟主机server_name的正确用法3.1 server_name怎么匹配别再搞混优先级Nginx里区分多域名靠的就是每个server块里的server_name指令。它的职责是对应HTTP请求头里的Host字段。比如用户访问http://api.example.com请求头里带着Host: api.example.comNginx就去找有没有这个server_name找到了就用这个server块处理找不到就走默认规则。匹配优先级从高到低依次是精确匹配、通配符前缀匹配*.example.com、通配符后缀匹配example.*、正则匹配~^api\.。同一个域名的请求优先走精确匹配的server块这一点在配置多域名时非常重要。如果你写了一个server_name example.com *.example.com;然后又单独写了一个server_name api.example.com;那么访问api.example.com时会优先命中精确匹配的那个块而不是通配符那个。另外server_name是不允许出现下划线做正常域名使用的虽然Nginx允许你写server_name _;但这个下划线通常只用来作为“非正常请求”的兜底不能当真实域名访问。很多新手拿server_name local_test;去访问结果死活匹配不上就是这个问题。3.2 一定要设置default_server兜底有了多域名就一定会出现“请求的域名不在我预期列表里”的情况比如有人直接用IP访问或者你的域名到期被重新解析到别的服务。这时如果没有任何server块能匹配Nginx的行为是选取默认server块来处理。默认server块可以自己指定写法是在listen指令后面加default_server参数server { listen 80 default_server; listen 443 ssl default_server; server_name _; ssl_certificate /etc/nginx/certs/default/fullchain.pem; ssl_certificate_key /etc/nginx/certs/default/privkey.pem; return 444; }如果不主动配置default_serverNginx会拿配置文件里第一个加载的server块来兜底。那就很容易出现一个尴尬场面访问https://1.2.3.4时浏览器提示证书不匹配因为Nginx把A域名的证书返回给了没有域名的IP请求。设置default_server并返回444是一个干脆又安全的做法直接断开连接不给任何响应。3.3 本地开发环境自定义域名加到hosts说回本地开发场景。你在虚拟机里装了Nginx宿主机想用多个自定义域名访问虚拟机里的多个服务第一步是编辑宿主机hosts文件192.168.10.10 api.dev.com 192.168.10.10 tools.dev.com 192.168.10.10 files.dev.com再把虚拟机的Nginx配置成对应的server_name。这样浏览器输入http://api.dev.com就能到虚拟机Nginx再把请求转发到本地对应端口。如果需要HTTPS就用自签名证书并在浏览器里手动信任。这个流程和在公网配置几乎一致唯一区别是域名解析从DNS换成了hosts证书从正规CA换成了自签名其他全部通用。4. 多证书加载与HTTPS跳转的完整写法4.1 每个域名一套独立server块最稳的配置方式证书加载的核心是每个域名对应的443 server块里指向各自的证书文件。这句话听起来简单实际配置中却经常被写乱。来看一个标准写法server { listen 443 ssl http2; server_name api.example.com; ssl_certificate /etc/nginx/certs/api.example.com/fullchain.pem; ssl_certificate_key /etc/nginx/certs/api.example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; 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; } access_log /var/log/nginx/api.example.com.access.log; }多域名的时候把这个server块整个复制一份改掉server_name、证书路径、代理地址、日志路径。复制改配置是个好习惯比在同一个server里塞多个location去处理多个域名清晰得多。这里的核心判断依据是如果一个域名对应一个后端服务那就是一对一的server块如果一个域名下多个服务那才需要考虑location拆分这是两种不同的负载场景不要混在一起设计。4.2 HTTP自动跳转HTTPS的标准写法加了HTTPS之后旧链接访问http://api.example.com时如果不处理会让用户踩到一半加密一半不加密的尴尬。最简单可靠的跳转就是单独写一个80端口server块统一301server { listen 80; server_name api.example.com tools.example.com files.example.com; return 301 https://$host$request_uri; }注意这里可以把多个域名写在一起因为它们跳转逻辑相同而且80端口不涉及证书问题。$host取自原始请求的Host字段这样不管访问哪个域名跳转后都会保持域名不变不会串地址。如果后端服务还需要接收“用户原始请求是否是HTTPS”这个信息就要靠前面配置里的X-Forwarded-Proto $scheme头传递后端程序读到这个头就知道当前请求已经由Nginx完成HTTPS解密可以放行不用再担心重定向循环。4.3 证书替换后为什么经常不生效热词里有一条“nginx替换ssl证书不生效”这个我太有体会了。第一次换证书的时候我替换完文件愣是刷新半天都还是旧证书后来发现原因五花八门但核心就几类。第一类是Nginx根本没有reload。替换证书文件后必须执行nginx -s reload或systemctl reload nginx。很多人改完文件忘了这一步或者以为nginx会自动监听文件变化实际上不会。第二类是改了文件但改错了server块尤其在统一管理多个配置文件时可能在api的conf里改了tools的证书路径。第三类是nginx -t虽然通过了但Nginx启动时用的还是旧进程因为reload失败的情况下旧配置不会退出这时候要去看错误日志。第四类是浏览器缓存和HSTS。Chrome和Firefox对证书信息有缓存如果之前访问过某个域名并记住了HTTPS状态即使Nginx换了证书浏览器在短时间内可能还是用旧缓存。解决方式是换一个无痕窗口测试或者等缓存过期。HSTS更麻烦如果之前下发过Strict-Transport-Security头浏览器会强制HTTPS这阶段证书有问题的话会直接打不开不要被吓到先在无痕模式里排查。第五类才是真正的证书文件问题证书链不完整、私钥和证书不匹配、文件权限不对。判断方法很简单用openssl测试一遍即可openssl s_client -connect api.example.com:443 -servername api.example.com这条命令返回的信息里会明确显示证书链是否完整、证书是否在有效期内、域名是否匹配。配合nginx -t和/var/log/nginx/error.log几乎能覆盖所有证书不生效的排查方向。5. 多服务反向代理location与proxy_pass实战5.1 按子域名还是按路径区分服务怎么选多服务的暴露方式有两种主流方案一个子域名一个服务或者一个域名下不同路径对应不同服务。子域名方案的优势是证书和server块天然隔离每个服务互不影响这适合服务边界清晰的场景比如api、tools、files三兄弟。路径方案的典型场景是只有一个主域名比如example.com下/api走Java/admin走Node/files走文件服务。路径方案只需要一张证书但location规则复杂而且容易和后端自己的路由冲突比如后端本身就用/api作为接口前缀那location和proxy_pass的配合就要非常小心。我的建议是能上子域名就上子域名实在只有一个域名时才用路径拆分。子域名在证书续期和故障排查上都有天然优势你不需要在同一个server块里去猜哪条location对应哪个服务。5.2 location的匹配优先级别再凭感觉写location匹配是反向代理里最核心也最容易写错的地方。Nginx内部执行顺序按优先级从高到低是精确匹配最高然后是前缀匹配且带^~修饰符的再到正则匹配~或~*最后才是普通前缀匹配。举几个实际例子。location /health只匹配/health这一个路径location ^~ /static/匹配以/static/开头的路径而且匹配后不再检查正则location ~ \.php$会匹配以.php结尾的URL普通写法的location /api/则配广泛前缀。有一个很经典的坑是前端项目里常见的配置了location /api/ { proxy_pass http://127.0.0.1:8080; }然后又写了一个location / { proxy_pass http://127.0.0.1:3000; }用来承接纯静态页面。这时候请求/api/login会命中/api/请求根路径会命中/看起来没问题但如果你把正则加进去比如location ~* \.(js|css)$ { root /data/static; }那就要仔细想想正则与普通前缀谁会先执行Nginx在这个坑里翻车的人不在少数。记住一个原则正则匹配永远在普通前缀匹配之后执行但^~能阻止正则继续搜索。5.3 proxy_pass的斜杠陷阱404的真正来源反向代理最隐蔽的坑是proxy_pass目标地址末尾有没有斜杠。这直接影响转发给后端服务时的URI。看两个典型写法location /api/ { proxy_pass http://127.0.0.1:8080; }这个写法中proxy_pass末尾不带斜杠转发给后端时保留完整URI后端看到的是/api/login。适合后端本身就是按/api前缀开发的接口服务。location /api/ { proxy_pass http://127.0.0.1:8080/; }这个写法中proxy_pass末尾带了斜杠Nginx会把location匹配到的那一段去掉后端看到的是/login。适合后端本身没有/api这个前缀、只是前端对外用/api来区分服务的场景。这两种写法没有绝对的对错取决于你后端的路由设计。但如果你配完之后发现部分接口404优先检查这里大概率是斜杠问题。我早期调试时前端/api/login到后端变成了//login或者/login各种莫名其妙的路径错位都是这个细节造成的。还要注意代理时头信息的传递。后端服务如果想获取用户的真实IP、真实协议你得把下面这一段配上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;尤其是X-Forwarded-Proto如果后端是Spring这类框架并且自己做了HTTPS重定向没有这个头的话它会认为原始请求是HTTP然后强制跳转结果就是你反复体验过的301循环。5.4 几个高频服务的代理写法实战代理Node.js服务常规写法location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; }代理WebSocket服务比如在线聊天、实时通知location /ws { proxy_pass http://127.0.0.1:3001; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 3600s; }WebSocket的关键是Upgrade和Connection两个头不写的话连接会一直断。proxy_read_timeout也要调大默认60秒很容易导致长连接被切断。代理内网AI服务比如Ollama这类本地推理服务只需要把Nginx配置成统一入口转发到本机11434端口即可location / { proxy_pass http://127.0.0.1:11434; 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_read_timeout 600s; }本地模型推理耗时长proxy_read_timeout一定要拉到几百秒以上否则Nginx会在模型还没返回结果时就主动断开给用户一个504。6. 一份可直接抄作业的完整配置模板6.1 全局配置段先看一眼先说主配置文件/etc/nginx/nginx.conf里需要关注的部分。系统默认的nginx.conf整体够用重点留意下面几个关键点user nginx; worker_processes auto; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; sendfile on; keepalive_timeout 65; gzip on; include /etc/nginx/conf.d/*.conf; }很多人会忽略worker_connections和keepalive_timeout。连接数按需调常规小项目worker_connections 1024足够keepalive_timeout保持默认或者调到65秒都比较稳妥。关键在最后一行include /etc/nginx/conf.d/*.conf;确保你新建的conf文件都放在这个目录里。6.2 一个域名的完整server组合直接复制改下面是一套完整的、能直接抄走的域名配置我加了详细注释。# 域名: api.example.com # 后端: http://127.0.0.1:8080 # 80端口统一跳转HTTPS server { listen 80; server_name api.example.com; return 301 https://$host$request_uri; } # 443端口入口加载证书代理后端 server { listen 443 ssl http2; server_name api.example.com; ssl_certificate /etc/nginx/certs/api.example.com/fullchain.pem; ssl_certificate_key /etc/nginx/certs/api.example.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers on; access_log /var/log/nginx/api.example.com.access.log; error_log /var/log/nginx/api.example.com.error.log; 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; } }第二个域名tools.example.com就复制这个文件把server_name、证书路径、代理端口、日志路径全部替换。每个域名独立文件的好处是以后换证书时只需要改对应文件里的证书路径再reload即可绝不会误伤其他站点。6.3 reload与验证流程这条流程必须在换配置后走一遍配置文件写好后完整流程我建议按这个来nginx -t这个命令会检测所有配置语法是否正确显示syntax is ok和test is successful才能继续。配置有错时Nginx会明确指出是哪一行。然后重新加载配置nginx -s reloadreload和restart区别很大。reload会先检查配置成功则平滑重载不中断当前连接失败则继续用旧配置这很安全。所以我已经很久不用nginx -s restart了reload就够。接着验证跳转和证书curl -I http://api.example.com这能看到是否返回301Location头是否正确指向HTTPS。curl -I https://api.example.com确认返回正常状态码比如200。openssl s_client -connect api.example.com:443 -servername api.example.com确认证书链和域名匹配。这套流程走完一个域名的配置就算真正落地了。7. 常见问题与排查技巧实录7.1 高频问题速查表这么多年的配置经验我把最常踩的问题整理成一张速查表适合打印出来贴显示器上。现象可能原因解决办法访问域名报NET::ERR_CERT_COMMON_NAME_INVALID证书域名与访问域名不匹配核对证书SAN重新申请匹配域名的证书替换证书后还是显示旧证书未reload、浏览器缓存执行nginx -s reload用无痕窗口测试配置了HTTPS但HTTP不跳转缺少80端口server块添加return 301 https://$host$request_uri;301重定向循环后端自身强制HTTPS且Nginx未传X-Forwarded-Proto给location加proxy_set_header X-Forwarded-Proto $scheme;502 Bad Gateway后端服务未启动或端口错误ss -lntp检查端口确认后端进程504 Gateway Timeout后端处理超时调大proxy_read_timeout404 Not Foundproxy_pass末尾斜杠错误或location匹配错了检查proxy_pass是否带斜杠确认location规则静态文件403目录权限不足给目录加r权限或检查index配置WebSocket连不上缺少Upgrade头配置proxy_set_header Upgrade $http_upgrade;IP直接访问返回错误证书未配置default_server配置default_server返回444或跳转7.2 我的独家排查套路最后分享一个我用了很久的排查套路。遇到多域名代理问题我永远先做三件事看错误日志跑curl确认server_name命中情况。第一步是tail -f /var/log/nginx/error.log这里会记录几乎所有底层错误包括证书权限、上游连接失败。第二步是curl -v http://api.example.com看返回头里的Server和Location字段快速判断是否被Nginx处理。第三步是最容易被忽略的确认当前请求到底进入了哪个server块。方法是临时在一个server块的location里加一行add_header X-Debug-Server api;然后用curl -I看响应头立刻就知道是哪个server块在处理请求这个技巧在处理多域名串证书、跳转异常时极其有用。再补充一个小经验给每个域名配置独立的access_log比如access_log /var/log/nginx/api.example.com.access.log;。日志一分开哪个域名有异常流量、哪个域名出现了大量4xx一眼就能看出来。而且以后做日志分析的时候分文件收集数据也方便得多。很多小问题看着玄乎实际把日志分开后线索立刻就清晰了。
返回列表