Nginx 反代 FRP:部署本地 WordPress

以每年 99 元的低价用了两年阿里云服务器后,其性能瓶颈愈发明显:仅运行 WordPress 尚可,稍加负载便会卡死,漫长的重启过程更是令人煎熬。面对即将到来的高额续费,我选择将 WordPress 迁移至本地 Windows 主机,以发挥个人电脑的计算优势。云服务器则降级为纯粹的流量转发节点,通过 Nginx 和 FRP 对外提供服务。这种架构最大的好处是灵活性:未来更换云服务商,只需在新服务器上配置 Nginx 和 frps 即可,彻底告别了以往复杂且高风险的网站与数据库迁移工作。

优势:

  1. localhost访问速度
  2. 运用本地windows的高性能CPU计算资源、大容量存储、GPU需求
  3. 数据掌握在自己手里,不用受云服务商的监控
  4. 非常适合作为开发或测试环境,可以直接在本地开发,并通过公网进行预览和测试,无需频繁部署到云服务器。

缺点:

  1. 同时受限于云服务器的带宽和本地windows的上行流量,访问速度打折。除了带宽限制,数据包在 用户 -> 云 -> 本地 -> 云 -> 用户 之间往返,必然引入额外的网络延迟,对于交互性要求高的操作体验会有影响。
  2. 增加了故障点。云服务器、本地 Windows 主机、本地网络、frp 服务本身,任何一个环节出问题都会导致网站不可访问。
  3. 对本地网络的稳定性和公网 IP(如果是动态 IP 可能需要 DDNS)有依赖。

关键点

  1. Nginx的作用:在云服务器上处理HTTPS(TLS/SSL卸载)并将解密后的HTTP请求转发给frps。
  2. frps与frpc:frps(服务端)运行在云服务器上,frpc(客户端)运行在内网(或本地)机器上。frpc主动连接frps,建立一个稳定的隧道。
  3. 数据流:外部请求 -> Nginx -> frps -> frpc -> Apache -> WordPress,响应则反向流回。

流程

  1. 用户在浏览器输入域名。
  2. 浏览器向DNS服务器查询域名对应的IP地址。
  3. DNS服务器返回云服务器的公网IP地址。
  4. 浏览器向云服务器IP的80/443端口发起HTTP/HTTPS请求。
  5. 云服务器上的Nginx监听80/443端口,接收请求。如果是HTTPS(443),Nginx处理TLS握手并解密。
  6. Nginx根据配置,将HTTP请求反向代理到同台云服务器上监听xxxx端口的frps服务。
  7. frps通过预先由frpc建立的隧道,将请求转发给内网的frpc客户端。(frpc是主动连接到frps并保持连接的,请求是通过这个连接传输,而不是每次请求都重新握手)。
  8. frpc客户端接收到请求,根据配置将其转发给本机(localhost)上监听yyyy端口的Apache服务。
  9. Apache接收请求,调用PHP处理(通常通过mod_php或PHP-FPM),执行WordPress程序。
  10. WordPress处理请求并生成响应数据。
  11. 响应数据逐级返回:WordPress -> Apache -> frpc -> frps (通过隧道) -> Nginx -> 浏览器。
  12. Nginx将响应数据(如果是HTTPS)加密后发送给浏览器。
  13. 浏览器接收并渲染数据。
sequenceDiagram
    participant User as 用户/浏览器
    participant DNS as DNS服务器
    participant Nginx as 云服务器Nginx(公网IP:80/443)
    participant frps as 云服务器frps(监听xxxx)
    participant frpc as 内网frpc客户端
    participant Apache as 内网Apache(监听yyyy)
    participant WP as 内网WordPress

    Note right of frps: frpc预先连接frps建立隧道

    User->>DNS: 查询域名 A 的 IP 地址
    DNS-->>User: 返回 云服务器公网IP

    User->>Nginx: 1. 发起HTTPS请求 (访问域名A)
    Nginx->>Nginx: 2. TLS解密
    Nginx->>frps: 3. 反向代理HTTP请求 (转发到localhost:xxxx)
    frps->>frpc: 4. 通过隧道转发请求
    frpc->>Apache: 5. 转发请求到localhost:yyyy
    Apache->>WP: 6. 调用WordPress处理
    WP-->>Apache: 7. WordPress返回响应
    Apache-->>frpc: 8. Apache返回响应
    frpc-->>frps: 9. 通过隧道返回响应
    frps-->>Nginx: 10. frps返回响应
    Nginx->>Nginx: 11. TLS加密
    Nginx-->>User: 12. 返回HTTPS响应
    User->>User: 渲染页面

