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老师!

评论

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