FRP 内网穿透完整实战:从 HTTP 映射到 HTTPS 自签代理

image.png

FRP 内网穿透完整实战:从 HTTP 映射到 HTTPS 自签代理

在本地开发时,我们经常会遇到这样的需求:

  • 本地运行了一个 Vite、Vue、React 或 Node.js 服务;
  • 服务只能通过 127.0.0.1 访问;
  • 希望让外网设备、客户或同事访问;
  • 除了普通 HTTP,还希望提供 HTTPS 地址;
  • 希望 FRP 服务端和客户端都支持开机自启动。

本文将从零开始搭建一套完整的 FRP 内网穿透环境,并在公网服务器上通过 Nginx 为 FRP 服务增加 HTTPS 入口。


一、最终实现效果

假设本地 Mac 上运行了三个 HTTP 服务:

1
2
3
http://127.0.0.1:5173
http://127.0.0.1:5174
http://127.0.0.1:5175

公网服务器信息:

1
2
公网 IP:172.22.22.22
公网域名:custom.com

最终提供六个公网访问入口。

HTTP 访问地址

1
2
3
http://custom.com:30001
http://custom.com:30002
http://custom.com:30003

HTTPS 访问地址

1
2
3
https://custom.com:31001
https://custom.com:31002
https://custom.com:31003

端口映射关系如下:

本地服务 HTTP 公网端口 HTTPS 公网端口 HTTPS 内部 FRP 端口
127.0.0.1:5173 30001 31001 32001
127.0.0.1:5174 30002 31002 32002
127.0.0.1:5175 30003 31003 32003

二、整体架构

HTTP 链路

1
2
3
4
5
6
7
8
9
10
11
12
外网浏览器

│ http://custom.com:30001

公网服务器 frps:30001

│ FRP 隧道

本地 Mac frpc


http://127.0.0.1:5173

HTTPS 链路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
外网浏览器

│ https://custom.com:31001

公网服务器 Nginx:31001

│ HTTP 反向代理

公网服务器 frps:32001

│ FRP 隧道

本地 Mac frpc


http://127.0.0.1:5173

完整链路可以理解为:

1
2
3
4
5
6
7
8
HTTP  30001 → FRP → 本地 5173
HTTPS 31001 → Nginx → FRP 32001 → 本地 5173

HTTP 30002 → FRP → 本地 5174
HTTPS 31002 → Nginx → FRP 32002 → 本地 5174

HTTP 30003 → FRP → 本地 5175
HTTPS 31003 → Nginx → FRP 32003 → 本地 5175

这里需要注意:

  • 30001~30003 由 FRP 直接对外提供 HTTP。
  • 31001~31003 由 Nginx 对外提供 HTTPS。
  • 32001~32003 是 Nginx 转发到 FRP 的内部上游端口。
  • HTTPS 由公网服务器上的 Nginx 负责终止。
  • 本地服务仍然只需要运行普通 HTTP。

三、准备条件

需要准备:

  1. 一台拥有公网 IP 的 Linux 服务器。
  2. 一个解析到公网服务器的域名。
  3. 本地 Mac 上能够正常访问的 HTTP 服务。
  4. 云服务器安全组允许相关端口。
  5. FRP 服务端和客户端尽量使用相同版本。

本文示例:

1
2
3
4
FRP 版本:0.69.1
公网 IP:172.22.22.22
公网域名:custom.com
FRP 控制端口:7000

先给域名添加 DNS 解析:

1
2
3
记录类型:A
主机记录:@
记录值:172.22.22.22

检查解析:

1
dig +short custom.com

预期返回:

1
172.22.22.22

服务端安装 frps

四、确认服务器架构

登录公网服务器:

1
ssh root@172.22.22.22

查看服务器架构:

1
uname -m

对应关系:

系统输出 FRP 安装包
x86_64 linux_amd64
aarch64 linux_arm64
arm64 linux_arm64

下面以 linux_amd64 为例。


五、下载 FRP

设置版本变量:

1
FRP_VERSION=0.69.1

使用 wget 下载:

