前两天帮一个朋友收尾部署在宝塔面板上的前后端分离项目第一眼看到的就是浏览器地址栏里一长串http://IP:8080前端页面里的接口请求也写死了服务器IP加端口。这个状态在开发联调阶段用起来没毛病但真要交付的时候就尴尬了地址记不住、HTTPS证书办不下来、后端接口等于直接裸奔在公网上。把宝塔部署的项目从IP访问改成自定义域名访问要做的事情其实很集中——准备域名解析、在宝塔里建站点、配置反向代理、重新构建前端、调好后端跨域最后挂上证书。这篇文章就按这个顺序讲适合已经能用宝塔把项目跑起来、想正经上一个域名的朋友你可以对照自己手头的项目一步步来。1. 动手前先看清现状你的项目现在到底是怎么被访问的1.1 三种常见部署形态先对号入座改域名之前我建议你先花两分钟搞清楚当前项目是怎么被访问的。同一个“宝塔部署的前后端项目”实际形态差别很大迁移的改法也不同。第一种形态后端开在某个端口比如3000前端构建完的静态文件直接扔在一个PHP站点或者默认站点目录里。打开页面看的是http://IP:8080接口请求写在http://IP:3000。这种最典型也是这篇文章重点覆盖的情况。第二种形态前后端都直接监听公网端口比如后端http://IP:3000、前端http://IP:8080两边都是裸的。这种麻烦一点因为后端暴露公网本身就是个安全隐患迁移域名后正好可以顺手把后端收回到内网。第三种形态后端本身会托管静态资源前端打包后直接放到后端的静态目录里整个项目只有一个端口。这种改造量最小其实只需要给域名挂到宝塔站点然后用反向代理把请求转发到后端端口就行。不管哪种形态核心思路是一致的让一个域名作为对外唯一入口宝塔里的Nginx负责根据域名和路径把流量分到静态文件或者后端进程上。1.2 从IP到域名改变的其实不只是地址总有人觉得“不就是把地址换成域名吗”实际操作后会发现问题没那么简单。从IP访问改成域名访问表面上改的是地址栏实际上改的是整套访问链路。第一个好处是能把端口收敛掉。以前前端一个端口、后端一个端口都暴露在公网里任何人都能直接扫到然后对着后端端口发起请求。挂上域名后Nginx只对外开着80和443后端只需要监听本机的127.0.0.1:3000就够了从外面看只能看到你的前端入口。第二个好处是能正常上HTTPS了。免费的证书服务基本都要求验证域名那套签发流程对纯IP是不开放的。没有域名就没办法正经上HTTPS而没有HTTPS的话浏览器里一直顶着“不安全”的警告Cookie的可用性也受影响。第三个好处是地址稳定。前端代码里如果把API地址写死成http://IP:3000将来换服务器就要重新改代码重新打包。用了域名之后换服务器只需要把DNS解析指向新IP就行代码不动。所以迁移这件事的逻辑不是“换个地址好看”而是“把入口收拢、把链路标准化”。想通了这一点后面每一步就不会觉得多余了。2. 域名与解析准备先让域名认识你的服务器2.1 DNS记录到底怎么加才不会折腾半天不生效在宝塔面板里配置域名之前得先保证域名能解析到你的服务器IP上。这一步一般是在你的域名服务商控制台完成的不是宝塔里的操作。你需要判断一下自己走哪种方案如果准备给前端用example.com给后端单独开一个子域名api.example.com那要添加两条记录分别对应两个域名如果准备一个域名通吃也就是用同一个域名下的/api路径去访问后端接口那只需要给主域名和www添加解析记录就行。主域名example.com一般添加A记录记录值填服务器的公网IP。www.example.com如果你的项目和主域名是同一个站点也可以添加A记录指向同一个IP或者用CNAME指到example.com。后端子域名api.example.com如果端口反代也是在这台服务器上直接也加一条A记录指向同一个IP就行不需要单独一台机器。这里有个高频翻车点很多人加了解析记录之后域名半天打不开就开始怀疑宝塔、怀疑防火墙其实问题就出在DNS缓存上。你用本地电脑去ping example.com看到的还是旧IP是因为本地DNS缓存还没刷新或者TTL值设得太长。建议在迁移之前就把TTL改成600秒甚至300秒等整个迁移完成后再改回默认值。验证解析是否生效Windows上可以用nslookup example.commacOS或Linux上可以用dig example.com short能看到IP已经变成服务器公网IP就说明解析生效了。还要注意一个很实际的问题如果你的服务器在境内域名要正常用80和443端口对外服务通常需要先在服务商侧完成合规流程具体问一下卖你服务器和域名的服务商就行。这一步如果没确认清楚后面证书申请会一直失败而且问题表现得很隐蔽。2.2 安全组和宝塔防火墙改完域名反而打不开网站的真凶解析配好了、域名也能ping通了但浏览器打开仍然超时或者直接被拒绝连接这时候九成以上是端口没有放行。很多服务器之前只开了后端端口比如3000/8080因为以前就是通过IP加端口访问的。现在你要用域名访问域名默认走80端口HTTPS走443端口如果这两个端口在防火墙层面是关闭的那不管域名解析得多正确都打不开。这里要检查两个地方。一个是云服务商控制台里的安全组规则需要添加入方向规则放行TCP的80和443端口另一个是宝塔面板的“安全”菜单里同样需要放行80和443。这两个地方是层层叠的关系最外层安全组没放行宝塔里怎么放都没用宝塔防火墙没放行安全组放行了也进不来。顺手做一个小优化把后端端口比如3000从安全组和宝塔防火墙的公网放行列表里删掉只保留80、443和宝塔面板端口。因为切到域名访问之后后端已经不需要对外暴露了它只需要能被Nginx从127.0.0.1访问到就行。这一步做完你马上就体会到“收敛入口”的价值了。3. 宝塔建站和反向代理把流量交给正确的程序3.1 前端站点先让静态页面能以域名打开DNS和端口就绪之后正式进入宝塔操作环节。前端站点这一步的目标很简单访问http://example.com的时候能打开你的前端页面。打开宝塔面板进入“网站”点“添加站点”。域名填example.com和www.example.com两个都填上去。网站类型选“纯静态”就好因为前端项目构建完就是一堆静态文件不需要PHP或Java之类的运行环境。网站目录保持默认的/www/wwwroot/example.com创建完成之后把你本地前端项目执行npm run build构建出来的dist目录内容全部上传到这个根目录下。这一步有个细节值得注意如果你的前端用的是Vue或React的history路由模式而不是hash模式那打开首页没问题但点进某个子页面后刷新一下就会白屏浏览器报404。这是因为请求路径/user/list在服务器上找不到对应的物理文件。解决办法是在站点的“配置文件”或者“伪静态”功能里加一个Nginx规则让所有不存在的路径都回退到首页location / { try_files $uri $uri/ /index.html; }这样前端路由就交给页面里的JS去处理了刷新404的问题直接消失。3.2 后端接口两种暴露方式按项目情况选前端页面能用域名打开之后下一步就是把后端接口也搬到域名体系下。这里有两种思路我建议你先想清楚再动手因为这决定了后面前端代码要怎么写也决定了要不要单独处理跨域。方案A给后端单独开一个子域名。在宝塔里再添加一个站点域名填api.example.com类型同样选“纯静态”因为它的作用只是作为反向代理的入口。建好之后进入这个站点的“反向代理”设置添加一条记录目标URL填http://127.0.0.1:3000也就是你后端实际监听的地址。代理名称随便写一个自己能认出来的就行。这种方式下前端代码里API基础地址会写成https://api.example.com前端站点和API站点是两个独立域名所以这算一种跨域架构。方案B不另外用子域名让前端和后端共用同一个域名。请求路径只要以/api开头就转发到后端端口其它路径仍然由前端静态文件自己处理。这种方式需要在站点的配置文件里改一下因为宝塔自带的反向代理默认会把整个站点的请求都转发到后端这跟“静态文件也要被访问”的需求是冲突的。我给你的建议是小项目、内部系统、不想折腾跨域的用方案B以后准备把API独立出去给第三方调用、或者前端可能有多个站的用方案A。两个方案的实际效果对比如下对比项方案A子域名 api.example.com方案B同域 /api 路径需要申请的证书前端一个、后端一个只需一个跨域问题存在需处理CORS基本不存在Cookie管理需配置Domain为.example.com同源自动携带Nginx配置复杂度两个站点各配一次反代需要手写location块适用场景API要独立开放、多前端复用前后端同项目交付、内部使用如果你选了方案B在宝塔的“配置文件”里找到站点对应的server块手动加一段location /api/就够了前端静态部分继续走默认的root配置location /api/ { proxy_pass http://127.0.0.1:3000; 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的URI写法决定后端收到什么路径。假如请求进来是/api/user/list如果proxy_pass http://127.0.0.1:3000;后面不带斜杠后端收到的就是完整的/api/user/list如果写成proxy_pass http://127.0.0.1:3000/;后端收到的是/user/list也就是把/api前缀剥掉了。你得根据后端路由实际情况来选别两边都对不上还排查半天。3.3 反向代理里那几个请求头为什么要单独设置很多人配反向代理的时候容易忽略宝塔里那一排“发送域名”“发送URI”之类的开关。这些选项最终会变成Nginx配置里的proxy_set_header指令你别觉得它们可有可无。Host这一项很关键。后端在生成某些链接、做重定向、或者校验请求来源的时候依赖的就是这个Host头。如果反代把Host发成了127.0.0.1:3000后端拿到后会以为自己是被本机访问输出的跳转地址可能是http://127.0.0.1:3000/xxx用户点进去就出问题了。所以要让转发时带上原始域名也就是变量$host。X-Forwarded-For和X-Real-IP是用来传递真实客户端IP的。后端日志里如果显示的IP全是127.0.0.1就是这两行没配上。X-Forwarded-Proto更实用它告诉后端“用户实际用的是HTTP还是HTTPS”。如果后端起过自己的HTTPS跳转逻辑而Nginx没有把这个头传过去后端以为自己还在HTTP环境下就会导致重定向死循环。这是我实际调过的一个问题后面排查小节里会详细说。4. 前后端代码适配面板配完代码也得跟着改4.1 前端重新构建前需要改掉的位置宝塔面板配置只是完成了服务端的一半前端代码如果不跟着改页面能打开但接口全挂。这种情况我见过太多次了域名能访问了人很兴奋一打开控制台全是请求失败最后发现前端代码里的API地址还是旧的IP加端口。前端项目里API地址一般不会只出现在一个地方。最规范的做法是用环境变量比如Vite项目里有一个.env.production文件如果没有就自己创建一个把基础地址定义进去# .env.production VITE_API_BASE_URL/api如果你用的是方案A、子域名独立访问API那就写成# .env.production VITE_API_BASE_URLhttps://api.example.com改完后一定记得重新执行构建命令把产物更新到宝塔站点目录里。很多人改了源码忘了重新打包线上跑的还是老版本的产物折腾一晚上最后发现是这个低级问题。如果你原来的项目不规范API地址是直接在某个工具类或者页面文件里写死的那就全局搜一下代码里的http://开头的内网地址看到的IP加端口全部替换成新的域名或者相对路径。一块写死的IP也不能留因为浏览器安全策略下页面是HTTPS时请求HTTP接口会被直接拦截你在控制台看到的不是CORS错误就是Mixed Content警告。4.2 后端该跟着动的CORS、Cookie和Host校验后端程序这边的调整取决于前端代码最终是跨域请求还是同域请求。如果你选了子域名方案前端在example.com接口在api.example.com那么跨域就一定会发生。后端要在CORS配置里把允许来源从*改成具体的白名单比如https://example.com和https://www.example.com。注意不能继续用通配符同时配合Cookie凭证浏览器遇到Access-Control-Allow-Origin: *和Access-Control-Allow-Credentials: true一起出现会直接拒掉请求。如果你后端代码里之前写的是“允许所有来源”现在切到子域名方案之后要改成“只允许指定来源”并且处理好预检请求OPTIONS。Cookie这块也要单独看。方案B同域下浏览器会把Cookie正常带上一般不用额外处理。方案A子域名下如果前端登录后要把Session或Token存进Cookie而后端设置Cookie时没指定Domain浏览器会认为这个Cookie只属于api.example.com前端在example.com的页面里就读取不到。需要后端在Set-Cookie时加上Domain.example.com让子域名之间共享这份Cookie。不过Cookie里存登录态本身有利有弊具体用不用得结合项目情况权衡。还有一个很多人想不到的地方一部分后端框架会根据请求的Host头做合法性校验。比如有的框架默认只允许本机访问换上域名反代之后Host变成了example.com后端直接拒绝请求返回400错误。这时候需要在后端的允许主机列表里加入新域名再重启后端服务。这个问题的报错形式很迷惑明明Nginx配置看着没问题但请求就是过不去最后排查到后端框架文档才发现是Host校验的问题。另外一个安全习惯后端服务启动时监听地址尽量从0.0.0.0改成127.0.0.1。改成127.0.0.1之后这个端口就只能被服务器本机访问Nginx反代不受影响但外网再也扫不到你的后端端口了。这一步做完你的项目架构才算真的健康。5. HTTPS证书与强制跳转给域名戴上绿锁5.1 申请证书前必须满足的三个条件缺一个都失败前面的步骤都跑通之后最后一件大事就是给域名上HTTPS证书。没有证书也能用域名访问但浏览器的提示会显得项目很不专业而且部分现代浏览器的隐私特性会限制很多功能。宝塔面板里申请免费证书非常方便。进入站点设置找到“SSL”菜单选择“Let‘s Encrypt”把站点绑定的域名全部勾上点申请就行。一般一两分钟就能签下来。但有几个前提条件不满足的话申请过程会一直失败。第一域名解析必须已经生效而且解析到的IP必须就是当前这台服务器。证书服务商在签发之前会尝试通过域名访问你的服务器来验证控制权如果域名解析还指着别的地方验证必然失败。第二80端口必须能从外网访问。免费证书申请用的验证方式一般是HTTP-01也就是证书服务商会让它那边的一个专用地址访问你服务器上的某段内容。如果安全组或宝塔防火墙没放行80端口这个验证请求进不来申请就会失败。第三站点必须已经在宝塔里绑定了这个域名。不要试图在一个还没创建对应站点的域名上直接申请证书因为验证请求进来之后Nginx也不知道该往哪里路由。先把前面第三步做了再申请证书。5.2 强制HTTPS开启后的连锁反应别被跳转循环绕晕证书申请完并部署成功之后宝塔上会有一个“强制HTTPS”的开关打开之后所有访问HTTP地址的请求都会被301跳转到HTTPS地址。这个开关建议开启因为以后你很难保证别人不会用http://访问你的站点。开启强制HTTPS之后有几件事要一起检查。前端代码里如果API地址写的是http://api.example.com就算你开了强制跳转浏览器一样会先因为混合内容直接拦截这次请求根本等不到后端做301。所以前端环境变量里的API地址一定得是https://开头的或者用相对路径/api。还要检查后端是不是自己也有跳转逻辑。有些后端框架在检测到非HTTPS请求时会自动重定向到HTTPS这是好事。但如果你用了Nginx反代Nginx本身已经处理了HTTPS而后端并不知道用户用的是HTTPS——因为后端看到的连接来自Nginx的127.0.0.1它只会把这个连接当成普通HTTP。此时如果后端又做了一次“把HTTP重定向到HTTPS”就会形成一个跳转死循环Nginx转发给后端后端觉得不是HTTPS就发302让浏览器重新走HTTPS浏览器请求又到Nginx又转发给后端又302。解决办法就是前面提到的X-Forwarded-Proto把这个请求头正确传给后端后端判断协议时优先信任反代传过来的值就能打破这个循环。证书续期基本不用操心宝塔默认会自动续期。只是要记住续期验证走的还是80端口所以别在迁移完成之后又把80端口封了不然证书到期前续期失败网站会突然显示证书过期那个排查更闹心。6. 迁移过程中遇到的典型问题排查6.1 域名打不开或者打开之后看到的是别人的站点这种状况排在所有问题里的第一名。域名打不开先不要急着怀疑宝塔先看解析。本地执行ping example.com看解析出来的IP是不是当前服务器的公网IP。如果还是旧IP说明DNS缓存没刷新或者你几天前就改了解析但TTL设太长。可以把本地电脑的DNS改成公共DNS再试一次比如nslookup example.com 8.8.8.8直接向指定DNS服务器查询能跳过本地缓存干扰。如果域名解析IP没问题但浏览器打不开先确认安全组和宝塔防火墙的80/443放行状态。用命令从外网自测一下curl -I http://example.com如果超时说明端口还是被过滤了。另一类很迷惑的情况是域名能打开但打开的不是你的项目。这通常是因为宝塔服务器里还配置了默认站点IP访问绑定到了某个默认站点而你新加的域名解析过来后Nginx把请求匹配到了默认站点而不是你的站点。检查一下新域名是不是真的绑定到了正确的站点上以及服务器IP直接访问时显示的那个站点是不是占用了默认站点的位置。还有一个小技巧排查时用浏览器无痕窗口打开能排除浏览器缓存和DNS缓存的干扰实测下来很管用。6.2 证书一直申请失败可能的原因清单证书申请失败这个问题我前后踩过不少次每次原因都不一样。整理成一个速查表供你对照症状大概率原因处理方式申请提交后很快报“验证失败”域名解析未生效或指向了别的IP先确认解析等待生效后再申请一直卡在验证中然后失败80端口未被外网访问检查安全组和宝塔防火墙报“无效的响应”或404域名对应的站点不存在在宝塔里先建好站点再申请提示“申请过于频繁”短时间内重复申请太多等一小时或换其他域名申请成功但浏览器不认证书部署的站点和访问的站点不一致检查访问的是不是同一个域名另外很多人忽略了如果同一台服务器上挂了多个站点证书申请时要选对站点再申请。申请完记得看站点SSL面板里是不是显示“已部署”如果状态只是“已签发”但没有部署浏览器访问时底层的Nginx并没有加载证书。6.3 页面能开但接口404、502、跨域报错页面能正常显示说明前端静态文件和域名解析都没问题了。这时候接口报错问题基本集中在反代配置和前端代码两处。接口返回404如果走的是方案B的/api代理先确认你proxy_pass后面带没带斜杠。带斜杠会把/api前缀剥掉不带斜杠原样转发。你后端路由实际是带/api前缀的还是不带决定了Nginx该怎么写。这个不对404是最常见的表现。接口返回502Nginx拿到请求后无法连接后端。按顺序检查三件事后端进程是否还活着反代目标地址是否写对比如端口是不是从3000改成了3001后端监听地址是不是改成了127.0.0.1但反代还写的是公网IP。特别是最后一类如果后端监听改成了127.0.0.1反代目标URL也一定要对应改成127.0.0.1不能继续用服务器公网IP去连否则连接会被拒。跨域报错如果你用子域名方案浏览器控制台直接看到CORS相关错误那多半是后端没有正确返回Access-Control-Allow-Origin头。还有一种情况是预检请求处理不当前端发送带有自定义Header或非简单请求时浏览器会先发一个OPTIONS请求后端如果没有为这个请求返回正确的CORS头正式请求根本不会被发出。6.4 刷新404和WebSocket连不上两个经典专项问题刷新以后整个页面白屏、控制台报404这是history路由模式独有的症状。你访问example.com/user/list服务器在磁盘上找不到user/list这个目录自然返回404。解决办法就是在站点配置文件里加上try_files $uri $uri/ /index.html;。加完之后所有找不到物理文件的路径都会回退到页面入口由前端路由接管。如果你的项目用到了WebSocket比如在线聊天、消息推送域名迁移之后还需要单独给Nginx配置WebSocket升级支持。普通的反向代理配置默认不会处理Connection升级头导致WebSocket握手失败前端一直处于连接中状态。需要在反代的location块里补充几行配置location /ws/ { proxy_pass http://127.0.0.1:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_read_timeout 3600s; }proxy_http_version 1.1这行也很关键WebSocket协议要求HTTP/1.1以上而Nginx默认转发时用的是HTTP/1.0。proxy_read_timeout设长一点是因为WebSocket连接是长连接默认的60秒超时会让闲时连接被断开。6.5 一个容易被忽略的上传问题如果项目里有文件上传功能域名迁移后可能还会遇到上传大文件失败的怪现象。页面是HTTPS接口是HTTPS请求也没跨域但文件传到一半就断或者直接提示413。这个大概率是Nginx默认的client_max_body_size限制默认值是1M。改成大一点比如10M或按需设置。如果你用了宝塔的反向代理配置也要加在反代的location块里因为上传请求是走Nginx转发到后端的。写到这整个从IP访问到自定义域名访问的迁移链路已经完整过了一遍。我个人在实际操作中的体会是最花时间的永远不是面板操作本身而是没理清“哪一层出了问题”时的瞎猜。建议你按照“先解析、再端口、再站点、再反代、再代码、最后证书”这个顺序逐步推进每完成一步就用浏览器或curl验证一次不要等全部改完再一起看。另外有个很实用的小技巧域名解析生效之后先把后端端口从公网防火墙里去掉再重启后端确认只有宝塔反代能访问到它这个动作虽小但对项目整体安全性的提升非常明显。迁移完成后表面上只是换了个访问地址其实整个项目的入口边界和访问链路都变得更干净了。