Skip to content

Nginx 代理请求到 frp 做内网穿透

frp 安装

frp 安装:https://gofrp.org/zh-cn/docs/setup/

使用 systemd 命令管理 frps 服务:https://gofrp.org/zh-cn/docs/setup/systemd/

注:客户端和服务端可以分别配置 frps.service 和 frpc.service,配置步骤一样。

frp 配置和使用

frps.service

vi /usr/lib/systemd/system/frps.service

shell
[Unit]
Description = frp server
After = network.target syslog.target
Wants = network.target

[Service]
Type = simple
# 启动frps的命令,需修改为您的frps的安装路径
ExecStart = /opt/frp/frps -c /opt/frp/frps.toml

[Install]
WantedBy = multi-user.target

frpc.service

vi /usr/lib/systemd/system/frpc.service

shell
[Unit]
Description = frp client
After = network.target syslog.target
Wants = network.target

[Service]
Type = simple
# 启动frps的命令,需修改为您的frps的安装路径
ExecStart = /opt/frp/frpc -c /opt/frp/frpc.toml

[Install]
WantedBy = multi-user.target

systemd 命令管理 frps.service 服务

shell
# 设置 frps 开机自启动
systemctl enable frps
# 启动frps
sudo systemctl start frps
# 停止frps
sudo systemctl stop frps
# 重启frps
sudo systemctl restart frps
# 查看frps状态
sudo systemctl status frps

注:frpc.service 同理。

frp 内网穿透示例

frps.toml (vhostHTTPPort 使用 81 端口,避免和 nginx 冲突)

toml
bindPort = 13462
vhostHTTPPort = 81
#vhostHTTPSPort = 443
auth.token = "abcdef"

frpc.toml (serverAddr 填写实际的服务器端IP或域名)

toml
serverAddr = "aday.fun"
serverPort = 13462
auth.token = "abcdef"

# 以下为代理 ssh 端口
[[proxies]]
name = "ssh"
type = "tcp"
localIP = "127.0.0.1"
localPort = 22
remotePort = 13463


# 以下为代理为 http
[[proxies]]
name = "home_http"
type = "http"
localPort = 8001
customDomains = ["home.aday.fun"]

[[proxies]]
name = "vitality_http"
type = "http"
localPort = 8080
customDomains = ["vitality.aday.fun"]

[[proxies]]
name = "jenkins_http"
type = "http"
localPort = 8090
customDomains = ["jenkins.aday.fun"]


# 以下为代理为 https
# http 配置可和 https 配置同时存在,以达到同时支持两种协议的目的
[[proxies]]
name = "home"
type = "https"
customDomains = ["home.aday.fun"]
[proxies.plugin]
type = "https2http"
localAddr = "127.0.0.1:8001"
crtPath = "/etc/nginx/ssl/aday.crt"
keyPath = "/etc/nginx/ssl/aday.key"
hostHeaderRewrite = "home.aday.fun"
requestHeaders.set.x-from-where = "frp"

[[proxies]]
name = "vitality"
type = "https"
customDomains = ["vitality.aday.fun"]
[proxies.plugin]
type = "https2http"
localAddr = "127.0.0.1:8080"
crtPath = "/etc/nginx/ssl/aday.crt"
keyPath = "/etc/nginx/ssl/aday.key"
hostHeaderRewrite = "vitality.aday.fun"
requestHeaders.set.x-from-where = "frp"

[[proxies]]
name = "jenkins"
type = "https"
customDomains = ["jenkins.aday.fun"]
[proxies.plugin]
type = "https2http"
localAddr = "127.0.0.1:8090"
crtPath = "/etc/nginx/ssl/aday.crt"
keyPath = "/etc/nginx/ssl/aday.key"
hostHeaderRewrite = "jenkins.aday.fun"
requestHeaders.set.x-from-where = "frp"

Nginx 代理到 frp 内网穿透示例

假设 frps 服务端有一个 nginx, 本身代理了 80/443 端口提供了一个 home.aday.fun 服务。

现在 frpc 客户端有一个 vitality 服务需要被代理,用户需要先访问 vitality.aday.fun 域名,然后请求穿过 nginx,再由 nginx 转发到 frp 穿透的主机上。

frpc.toml 配置参考

toml
serverAddr = "aday.fun"
serverPort = 13462
auth.token = "abcdef"

# 假设 frpc 客户端已经代理好了 http://vitality.aday.fun:81 来访问服务。如下:
[[proxies]]
name = "vitality"
type = "http"
localPort = 8080
customDomains = ["vitality.aday.fun"]

nginx.conf

shell
# user  nobody;
worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    client_max_body_size 1024m;

    sendfile        on;
    #tcp_nopush     on;
    keepalive_timeout  65;

    server {
        listen 80;
        server_name *.aday.fun;
        rewrite ^(.*)$ https://$host$1 permanent;
    }

    server {
        listen       443 ssl;
        server_name  home.aday.fun;
        ssl_certificate /etc/nginx/ssl/aday.cer;
        ssl_certificate_key /etc/nginx/ssl/aday.key;

        location / {
            root /usr/share/nginx/html;
            index index.html index.htm;
            # vue 项目这样配置
            #try_files $uri $uri/ /index.html;
            # vitepress 项目这样配置
            try_files $uri $uri.html $uri/ =404;

            error_page 404 /404.html;
            error_page 403 /404.html;
        }
    }

    server {
        listen      443 ssl;
        server_name vitality.aday.fun;
        ssl_certificate /etc/nginx/ssl/aday.cer;
        ssl_certificate_key /etc/nginx/ssl/aday.key;

        location / {
            proxy_set_header Host $http_host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header REMOTE-HOST $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            # 注意,这里使用了不同的 81 frp 端口
            proxy_pass http://vitality.aday.fun:81/;
        }
    }
}
txt
这样:
访问 https://vitality.aday.fun:443 就会被 nginx 转发到 frp 代理的 http://vitality.aday.fun:81/ 地址;
访问 http://vitality.aday.fun:80   就会被 nginx 转先转为 https,然后再转发到 frp 代理的 http://vitality.aday.fun:81/ 地址;
访问 http://vitality.aday.fun:81   就直接会被 frp 接收到,直接转发到真实服务。

也比较推荐从 nginx 转发到 frp 做内网穿透:

  • 这样 ssl 证书可以放在 nginx 端
  • 可使用 nginx 隐藏访问端口,来更方便的访问多个服务
  • 让 frp 符合单一职责,就只让 frp 做内网穿透,其他交给 nginx 来做,逻辑更清楚。