1
wget "https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/frp_${FRP_VERSION}_linux_amd64.tar.gz"

没有 wget 时,可以使用:

1
curl -LO "https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/frp_${FRP_VERSION}_linux_amd64.tar.gz"

解压:

1
tar -xzvf "frp_${FRP_VERSION}_linux_amd64.tar.gz"

进入目录:

1
cd "frp_${FRP_VERSION}_linux_amd64"

检查版本:

1
./frps -v

六、安装 frps

创建配置目录:

1
sudo mkdir -p /etc/frp

复制程序:

1
2
sudo cp ./frps /usr/local/bin/frps
sudo chmod +x /usr/local/bin/frps

确认安装结果:

1
/usr/local/bin/frps -v

七、生成认证 Token

执行:

1
openssl rand -hex 32

输出示例:

1
8e1d9cc7f7c8b0562f4f984b0fb930a66a5b2d9f48045a554d8f8ef99e9c503d

保存这个 Token,服务端和客户端必须使用相同值。

不要把真实 Token 提交到公开仓库或发布到文章中。


八、创建 frps 配置

创建配置文件:

1
sudo vim /etc/frp/frps.toml

写入:

1
2
3
4
5
6
7
8
9
10
# frpc 连接 frps 的控制端口
bindPort = 7000

# Token 认证
auth.method = "token"
auth.token = "替换成你的随机Token"

# 日志输出到 systemd journal
log.to = "console"
log.level = "info"

这里不配置:

1
proxyBindAddr = "127.0.0.1"

因为本文中的 30001~30003 需要由 FRP 直接对公网提供 HTTP。

32001~32003 虽然也由 FRP 监听,但不应该在云安全组中对公网开放。


九、校验 frps 配置

1
sudo /usr/local/bin/frps verify -c /etc/frp/frps.toml

正常输出类似:

1
frps: the configuration file /etc/frp/frps.toml syntax is ok

十、创建 frps systemd 服务

创建服务文件:

1
sudo vim /etc/systemd/system/frps.service

写入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[Unit]
Description=FRP Server
Documentation=https://github.com/fatedier/frp
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart=/usr/local/bin/frps -c /etc/frp/frps.toml

Restart=always
RestartSec=5

LimitNOFILE=1048576
NoNewPrivileges=true
PrivateTmp=true

[Install]
WantedBy=multi-user.target

重新加载 systemd:

1
sudo systemctl daemon-reload

启动服务并设置开机自启:

1
sudo systemctl enable --now frps

查看状态:

1
sudo systemctl status frps

正常应看到:

1
Active: active (running)

确认开机自启:

1
systemctl is-enabled frps

预期输出:

1
enabled

确认运行状态:

1
systemctl is-active frps

预期输出:

1
active

十一、frps 常用管理命令

启动:

1
sudo systemctl start frps

停止:

1
sudo systemctl stop frps

重启:

1
sudo systemctl restart frps

查看状态:

1
sudo systemctl status frps

查看实时日志:

1
sudo journalctl -u frps -f

查看最近 100 行日志:

1
sudo journalctl -u frps -n 100 --no-pager

修改 frps.toml 后:

1
2
3
sudo /usr/local/bin/frps verify -c /etc/frp/frps.toml && \
sudo systemctl restart frps && \
sudo systemctl status frps

如果修改的是 frps.service

1
2
3
sudo systemctl daemon-reload && \
sudo systemctl restart frps && \
sudo systemctl status frps

查找所有以 frp 开头的 systemd 服务:

1
systemctl list-unit-files --type=service | grep -i '^frp'

客户端安装 frpc

十二、下载 Mac 客户端

Apple Silicon Mac 使用 darwin_arm64

1
2
3
4
5
6
7
8
9
cd ~/Downloads

FRP_VERSION=0.69.1

curl -LO "https://github.com/fatedier/frp/releases/download/v${FRP_VERSION}/frp_${FRP_VERSION}_darwin_arm64.tar.gz"

tar -xzvf "frp_${FRP_VERSION}_darwin_arm64.tar.gz"