核心代码

Nginx反向代理设置

Nginx
#PROXY-START/
# 这个最好通过宝塔面板在前端操作,以免自己改代码和宝塔产生冲突

#PROXY-START/

location ^~ /
{
    proxy_pass http://127.0.0.1:xxxx; # Frps服务器的监听端口
    proxy_set_header Host $host; # 传递客户端(浏览器)请求的名字
    proxy_set_header X-Forwarded-Host $host; # 传递客户端(浏览器)请求的标准主机名
    proxy_set_header X-Real-IP $remote_addr; # 传递客户端真实ip
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 传递客户端ip和代理的ip
    # --- 关键修改:添加/确保正确的协议和端口头 ---
    proxy_set_header X-Forwarded-Proto $scheme; # 传递协议
    proxy_set_header X-Forwarded-Port $server_port; # 传递监听端口
    # --- 关键修改结束 ---
    # proxy_set_header REMOTE-HOST $remote_addr; # 这个头通常不是必需的
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade"; # 修改为 "upgrade" 以支持 WebSocket
    proxy_http_version 1.1;
    proxy_redirect off; # wordpress让关闭重定向

    # --- 移除/注释掉宝塔自动添加的缓存和静态文件判断逻辑 ---
    # add_header X-Cache $upstream_cache_status;
    # set $static_fileqalaflpo 0;
    # if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" )
    # {
    #   set $static_fileqalaflpo 1;
    #   expires 1m;
    # }
    # if ( $static_fileqalaflpo = 0 )
    # {
    #    add_header Cache-Control no-cache;
    # }
    # --- 结束移除/注释 ---
}

#PROXY-END/
proxy.conf

以下是wordpress官方文档的推荐配置(适用于反向代理):

JavaScript
location / {
    proxy_pass http://your_host_name:your_port;
    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-Host $server_name;
    proxy_set_header X-Forwarded-Proto $scheme; //这个告诉frps服务端协议是https还是http
    proxy_redirect off; //这个很重要,关闭nginx本身对于域名的重定向,以免跟wordpress的重定向规则冲突
}
JavaScript

但是要注意:
proxy_set_header X-Forwarded-Host $host; # 传递客户端(浏览器)请求的标准主机名

上面这一行要注意尽量是 $host,而非$server_name,因为在前面的nginx.conf中很可能配置了多个server_name,这时候如果用proxy_set_header X-Forwarded-Host传递的默认是第一个,如果你刚好跟浏览器输入的域名不一样(比如是ip地址),就会导致一些静态资源到时候以https://ip地址/xxx的形式请求。


frp内网穿透

这里我遇到一个大坑啊,按理说,上面通过proxy_set_header X-Forwarded-Proto $scheme已经将协议(http or https)传递给了frps,frps应该原样传递给frpc,我的SSL证书是部署在nginx所在的云服务器的,nginx解析https内容后,以http的方式给frps,进而我本地应该让wordpress通过传递来的 $scheme的值调整内部url处理方式全部https(即便接收到的数据实际上是来自http的)但是实测下来,frpc接收到的X-Forwarded-Proto却是http。排查了好久好久,在github的讨论区找到一个issue,终于发现是新版会自动把https转成http,于是回退到0.51.3才解决问题。

Frps
bindPort = zzzz # 需要跟frpc设置保持一致
vhostHTTPPort = xxxx # 跟Nginx设置保持一致
auth.token = "your_secret_token"    # 必须加,网络安全大于天
transport.tls.force = true # 强制TLS加密
frps.ini

