2020年我接到一个部署需求一台在某云厂商买的云服务器一个刚注册的域名要把里面跑在8080端口的Web服务变成直接输入域名就能访问。老实说一开始我也在图省事想着把域名解析到服务器IP然后在公网地址后面加个端口不就行了结果第一次给非技术朋友演示的时候对方盯着URL里那一长串端口号问了一句“这玩意儿是不是不安全”。看着确实不好看但更关键的是后面要挂HTTPS、要加第二个站点的时候裸奔式IP加端口的方式根本接不住。那一次之后我把Nginx和Caddy两条路都完整走了一遍这篇就把整个过程、配置、踩坑点都记录下来尤其是反向代理配置里的细节希望能帮你少走点弯路。1. 为什么非要用反向代理访问域名1.1 直接IP带端口访问的麻烦很多新手拿到云服务器后第一反应是把服务跑起来然后用公网IP:端口去访问。这在开发环境没问题一旦到了正式场景问题一个接一个冒出来。首先是不好看也不方便。普通用户记不住http://123.123.123.123:8080这种地址你发给客户客户只会觉得不专业。浏览器默认只认80和443端口其他端口都得手动敲进去书签收藏、微信分享、二维码生成都会变得非常别扭。然后是端口暴露的问题。如果把8080、3000这种端口直接开到公网等于把后端服务的端口号和存活情况直接暴露给所有人。配合扫描工具很容易被盯上被扫出来之后还会被反复尝试登录和打漏洞。每个服务都直接暴露攻击面会变得很大单靠程序自身的安全措施远远不够必须在前面加一个统一收敛的入口。还有一个实际问题一个服务器上往往不只跑一个服务。今天有一个博客明天加一个API后天再来一个监控面板。如果都用IP加端口访问端口冲突很快就来了而且别人访问的时候还要记住哪个端口对应哪个服务。域名访问最大的好处就是通过域名把不同服务分隔开一个80端口和一个443端口就能托管几十上百个站点无需为每个服务单独开公网端口。1.2 反向代理到底做了什么反向代理是位于客户端和后端服务之间的一层“中转站”。用户访问http://blog.example.com请求会先到达云服务器上监听80/443端口的反向代理程序反向代理根据配置里的域名规则再把请求转发到后端的某个端口比如127.0.0.1:8080然后把响应原路返回给浏览器。整个过程对用户完全透明用户只知道自己访问了一个域名根本不知道后端跑在哪个端口、是Java还是Python写的。这就像公司前台来访者只需要说“我找技术部的张三”前台会自己去把张三叫出来来访者不需要知道张三坐在哪一层哪个工位。从安全角度讲这层代理把后端服务全部藏在了内网或者回环地址里外网扫描只能看到80/443看不到8080、9090这些实际业务端口。后面就算后端要换端口、要加实例做扩容只要代理配置不变对外访问地址完全不受影响。HTTPS证书也能集中在这层统一配置不用每个后端服务各自折腾一遍。1.3 为什么选Nginx和Caddy这两条路反向代理工具很多Apache、HAProxy、Traefik都能做但我最后集中研究了Nginx和Caddy。Nginx是2020年时绝对的主流稳定、性能强、文档多、生态大生产环境几乎绕不开它。Caddy则是另一个极端配置极简、默认全自动HTTPS尤其Caddy 2在2020年发布之后把反向代理的门槛拉到了极低非常适合快速上手。两条路线我都实际部署过体验差异很大。Nginx像是手动挡的越野车什么路况都能处理但需要你对配置细节有掌握Caddy像是自动挡的城市SUV日常使用非常舒服自动帮你把轮胎气压、差速锁都调好了只是遇到极端需求时能改的选项没有Nginx多。后面我会分别给出完整的实操配置再说说具体场景里到底怎么选。2. 部署前先把域名解析和安全组放行搞定2.1 云服务器基本初始化配置反向代理之前服务器本身的初始化直接决定后面能少踩多少坑。我当时用的是1核2G的入门配置系统选的Debian系原因很简单包管理方便、软件新、网上教程最多。如果你手头是CentOS 7/8命令要换成yum配置思路一样只是包名略有差异。拿到服务器后建议第一时间执行更新然后创建一个普通用户用来日常操作不要一直用root。很多安全问题都是因为长期直接用root操作一旦命令写错补救的机会都没有。# Debian/Ubuntu 系 apt update apt upgrade -y apt install -y sudo curl wget vim # 创建普通用户 adduser deploy usermod -aG sudo deploy # 之后用 deploy 用户登录命令前面加 sudo这一步看似跟反向代理没有直接关系但后面所有配置文件的编辑、服务的启动、日志的排查都会用到。先把基础环境收拾干净配置代理时才不会因为权限、缺包、时间不对等问题分心。2.2 域名解析把域名指到服务器IP域名解析是最容易出问题的一环90%的“域名无法访问”其实都是解析没弄对。登录域名管理控制台找到解析设置添加一条A记录主机记录填记录值填云服务器的公网IP。如果你希望用户访问时带不带www都能到达就再加一条主机记录为www的A记录指向同一个IP。TTL建议先设成600秒10分钟方便调试时解析记录能尽快生效。等一切稳定之后可以再放宽到3600或更长减少DNS查询压力。验证解析是否生效我习惯直接在服务器上执行dig short blog.example.com # 或者 nslookup blog.example.com如果输出的IP和服务器公网IP一致说明解析已经生效。如果还没生效先别急着改Nginx多半是本地DNS缓存没刷新换一个公共DNS或者等几分钟再看。想立刻测试可以在本地电脑的hosts文件里临时加一条“公网IP 域名”的记录把解析绕过去直接验证服务器上的配置是否正常。2.3 安全组放行80和443这是很多新手最容易忽略的一步服务器上的防火墙规则看起来没问题但域名就是访问不了最后发现是云平台的安全组没放行。云平台的安全组相当于虚拟机外层的总闸默认情况下只放行22端口SSH和3389Windows远程桌面。你需要手动添加入方向规则允许TCP 80和TCP 443端口来源地址填0.0.0.0/0也就是允许所有公网IP访问。如果不想完全放开也可以限制来源IP段但正经对外提供网页服务80和443基本都得放开。另外还要注意系统防火墙这一层。Debian/Ubuntu上叫ufwCentOS上叫firewalld。安全组放行不等于系统防火墙也放行了两层是叠加的关系。我踩过很丢人的坑安全组放行了80服务器上也在监听80但ufw没放行结果从外网访问一直超时最后查日志才发现是系统防火墙拦截。# ufw 放行 HTTP/HTTPS sudo ufw allow 80/tcp sudo ufw allow 443/tcp2.4 域名对外服务的合规提醒如果你用的服务器节点部署在国内域名要在公网通过80/443端口提供访问一定提前把平台要求的备案和接入流程处理完否则域名解析做得再完美80端口也一样会被卡住。我处理过几次类似的情况最稳妥的做法是先到云平台控制台提交备案申请审核通过后再把域名解析正式切到服务器中间还能顺便把服务器环境准备好。这块属于正规流程不要想绕过去该提交的资料按平台提示填就行。3. Nginx反向代理配置实操从零到HTTPS3.1 安装Nginx并搞懂配置文件布局Nginx安装很无脑不同的Linux发行版各自执行命令即可# Debian/Ubuntu sudo apt install -y nginx sudo systemctl enable --now nginx # CentOS sudo yum install -y nginx sudo systemctl enable --now nginx安装之后别急着改配置先搞清楚文件布局。Nginx主配置在/etc/nginx/nginx.conf里面通常有一行include /etc/nginx/conf.d/*.conf;意思就是conf.d目录下所有以.conf结尾的文件都会被加载。Deiban系还有sites-available和sites-enabled这套软链接机制没必要两种混着用我建议统一使用conf.d目录一个域名一个配置干净又清晰。默认的nginx.conf写得非常保守很多参数针对静态网站反向代理场景中最关键的是http块里是否已经include了conf.d目录。你可以先执行nginx -T看看当前最终生效的配置确认include存在再动手写站点配置。3.2 写一份最小可用反向代理配置在/etc/nginx/conf.d/下新建一个blog.conf写入一份最小可用的反向代理配置server { listen 80; server_name blog.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; } }这里面的每一行都值得说清楚。listen 80;让Nginx监听80端口浏览器访问域名时默认就走80。server_name blog.example.com;是虚拟主机最重要的判断条件同一台服务器的多个站点就靠它区分完全匹配当前请求域名才会进入这个server块。location /匹配所有请求路径。proxy_pass http://127.0.0.1:8080;把请求转发到本机8080端口也就是实际跑业务服务的端口。后面的proxy_set_header一行都不能省尤其是Host后端程序拿到这个头才能知道用户访问的是哪个域名不然生成出来的链接、跳转地址都会乱掉。这里有个非常经典的坑proxy_pass后面的URL带不带结尾的斜杠行为完全不同。# 不带斜杠转发时保留完整 URI proxy_pass http://127.0.0.1:8080; # 带斜杠会把原 URI 中匹配到的 location 部分替换掉 proxy_pass http://127.0.0.1:8080/;比如用户访问blog.example.com/api/user第一种配置转发到后端的还是/api/user第二种配置会把/api这个前缀去掉转发到后端变成/user。很多接口突然404查到最后都是这里多写了一个斜杠。3.3 用工具自动签发HTTPS证书没有HTTPS的网站在2020年已经很难让人放心访问了浏览器会直接标记“不安全”。手动配置证书证书的过程又比较琐碎所以我的建议是直接用自动签发工具。在Debian/Ubuntu上安装工具然后一行命令就能签发证书并自动改好Nginx配置sudo apt install -y certbot python3-certbot-nginx sudo certbot --nginx -d blog.example.com -d www.blog.example.com执行过程中会询问是否把HTTP自动跳转到HTTPS建议选自动跳转。命令跑完之后Nginx配置会被改写成监听443的SSL站点并自动加上证书路径不需要自己再手动配ssl_certificate。证书有效期一般是90天别忘了设置自动续期sudo certbot renew --dry-run可以把certbot renew写进系统的定时任务每天执行两次就行。我习惯每周手动看一眼执行日志毕竟真要到了证书过期还没续上用户访问时会看到大红警告页那画面太折磨了。3.4 多站点和路径级转发一台服务器挂多个域名反向代理是标配答案。每个域名写一个server块然后全部放到conf.d目录下Nginx会根据请求的Host头自动选择对应的配置# /etc/nginx/conf.d/api.conf server { listen 80; server_name api.example.com; location / { proxy_pass http://127.0.0.1:9000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }更复杂一些的场景是同一个域名下按路径把请求分给不同后端。比如blog.example.com/api走接口服务其他路径走前端页面server { listen 80; server_name blog.example.com; location /api/ { proxy_pass http://127.0.0.1:9000; } location / { proxy_pass http://127.0.0.1:8080; } }如果后端有多个实例还可以用upstream做负载均衡upstream backend { server 127.0.0.1:8080 weight3; server 127.0.0.1:9090 backup; } server { listen 80; server_name blog.example.com; location / { proxy_pass http://backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }weight3表示第一个节点默认多分一些流量backup表示第二个节点平时不参与转发只有第一个节点挂掉之后才自动接管。这个配置在横向扩容时特别实用后端服务重启也能平滑过渡。3.5 Nginx配置检查和重启的正确姿势每次改完配置先检查语法再重载服务这两步绝对不能省sudo nginx -t sudo systemctl reload nginxnginx -t会告诉你配置文件有没有语法错误如果输出test is successful再执行reload。reload是平滑重载不会中断现有连接而restart会临时把服务完全停掉再启动生产环境慎用。常见错误是配置写了但没生效排错顺序通常是这样先执行nginx -t排除语法问题再看reload是否成功最后看错误日志sudo tail -f /var/log/nginx/error.log日志里如果出现写权限不足、unknown directive、cannot bind port 80这类关键字基本都是配置问题。还有一个小习惯我坚持了多年每次改配置前先把原配置备份成.bak文件改完测试通过后再删防止手滑把整个站点配置改坏又找不到原来的样子。4. Caddy2反向代理配置实操一行搞定自动HTTPS4.1 安装Caddy2的方法Caddy 2在2020年刚发布不久和Caddy 1语法差异很大所以安装时一定要确认版本。最简单的方式是从官方下载编译好的二进制文件放到/usr/local/bin下然后注册成系统服务。# 从官方仓库下载对应系统架构的 caddy 二进制文件 # 拷贝到 /usr/local/bin/ 并赋予执行权限 sudo chmod x /usr/local/bin/caddy # 验证版本看到 2.x.x 就说明装对了 caddy version如果Linux发行版自带软件源里有caddy包也可以直接用apt install caddy但装之前先确认软件源里的版本是不是2.x否则照着2的语法写配置会直接报错。我更推荐二进制方式版本可控、升级方便而且不依赖系统源。给Caddy写一个systemd服务文件挂载到/etc/systemd/system/caddy.service核心内容就三块启动命令、工作目录、用户权限。跑一段时间以后它会自动监听80和443端口并读取/etc/caddy/Caddyfile作为配置。4.2 一行核心配置Caddyfile装好之后最让人上头的时刻来了。在/etc/caddy/Caddyfile中写下唯一需要的内容blog.example.com { reverse_proxy 127.0.0.1:8080 }你没看错代理一个域名就这么点东西。blog.example.com是站点域名后面的reverse_proxy 127.0.0.1:8080指定后端地址。保存文件执行sudo systemctl restart caddy然后Caddy会立刻做三件让Nginx用户羡慕的事自动向公共CA机构申请HTTPS证书、自动配置80端口跳转443、自动把443流量转发到8080后端。整个过程不需要手动生成密钥、不需要改一堆SSL配置、不需要配置什么自动续期Caddy自己就全包了。打开浏览器访问https://blog.example.com证书已经生效那种爽感只有被Nginx证书折磨过的人才懂。4.3 Caddyfile进阶写法路径分流与负载均衡别以为Caddy只会写一行配置。多站点、按路径分流、负载均衡它都能做语法还是比Nginx简洁得多。按路径把请求分给不同后端blog.example.com { handle /api/* { reverse_proxy 127.0.0.1:9000 } handle { reverse_proxy 127.0.0.1:8080 } }handle指令按顺序匹配请求路径以/api/开头时走接口服务其他所有请求落入下面的兜底handle转发到前端页面服务。这种写法比Nginx的location字母优先级规则直观多了一眼能看懂请求最终去了哪。负载均衡同样简单后端多个地址直接列在后面blog.example.com { reverse_proxy 127.0.0.1:8080 127.0.0.1:9090 }Caddy默认会在这些后端之间自动轮询不需要额外配置。如果你需要自定义证书路径加一行tls指令即可不想自定义就直接让Caddy自动管。4.4 Caddy2的常见坑Caddy不是没有坑。首先自动HTTPS依赖80和443端口能从外网访问同时域名必须已经正确解析到当前服务器。如果域名还没解析过来Caddy申请证书时会报错no A record这个时候要先去改解析而不是对着Caddy配置反复纠结。其次Caddy 2和Caddy 1的配置文件语法不兼容网上搜索教程时一定要看清版本。我曾经拿Caddy 1的proxy语法套到2上面caddy validate直接提示不认识的指令改成reverse_proxy之后一切正常。记住一条判断标准Caddy 2里反向代理关键词是reverse_proxy不是proxy。排查Caddy问题日志是主要来源sudo journalctl -u caddy -f看到certificate obtain相关的报错优先检查域名解析和80/443端口。还有一个容易被忽略的坑如果你先装了Nginx而Nginx还占着80和443端口Caddy启动时必然报address already in use。同一时间只能有一个程序负责监听80/443不能同时跑。5. 选Nginx还是Caddy我的对比结果5.1 配置语法与学习成本放个直观的对比表格方便你快速判断维度NginxCaddy配置语言指令式分server/location块声明式按缩进写规则反向代理最小配置量大约10行2行HTTPS自动管理需要Certbot或手动配置内置自动申请续期学习曲线陡峭细节多平缓半天上手调试工具日志丰富但需要理解日志直观但内容较少Nginx的每个指令背后都有丰富的自动化约定和生态历史新手容易在upstream、location优先级、ip_hash这些概念里绕晕。Caddy把99%的常规用法压缩成一行几乎不需要查文档就能跑通。说实话如果我只是给朋友搭个个人博客Caddy绝对是首选。5.2 性能与资源占用对比反向代理本质上是一个扛并发的门卫。Nginx是C写的基于事件驱动模型能扛非常高的并发连接内存占用也低这是它长期霸占服务器市场的重要原因。Caddy用Go语言实现并发能力同样不弱但二进制大、内存占用相对高一些。在小流量项目里两者性能体感几乎没有差别真要到了每秒几千上万的请求量Nginx的成熟度会让人更安心。2020年我实际跑过一个日活约几千人的小站分别用Nginx和Caddy各跑一周CPU和内存的差距都很小完全不需要因为性能而纠结。真正影响性能的往往是后端服务、数据库和代码质量代理层反而经常是最轻松的一环。5.3 生态与生产环境适配Nginx的生态优势在需要复杂流量治理时体现得淋漓尽致。基于Nginx可以写Lua脚本、做缓存、做安全防护、做灰度发布OpenResty生态也基本都是围绕它转的。团队运维经验丰富的时候Nginx能从一个入口网关扩展成很多场景的基础设施。Caddy的优势在简单和自动化官方模块机制虽然也支持拓展但社区规模和成熟度远不如Nginx。在企业生产环境里如果团队里已经有人熟练用Nginx那就继续用Nginx不要为了“新工具”而引入Caddy因为后续接监控、接日志、接灰度发布时Nginx的踩坑积累确实远远多于Caddy。如果是个人项目、独立开发者的产品或者临时搭建的Demo和内部工具Caddy完全是更聪明的选择。5.4 我的组合建议实际用下来我的习惯是“快用Caddy重用Nginx”。具体来说临时起一个服务、给客户演示、或者内部工具直接用Caddy几分钟上线自动HTTPS一点不操心到了长期运行、流量上升、需要精细化控制的时候再把配置迁移到Nginx。另一种思路是两者搭配使用Caddy在最前面负责自动HTTPS和域名入口后面再用Nginx集群做负载均衡和应用路由。大部分场景其实不需要这么设计徒增复杂度。真正的一线经验是代理层设计得越简单越好先把业务跑通再根据实际瓶颈逐步调整不要一开始就上一套“全家桶”。6. 反向代理常见报错排查速查6.1 域名解析不生效怎么办域名解析这个环节问题发生得最隐蔽。访问域名超时但服务器明明开着、服务也在跑第一反应往往是看代理配置其实应该先确认解析对不对。在服务器上执行dig short 域名看返回的IP是不是服务器公网IP。如果返回为空说明解析还没生效检查域名管理后台的A记录是否填写正确。如果本地电脑访问不了但服务器上curl能通大概率是本地DNS缓存了旧的解析结果可以换个网络或者等待TTL过期。调试期我会在本地电脑的hosts文件里临时写一条“IP 域名”直接绕过DNS验证服务器配置等一切正常了再删掉。6.2 502 Bad Gateway排错流程502是反向代理最常见的报错意思是代理服务器连不上后端。先别怀疑代理本身按顺序排查# 1. 后端服务在不在监听 curl http://127.0.0.1:8080 # 2. 端口有没有被监听 ss -lntp | grep 8080 # 3. 从服务器本机访问域名 curl -I http://blog.example.com如果直接在服务器上访问后端端口返回正常但访问域名时依然502说明问题出在proxy_pass指向错了或者代理和后端不在同一个网络命名空间。如果连127.0.0.1:8080都连不上那就去看后端服务的启动日志多半是后端进程挂掉了或者启动时没监听成功。还有一个小概率情况后端监听的是127.0.0.1但对端期望的是0.0.0.0也会导致请求进来却没有程序接单。6.3 403 Forbidden怎么处理403通常和反向代理本身关系不大更多的是权限问题。如果location里做的是静态文件服务403的原因基本是Nginx工作进程没有目录的读取权限把目录权限改成755、文件改成644就好。如果反向代理到后端后仍返回403那就是后端服务自己拒绝了请求常见原因是IP白名单、Host头校验、或者应用层权限控制。我之前遇到过一次很诡异的403Nginx配置看着完全正常后端也能访问最后排查发现是SELinux拦截了Nginx主动外联后端端口把SELinux的httpd_can_network_connect打开才解决。CentOS服务器上遇到诡异的网络问题记得留个心眼查SELinux。6.4 80端口被占用或安全组不生效启动Nginx或Caddy时报address already in use一定是80端口被别的程序占了。执行sudo ss -lntp | grep :80看看PID对应什么程序常见的是Apache、cloud-init或者是之前残留的Nginx进程。找到后停掉或改掉冲突方然后再启动代理服务。安全组不生效的情况也很多明明加了80/443放行规则但外网还是不通先检查规则是否应用到正确的云服务器实例上再检查系统防火墙有没有拦截一层层排查不要想当然。6.5 HTTPS证书申请失败证书申请失败在NginxCertbot和Caddy里都遇到过原因高度一致。先用浏览器直接访问http://域名如果域名能返回页面说明解析和80端口是通的如果返回不了先去修连接问题证书申请自然就通了。再看服务器系统时间证书签发对时间同步比较敏感时间偏差太大时会直接失败执行date确认时间必要时ntpdate或chronyc同步一次。还有ACME协议自带频率限制同一个域名短时间内反复申请会触发限流报错里会出现“too many requests”这种就只能等一会儿再试。Caddy的证书日志在journalctl -u caddy -f里Certbot的日志在/var/log/letsencrypt/下出问题先看日志关键字。6.6 排错通用套路我总结了一套排错顺序按这个顺序做90%的问题都能找到根因域名解析dig确认域名指向正确IP。网络连通外网直接访问IP和端口看通不通。端口监听ss确认代理和后端都在正确端口。代理配置nginx -t或caddy validate检查语法。后端日志最后看应用日志确认请求有没有真正到达后端。不要一上来就重启服务、改防火墙那样只会让问题变得更难排查。先定位到底断在哪一层再针对性动手。7. 一些我踩过之后才记住的经验这部分其实是我最想写的。第一个经验配置文件的备份和版本管理一定要做好。我后来把所有Nginx和Caddy的配置都纳入了Git仓库每次改动都留下commit记录出问题可以随时回滚。不要低估手滑的概率我至少有两次因为少写了一个分号把线上站点搞到全局502有了Git心里踏实很多。第二个经验先用Caddy跑通需求再决定要不要换Nginx。Caddy 2的自动HTTPS在上线阶段节省的时间非常可观把域名解析做好、写两行配置、服务起来就能用。等你确认业务稳定了再评估到底需不需要Nginx的复杂能力不要一上来就在Nginx配置里折腾半天。很多新手的挫败感其实都来源于过度设计。先把事情做简单再逐步增加复杂度是部署这类基础设施最靠谱的路径。最后分享一个小技巧换服务器IP的时候记得同时改三处——域名解析、安全组规则、反向代理配置文件。听起来很基础但我确实在一次迁移时只改了域名解析忽略了防火墙安全组结果整整浪费了一个晚上才发现流量根本到不了新服务器。类似的坑希望读到这里的你不用再踩。