cd "frp_${FRP_VERSION}_darwin_arm64"

检查版本:

1
./frpc -v

Intel Mac 应使用:

1
darwin_amd64

十三、配置单个 HTTP 服务

如果只需要把本地 5173 映射到公网 30001,可以使用最小配置。

创建:

1
vim frpc.toml

写入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
serverAddr = "custom.com"
serverPort = 7000

auth.method = "token"
auth.token = "替换成和服务端相同的随机Token"

log.to = "console"
log.level = "info"

[[proxies]]
name = "local-5173-http"
type = "tcp"

localIP = "127.0.0.1"
localPort = 5173

remotePort = 30001

transport.useEncryption = true

映射关系:

1
2
3
http://custom.com:30001

http://127.0.0.1:5173

校验:

1
./frpc verify -c ./frpc.toml

启动:

1
./frpc -c ./frpc.toml

十四、同时配置 HTTP 和 HTTPS 上游

如果同一个本地服务既要提供 HTTP,又要提供 HTTPS,需要为它配置两个 FRP 代理。

完整 frpc.toml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
serverAddr = "custom.com"
serverPort = 7000

auth.method = "token"
auth.token = "替换成和服务端相同的随机Token"

log.to = "console"
log.level = "info"

# ==================================================
# 本地服务 5173
# ==================================================

# HTTP:http://custom.com:30001
[[proxies]]
name = "service-5173-http"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5173
remotePort = 30001
transport.useEncryption = true

# HTTPS 上游:Nginx 31001 → FRP 32001
[[proxies]]
name = "service-5173-https-upstream"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5173
remotePort = 32001
transport.useEncryption = true

# ==================================================
# 本地服务 5174
# ==================================================

# HTTP:http://custom.com:30002
[[proxies]]
name = "service-5174-http"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5174
remotePort = 30002
transport.useEncryption = true

# HTTPS 上游:Nginx 31002 → FRP 32002
[[proxies]]
name = "service-5174-https-upstream"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5174
remotePort = 32002
transport.useEncryption = true

# ==================================================
# 本地服务 5175
# ==================================================

# HTTP:http://custom.com:30003
[[proxies]]
name = "service-5175-http"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5175
remotePort = 30003
transport.useEncryption = true

# HTTPS 上游:Nginx 31003 → FRP 32003
[[proxies]]
name = "service-5175-https-upstream"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5175
remotePort = 32003
transport.useEncryption = true

校验配置:

1
./frpc verify -c ./frpc.toml

启动:

1
./frpc -c ./frpc.toml

配置 HTTPS 自签证书

十五、为什么还需要 Nginx

FRP TCP 代理只负责转发 TCP 流量,它不会自动为本地 HTTP 服务增加 HTTPS。

因此需要在公网服务器增加 Nginx:

1
2
3
4
5
6
7
HTTPS 请求

Nginx 负责 TLS 解密

转发到 FRP 的 HTTP 上游端口

本地 HTTP 服务

本地服务仍然可以保持:

1
http://127.0.0.1:5173

不需要给 Vite、Vue 或 Node.js 单独配置证书。


十六、生成自签证书

在准备存放证书的目录中执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
DOMAIN="custom.com"

openssl req \
-x509 \
-nodes \
-newkey rsa:2048 \
-sha256 \
-days 3650 \
-keyout "./$DOMAIN.key" \
-out "./$DOMAIN.crt" \
-subj "/CN=$DOMAIN" \
-addext "subjectAltName=DNS:$DOMAIN" && \
chmod 600 "./$DOMAIN.key"

生成结果:

1
2
./custom.com.crt
./custom.com.key

检查证书:

1
2
3
4
5
6
7
openssl x509 \
-in ./custom.com.crt \
-noout \
-subject \
-issuer \
-dates \
-ext subjectAltName

应当包含:

1
DNS:custom.com

需要注意:

自签证书即使域名完全匹配,浏览器仍然会提示证书不受信任。
这是因为证书不是由系统信任的 CA 机构签发。

测试时可以使用:

1
curl -k