Frpc
serverAddr = "xx.xxx.xxx.x"  # 公网ip地址
serverPort = zzzz # 需要跟frps设置保持一致
auth.token = "your_secret_token"    # 添加认证token,需要与服务器端配置相同

[[proxies]]
name = "web"
type = "http" # 证书是在云服务器而非本地,frps和frpc之间通过http通信
localPort = 80  # 确认Apache监听一致的端口
customDomains = ["your_domain.com"]

[transport]
proxyURL = "socks5://127.0.0.1:1080" # 可选,如果Localhost开启了代理,可以通过这个控制是否走代理
frpc.ini

wordpress配置

wordpress的矛盾点在于,实际接收到的都是已经经过云服务器nginx解析后的http数据,却自以为是https(因为访客从浏览器输入https://domain.com访问),引起mixed content错误。我的做法是,就保持后台siteurl和home的值为”http://localhost”,这样的好处是,本机直接访问localhost一点问题都没有,它不走frpc的流量,充分发挥localhost的便捷优势。而从https域名访问时,在wp-config.php加几行额外判断:

PHP
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
    $_SERVER['HTTPS'] = 'on';
    $_SERVER['HTTP_HOST'] = $_SERVER['HTTP_X_FORWARDED_HOST'];
    define('WP_HOME', 'https://' . $_SERVER['HTTP_HOST']);
    define('WP_SITEURL', 'https://' . $_SERVER['HTTP_HOST']);
    define('FORCE_SSL_ADMIN', true);
}


/* That's all, stop editing! Happy publishing. */
wp-config.php

以上代码的意思是,如果frpc接收到的HTTP_X_FORWARDED_PROTO协议是https(即从浏览器输入域名访问的情况),就强制让wordpress把内部链接头全部替换成https,同时根据frpc接收到的HTTP_HOST(即域名)修改home和siteurl的值为https://域名,从而让所有浏览器请求的资源都以https://域名/xxxxxx.xx开头,不会出现mixed content错误。(友情提示,如果不解决https和http绕来绕去的关系,不仅是出现mixed content,还可能因为wordpress内部的url检查导致某些页面,如首页或后台首页,陷入301/302重定向循环),我开始找了一堆插件希望能解决这个问题,后来发现还是上面的代码最直观和一劳永逸。


按惯例,致敬伟大的AI老师!


PS. 后来某次wordpress版本更新以后,以上流程出了一点点问题:“输入fanyiming.life/wp-admin会自动跳转localhost的登录页面(导致无法远程访问),但是其他页面的跳转都是正常的,不会被重定向到localhost。”

Gemini老师给出的解决方案如下,已采纳:

您好!您遇到的这个问题非常典型,是反向代理(特别是您这种 Nginx -> FRP -> 本地服务器 的多层代理)部署 WordPress 时常见的一个痛点。

您提供的文档非常详尽,对整个架构的理解也很到位。问题的原因基本可以锁定在 WordPress 本身处理后台登录和重定向的机制上。

问题根源分析

简单来说,问题出在 WordPress 的 wp-login.php 和后台区域 /wp-admin/ 有自己独立的、更严格的重定向和 URL 判断逻辑。

  1. 数据库中的 “权威” 地址:您在 WordPress 后台设置的 “站点地址 (URL)” 和 “WordPress 地址 (URL)” 是 http://localhost。这对 WordPress 来说,是它身份的“最终权威”。
  2. 前台页面的“欺骗”成功:对于网站前台页面(如文章、页面),您在 wp-config.php 中添加的代码成功地“欺骗”了 WordPress。当请求通过 fanyiming.life 访问时,HTTP_X_FORWARDED_PROTO 和 HTTP_X_FORWARDED_HOST 这两个 Header 让 WordPress 临时认为自己的地址是 https://fanyiming.life,并正确地生成了页面内的链接,所以前台访问正常。
  3. 后台登录的“叛逆”:当您访问 fanyiming.life/wp-admin 时,WordPress 会触发登录验证流程。这个流程中的某些核心函数(例如 wp_login_url() 或其他内部重定向函数)可能没有完全遵循 wp-config.php 中 define 的 WP_SITEURL,而是直接或间接地读取了数据库中那个“权威”的 http://localhost 地址,然后生成了一个指向 http://localhost/wp-login.php?... 的重定向指令(HTTP 302 Found)。您的浏览器收到这个指令后,就乖乖地跳转到了 localhost 的登录页。

