用FPC实现HTTPS内网穿透

Ken Chang
8 min readFeb 21, 2023

用Docker封装Frp并通过Nginx反向代理HTTPS实现内网穿透

Photo by Clément Hélardot on Unsplash

最近在开发项目的时候,发现有些情况需要将自己主机的开发环境直接暴露给外网,并且还需要HTTPS 安全连接,比如:shopify第三方程序,微信小程序等等.

这时候,就需要一份内网穿透方案了,之前用过ngrok服务做转发,当然也可以用,不过现在他们商业化了,免费的用户比较慢,而且还是动态的域名地址,每次都要重新换一很麻烦.

另外还找到一个SSH命令行搭建工具localhost.run这个更方便,也不需要下载什么工具,直接通过SSH就可以一键完成了.不过缺点也是动态的域名,而且速度和稳定性都没测试过.

目前主流的方案还是使用frp,通过在一台有固定ip的服务器上搭建服务端,配置好转发规则,然后客户端根据配置连接.

FRP的实现逻辑如下:

使用Docker封装Frp

使用docker封装frp有很多好处:

  1. 方便管理,查看状态更方便
  2. 端口不会污染宿主机,比如我主机上有很多网站服务互不影响
  3. 不残留垃圾,迁移方便

前置需求

  • 需要有一太已经预装了docker服务的云主机
  • 需要有固定的域名(可以是二级域名),ip地址
  • 最好有两个二级域名,比如一个用来做管理界面,另外一个做项目代理
  • 需要申请好SSL证书(目前腾讯云和阿里云都有免费SSL证书可以申请,但是不支持泛域名)

制作docker容器

制作容器非常简单,但是最好用更方便的方法来做,比如docker-compose,因为我实在是不喜欢每次运行容器的时候输入一长串启动命令.

首先配置frps.ini文件:

fpc中必须要的配置有:

  • bind_port 用于与客户端作为通讯端口
  • token 用于验证客户端的身份
  • dashboard_* 管理端地址,域名,用户名,密码,这些都是用于管理界面使用的配置
  • vhost_http_port 是http服务的端口地址
  • vhost_https_port 是https服务的端口地址
  • subdomain_host 这个可以在客户端配置二级域名的地址,比如这里写aaa.com,客户端设置subdomain=dev,然后你就可以通过 dev.aaa.com来访问了

frps.ini如下:

[common]
bind_port = {{ .Envs.FRP_SERVER_PORT }}
token = {{ .Envs.FRP_SERVER_TOKEN }}
dashboard_addr = 0.0.0.0
vhost_http_port = 80
vhost_https_port = 443
dashboard_port = {{ .Envs.FRP_ADMIN_PORT }}
dashboard_user = {{ .Envs.FRP_ADMIN_USER }}
dashboard_pwd = {{ .Envs.FRP_ADMIN_PASS }}
subdomain_host = {{ .Envs.SUB_HOST }}

将自己需要的配置,使用环境变量来配置,这样更方便后面使用.

docker-compose.yml文件内容如下:

version: "3.7"
services:  frps:
restart: always
container_name: frps_https
image: snowdreamtech/frps
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
- ./frps.ini:/etc/frp/frps.ini:ro
ports:
- 7890:7890
- 8888:8888
- 4443:443
- 8080:80
environment:
- FRP_SERVER_PORT=7890
- FRP_SERVER_TOKEN=K1234567890
- FRP_ADMIN_PORT=8888
- FRP_ADMIN_USER=admin_123
- FRP_ADMIN_PASS=aaa123456
- SUB_HOST=dev.aaa.com
logging:
driver: "json-file"
options:
max-size: "1m"

配置说明:

volumes:- ./frps.ini:/etc/frp/frps.ini 是说将当前目录下的frps.ini文件映射到容器内的etc/frp/frps.ini(覆盖)

ports:中的端口用于映射容器内的端口服务,至少应该有4个:用于fpc的基础服务/用于管理端/用于http和用于https

environment:中的配置,根据frps.ini对应填写即可,需要注意的是,这里等号后面不可以有空格

容器封装:

  • docker-compose.ymlfrps.ini这两个文件放在服务器中的某个文件夹里.
  • cd 到目录中,然后执行 docker-compose up -d.如果没有报错基本就完成了
  • 可以通过docker ps -a查看端口地址映射情况,并且可以通过 docker logs <容器id>来查看执行情况,比如出现:frps started successfully表示已经成功运行
  • 这时候打开管理端界面(<域名>:8888)看看是否已经可以正常运行了

配置Nginx实现SSL转发

  • 将下载的*.crt文件和*.key文件上传到nginx配置文件夹,比如我放在conf.d文件夹下面的ssl子文件夹中

conf配置如下

server {
listen 80;
listen 443 ssl http2;
server_name dev.aaa.com;
ssl_certificate /etc/nginx/conf.d/ssl/dev.aaa.com.crt;
ssl_certificate_key /etc/nginx/conf.d/ssl/dev.aaa.com.key;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;
ssl_prefer_server_ciphers on;
ssl_session_timeout 10m;
ssl_session_cache builtin:1000 shared:SSL:10m;
ssl_buffer_size 1400;
add_header Strict-Transport-Security max-age=15768000;
ssl_stapling on;
ssl_stapling_verify on;
access_log /var/log/nginx/dev_https.aaa.com.access.log main;
error_log /var/log/nginx/dev_https.aaa.com.error.log warn;

if ($ssl_protocol = "") { return 301 https://$host$request_uri; }

location / {
#端口号一定要和frps.ini的vhost_http_port一致
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
#这里填写你的公网服务器IP
proxy_set_header X-Real-IP 8.8.8.8;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
  • 配置完成后,需要检查一下配置文件是否有错误nginx -t
  • 如果没有错误,则执行 ngixn -s reload重新载入配置
  • 可以再单独配置一个用于管理界面的nginx转发,将管理域名转发给上面配置的8888端口

客户端配置

frpc.ini 配置:

[common]
server_addr = 8.8.8.8 # 换成你主机的ip地址
server_port = 7890
token = K1234567890
[app_http]
type = http
remote_port = 80
local_ip = 127.0.0.1 # 你想要映射的内网ip地址
local_port = 3000 # 你想要映射的内网端口地址
custom_domains = dev.aaa.com
use_encryption = false
use_compression = true
[app_https]
type = https
remote_port = 443
local_ip = 127.0.0.1
local_port = 3000
custom_domains = dev.aaa.com
use_encryption = false
use_compression = true

客户端执行: ./frpc -c ./frpc.ini

如果没有报错,打开你配置的地址,应该就可以看到页面了

--

--

Ken Chang

I'm a designer who creates beautiful products and shares design insights on Medium.