正式使用建议改为 Let’s Encrypt 或其他受信任证书。


Docker 部署 Nginx

十七、为什么推荐使用 host 网络

如果 Nginx 运行在 Docker 容器中,而 frps 运行在宿主机 systemd 中,那么容器内的:

1
127.0.0.1

默认表示 Nginx 容器自身,不是宿主机。

使用 Docker host 网络后:

1
network_mode: host

Nginx 容器与宿主机共用网络栈。

这样容器内可以直接访问:

1
2
3
127.0.0.1:32001
127.0.0.1:32002
127.0.0.1:32003

同时也不需要逐个配置 Docker ports 映射。


十八、Nginx Docker Compose 配置

目录结构示例:

1
2
3
4
5
6
7
nginx/
├── docker-compose.yml
└── conf.d/
├── frp.conf
└── cert/
├── custom.com.crt
└── custom.com.key

docker-compose.yml

1
2
3
4
5
6
7
8
9
10
services:
nginx:
image: nginx:1.29
container_name: nginx
restart: always

network_mode: host

volumes:
- ./conf.d:/etc/nginx/conf.d:ro

使用 host 网络后,不要再配置:

1
2
ports:
- "31001:31001"

因为 host 网络模式下,Nginx 会直接监听宿主机端口。

启动:

1
docker compose up -d

修改 Compose 配置后重新创建:

1
2
docker compose down
docker compose up -d

查看日志:

1
docker logs nginx -n 200 -f

十九、创建 Nginx HTTPS 配置

创建:

1
vim conf.d/frp.conf

写入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}

# ==================================================
# HTTPS 31001 → FRP 32001 → 本地 5173
# ==================================================

server {
listen 31001 ssl;
listen [::]:31001 ssl;

server_name custom.com;

ssl_certificate /etc/nginx/conf.d/cert/custom.com.crt;
ssl_certificate_key /etc/nginx/conf.d/cert/custom.com.key;

ssl_protocols TLSv1.2 TLSv1.3;

location / {
proxy_pass http://127.0.0.1:32001;

proxy_http_version 1.1;

proxy_set_header Host $http_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 https;
proxy_set_header X-Forwarded-Port 31001;

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

proxy_buffering off;
proxy_cache off;

proxy_connect_timeout 10s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
}
}

# ==================================================
# HTTPS 31002 → FRP 32002 → 本地 5174
# ==================================================

server {
listen 31002 ssl;
listen [::]:31002 ssl;

server_name custom.com;

ssl_certificate /etc/nginx/conf.d/cert/custom.com.crt;
ssl_certificate_key /etc/nginx/conf.d/cert/custom.com.key;

ssl_protocols TLSv1.2 TLSv1.3;

location / {
proxy_pass http://127.0.0.1:32002;

proxy_http_version 1.1;

proxy_set_header Host $http_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 https;
proxy_set_header X-Forwarded-Port 31002;

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

proxy_buffering off;
proxy_cache off;

proxy_connect_timeout 10s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
}
}

# ==================================================
# HTTPS 31003 → FRP 32003 → 本地 5175
# ==================================================

server {
listen 31003 ssl;
listen [::]:31003 ssl;

server_name custom.com;

ssl_certificate /etc/nginx/conf.d/cert/custom.com.crt;
ssl_certificate_key /etc/nginx/conf.d/cert/custom.com.key;

ssl_protocols TLSv1.2 TLSv1.3;

location / {
proxy_pass http://127.0.0.1:32003;

proxy_http_version 1.1;

proxy_set_header Host $http_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 https;
proxy_set_header X-Forwarded-Port 31003;

proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;

proxy_buffering off;
proxy_cache off;

proxy_connect_timeout 10s;
proxy_send_timeout 3600s;
proxy_read_timeout 3600s;
}
}

说明:

1
listen 31001 ssl;

表示监听 IPv4 的 31001 HTTPS 端口。

1
listen [::]:31001 ssl;

表示监听 IPv6 的 31001 HTTPS 端口。

只需要 IPv4 时,可以只保留:

1
listen 31001 ssl;