总结: 您的 wp-config.php 配置对前台生效了,但后台的登录和管理区域因为其自身的安全和重定向机制,“识破”了这个“骗局”,回归了数据库里的本源地址 http://localhost


解决方案

有两种解决方案,强烈推荐方案一,因为它是一劳永逸且最规范的做法。

方案一:【推荐】统一内外网访问地址,修改本地 hosts 文件

这是最稳定、最根本的解决方案。核心思想是让 WordPress 从始至终只认一个地址 https://fanyiming.life,然后通过修改您本地电脑的 hosts 文件来实现快速的本地访问。

步骤 1:修改 WordPress 数据库中的地址

  1. 登录您的 WordPress 后台(现在可能需要先用方案二临时修复,或者直接在本地通过 http://localhost/wp-admin 登录)。
  2. 进入 设置 -> 常规
  3. 将 WordPress 地址 (URL) 和 站点地址 (URL) 全部修改为您的公网域名:https://fanyiming.life
  4. 保存更改。

步骤 2:修改本地 Windows 的 hosts 文件

这个文件的作用是在您的电脑上建立一个本地的 DNS 记录,让域名直接指向本地 IP。

  1. 管理员权限打开记事本(Notepad)。
  2. 在记事本中,点击 文件 -> 打开,然后导航到 C:\Windows\System32\drivers\etc\
  3. 在右下角文件类型选择“所有文件(*.*)”,然后打开 hosts 文件。
  4. 在文件的末尾添加新的一行:TEXT127.0.0.1 fanyiming.life
  5. 保存并关闭文件。

步骤 3:调整 wp-config.php

现在 WordPress 的“权威”地址已经是正确的公网地址了,wp-config.php 里的 define 就不再是必须的了。您可以将之前添加的 define('WP_HOME', ...) 和 define('WP_SITEURL', ...) 这两行删除或注释掉。但为了确保反向代理下的 HTTPS 能被正确识别,保留其他部分仍然是好习惯。

简化后的 wp-config.php 判断逻辑可以这样:

PHPif (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
    $_SERVER['HTTPS'] = 'on';
}

/* That's all, stop editing! Happy publishing. */

FORCE_SSL_ADMIN 也可以保留,因为它现在会正确地将 http 请求重定向到 https://fanyiming.life/wp-admin

这样做的好处:

彻底解决问题:所有由 URL 混乱导致的问题(包括后台重定向、AJAX 请求失败、插件兼容性问题等)都会消失

配置统一:WordPress 的配置变得非常干净,不再有两套地址的冲突。

内外兼得

当您在本地电脑的浏览器访问 fanyiming.life 时,hosts 文件会让请求直接发送到 127.0.0.1,不经过云服务器和 FRP,实现您想要的本地极速访问和开发

外部用户访问 fanyiming.life 时,他们通过公网 DNS 解析到您的云服务器,流程和原来一样,可以正常访问。

一个小提示 (A Small Tip):

当您在本地通过 https://fanyiming.life 访问时,由于您的 SSL 证书部署在云服务器的 Nginx 上,而本地 Apache 服务器上没有这个证书,您的浏览器可能会提示“不安全”或“证书错误”。

  • 对于开发测试:直接点击“高级” -> “继续前往”即可,这不影响功能。
  • 如果想消除这个警告:您可以在本地也配置一个 SSL 证书(例如使用 mkcert 工具生成一个本地信任的自签名证书),但这会增加一些配置复杂度,通常没有必要。

评论

您的邮箱地址不会被公开。 必填项已用 * 标注