二十、检查 Nginx 配置

查看 Nginx 是否加载了 frp.conf

1
docker exec -it nginx nginx -T

只查找相关配置:

1
docker exec -it nginx nginx -T 2>&1 | grep -A 20 -B 2 "frp.conf"

测试语法:

1
docker exec -it nginx nginx -t

正常输出:

1
nginx: configuration file /etc/nginx/nginx.conf test is successful

重新加载:

1
docker exec -it nginx nginx -s reload

或者直接重启:

1
docker restart nginx

开放服务器端口

二十一、云安全组配置

需要开放以下 TCP 端口:

端口 是否公网开放 用途
7000 frpc 连接 frps
30001~30003 HTTP 公网入口
31001~31003 HTTPS 公网入口
32001~32003 Nginx 内部转发到 FRP
5173~5175 本地 Mac 服务

推荐规则:

协议 端口范围 来源
TCP 7000 建议限制为本地出口 IP
TCP 30001-30003 根据实际访问范围
TCP 31001-31003 根据实际访问范围

不要在云安全组中开放:

1
32001-32003

因为这些端口只供公网服务器上的 Nginx 使用。


二十二、服务器防火墙配置

使用 UFW:

1
2
3
4
sudo ufw allow 7000/tcp
sudo ufw allow 30001:30003/tcp
sudo ufw allow 31001:31003/tcp
sudo ufw reload

查看规则:

1
sudo ufw status numbered

使用 firewalld:

1
2
3
4
sudo firewall-cmd --permanent --add-port=7000/tcp
sudo firewall-cmd --permanent --add-port=30001-30003/tcp
sudo firewall-cmd --permanent --add-port=31001-31003/tcp
sudo firewall-cmd --reload

验证链路

二十三、检查服务端监听端口

执行:

1
sudo ss -lntp | grep -E '7000|3000[1-3]|3100[1-3]|3200[1-3]'

预期结果:

1
2
3
4
frps 监听:7000
frps 监听:30001~30003
frps 监听:32001~32003
nginx 监听:31001~31003

二十四、测试 FRP HTTP 入口

1
2
3
curl -v http://custom.com:30001
curl -v http://custom.com:30002
curl -v http://custom.com:30003

二十五、测试 HTTPS 内部上游

在公网服务器执行:

1
2
3
curl -v http://127.0.0.1:32001
curl -v http://127.0.0.1:32002
curl -v http://127.0.0.1:32003

如果这里访问失败,说明问题位于:

1
frps → frpc → 本地服务

常见原因:

  • 本地服务没有启动;
  • frpc 没有启动;
  • remotePort 配置错误;
  • localPort 配置错误;
  • Token 不一致;
  • FRP 客户端连接失败。

二十六、测试 HTTPS 入口

因为是自签证书,需要添加 -k

1
2
3
curl -vk https://custom.com:31001
curl -vk https://custom.com:31002
curl -vk https://custom.com:31003

如果返回:

1
502 Bad Gateway

说明:

1
浏览器 → Nginx

已经成功,但:

1
Nginx → FRP 32001

访问失败。

如果返回:

1
Connection refused

重点检查:

  • Nginx 是否监听 31001
  • 云安全组是否开放 31001
  • Linux 防火墙是否开放 31001
  • Nginx Docker 是否使用 host 网络;
  • Nginx 配置是否成功加载。

最终配置汇总

二十七、服务端 frps.toml

1
2
3
4
5
6
7
bindPort = 7000

auth.method = "token"
auth.token = "替换成你的随机Token"

log.to = "console"
log.level = "info"

二十八、客户端 frpc.toml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
serverAddr = "custom.com"
serverPort = 7000

auth.method = "token"
auth.token = "替换成和服务端相同的随机Token"

log.to = "console"
log.level = "info"

# 本地 5173:HTTP + HTTPS
[[proxies]]
name = "service-5173-http"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5173
remotePort = 30001
transport.useEncryption = true

[[proxies]]
name = "service-5173-https-upstream"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5173
remotePort = 32001
transport.useEncryption = true

# 本地 5174:HTTP + HTTPS
[[proxies]]
name = "service-5174-http"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5174
remotePort = 30002
transport.useEncryption = true

[[proxies]]
name = "service-5174-https-upstream"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5174
remotePort = 32002
transport.useEncryption = true

# 本地 5175:HTTP + HTTPS
[[proxies]]
name = "service-5175-http"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5175
remotePort = 30003
transport.useEncryption = true

[[proxies]]
name = "service-5175-https-upstream"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5175
remotePort = 32003
transport.useEncryption = true

二十九、最终访问地址

HTTP:

1
2
3
http://custom.com:30001
http://custom.com:30002
http://custom.com:30003

HTTPS:

1
2
3
https://custom.com:31001
https://custom.com:31002
https://custom.com:31003

最终映射关系:

1
2
3
4
5
6
7
8
HTTP  30001 → FRP → 本地 5173
HTTPS 31001 → Nginx → FRP 32001 → 本地 5173

HTTP 30002 → FRP → 本地 5174
HTTPS 31002 → Nginx → FRP 32002 → 本地 5174

HTTP 30003 → FRP → 本地 5175
HTTPS 31003 → Nginx → FRP 32003 → 本地 5175

常见问题

1. 为什么 HTTPS 不能直接使用 FRP 的 30001 端口

同一个 TCP 端口不能同时由 frps 和 Nginx 监听。

如果 30001 已经被 frps 占用,Nginx 就不能再监听:

1
listen 30001 ssl;

所以本文采用:

1
2
3
HTTP:30001
HTTPS:31001
HTTPS 内部 FRP:32001

2. 为什么 HTTPS 需要额外配置一个 32001

因为 31001 已经由 Nginx 占用。

Nginx 接收到 HTTPS 请求后,需要有另一个 FRP 端口把请求转发到本地,因此使用:

1
32001

完整链路:

1
31001 → Nginx → 32001 → FRP → 5173

3. 为什么 Docker 中不能直接访问宿主机 127.0.0.1

普通 Docker 网络模式下:

1
容器中的 127.0.0.1 = 容器自身

不代表宿主机。

使用:

1
network_mode: host

后,容器与宿主机共用网络栈,因此可以直接访问:

1
127.0.0.1:32001

4. 为什么 Nginx 日志没有显示加载 frp.conf

Nginx 启动日志默认不会逐个打印所有加载的配置文件。

应使用:

1
docker exec -it nginx nginx -T

查看最终生效配置。


5. 为什么自签证书浏览器仍然提示风险

因为自签证书不是由浏览器信任的 CA 机构签发。

即使证书中的域名完全正确:

1
DNS:custom.com

浏览器仍然会提示:

1
证书颁发机构不受信任

可以:

  • 在客户端系统中导入并信任该证书;
  • 或者使用 Let’s Encrypt 等受信任证书。

6. 修改配置后如何更新服务

修改 frps:

1
2
sudo /usr/local/bin/frps verify -c /etc/frp/frps.toml && \
sudo systemctl restart frps

修改 Nginx 配置:

1
2
docker exec -it nginx nginx -t && \
docker exec -it nginx nginx -s reload

修改 Docker Compose:

1
2
docker compose down
docker compose up -d

总结

这套方案把 FRP 与 Nginx 的职责进行了清晰拆分:

  • FRP 负责公网服务器和本地 Mac 之间的内网穿透;
  • Nginx 负责公网 HTTPS、证书和反向代理;
  • 本地应用只需要运行普通 HTTP;
  • HTTP 和 HTTPS 可以同时保留;
  • Docker host 网络可以减少端口映射和宿主机访问配置。

核心端口规划:

1
2
3
4
5
7000         FRP 控制连接
30001-30003 公网 HTTP
31001-31003 公网 HTTPS
32001-32003 HTTPS 对应的 FRP 内部上游
5173-5175 本地应用端口

最终可以通过下面的地址访问本地服务:

1
2
http://custom.com:30001
https://custom.com:31001

并按照同样的规则继续扩展更多端口和本地服务。