1. 背景

家中有一台 all in one 的 NAS 设备,希望实现在公网实现 https://a.example.com 自动对应 NAS 设备的 A 服务,b.example.com 访问 B 服务。

1.1. 存在的问题

  • 家庭使用的是移动捆绑宽带套餐,不提供公网 IP 地址
  • 注册的域名没有完成 ICP 备案,无法直接使用国内云服务器的 80 和 443 端口
  • 市面上的内网穿透工具和服务不够问题,出了问题只能等(还在使用 cpolar)
  • 使用第三方穿透工具配置 SSL 证书比较繁琐
  • 成本和穿透工具套餐的带宽不匹配

1.2. 考虑的解决方案

  1. 注册顶级域名(费用不高)
  2. 使用 letsencrypt 申请泛域名的 SSL 证书(免费,3个月续一次)
  3. ucloud 云主机(固定 IP,1M带宽)
  4. 在云主机配置 frp 服务端,启用 http 端口
  5. 在云主机配置 Nginx 实现 80 和 443 解析
  6. 在客户端配置 frp 客户端,定义需要访问的 http 应用
  7. 通过 Nginx 转发访问请求到 frps 的 http 端口

2. 组件配置

2.1. 云服务器1台

名称 描述
操作系统 ubuntu 22
公网IP 117.xx.xx.xx
开放端口 80,443,8002,8003,8004,20001~20010
frp 0.60.0 内网穿透
nginx 1.26.1 网站服务
acme.sh 3.0.7 SSL 域名证书申请

2.2. 域名服务 cloudflare

类型 内容 指向
A file 117.xx.xx.xx
A file1 117.xx.xx.xx
txt 参照acme.sh 提示 参照acme.sh 提示

3. 申请 SSL 泛域名证书

SSL 泛域名证书支持多个二级域名仅配置一条证书记录,acme.sh 脚本支持基于 DNS 的泛域名证书申请

4. 防火墙配置

需要同时配置操作系统的防火墙以及云服务器端的防火墙策略

sudo ufw allow 80
sudo ufw allow 443
sudo ufw allow 8003:8004/tcp
sudo ufw allow 20001:20010/tcp
sudo ufw status

5. Frp 配置

5.1. Frps 配置

以下内容在云服务器端配置,详细的 Frp 配置说明请参考官网文档

wget https://github.com/fatedier/frp/releases/download/v0.60.0/frp_0.60.0_linux_amd64.tar.gz
tar zxvf frp_0.60.0_linux_amd64.tar.gz
sudo mv frp_0.60.0_linux_amd64 /usr/local/frp

#配置文件
sudo cat > /usr/local/frp/frps.toml << EOF
bindPort = 8003

webServer.addr = "127.0.0.1"
webServer.port = 8002
webServer.user = "admin"
webServer.password = "admin"

log.to = "/tmp/frps.log"
log.level = "info"
log.maxDays = 30
log.disablePrintColor = false
detailedErrorsToClient = true

auth.method = "token"
auth.token = "token"
vhostHTTPPort = 8004
subdomainHost = "waringid.com"

allowPorts = [{ start = 20000, end = 20010 }, { single = 3001 },{ single = 3003 },{ start = 4000, end = 50000 }]
maxPortsPerClient = 5
udpPacketSize = 1500
natholeAnalysisDataReserveHours = 168
EOF

#配置为服务实现自启动
sudo cat > /usr/lib/systemd/system/frps.service << EOF 
[Unit]
Description=Frp Server Service
After=network.target syslog.target
Wants=network.target

[Service]
Type=simple
User=nobody
Restart=on-failure
RestartSec=5s
ExecStart=/usr/local/frp/frps -c /usr/local/frp/frps.toml

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl start frps
systemctl enable frps
systemctl status frps
ss -tln

5.2. frpc 配置

以下内容在需要内网穿透的设备上配置,以 windows 配置为例。先下载 windows 版本

serverAddr = "117.xx.xx.xx"
serverPort = 8003
auth.method = "token"
auth.token = "token"
log.to = "./frpc.log"

[[proxies]]
name = "rdp"
type = "tcp"
localIP = "127.0.0.1"
localPort = 3389
remotePort = 20001

[[proxies]]
name = "zyfeng"
type = "http"
localPort = 8000
localIP = "127.0.0.1"
subdomain  = "file"

5.3. 验证 frpc 

完成配置并确保启动正常后在浏览器输入 http://file.waringid.com:8004 应该可以访问内网的 127.0.0.0:80 端口。

当前直接使用 frp 的配置,没有经过 nginx 也没有加载 SSL 证书

6. Nginx 配置

6.1. 安装 Nginx 

直接参考官网给出的安装指南

sudo apt install curl gnupg2 ca-certificates lsb-release ubuntu-keyring
curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
    | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null
gpg --dry-run --quiet --no-keyring --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/ubuntu `lsb_release -cs` nginx" \
    | sudo tee /etc/apt/sources.list.d/nginx.list
echo -e "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \
    | sudo tee /etc/apt/preferences.d/99nginx
sudo apt update
sudo apt install nginx

6.2. Nginx.conf

user  nginx;
worker_processes  auto;

error_log  /var/log/nginx/error.log notice;
pid        /var/run/nginx.pid;


events {
    worker_connections  1024;
}


http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    
    log_format waringid '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" "$http_x_real_ip"';


    access_log  /var/log/nginx/access.log  main;
    proxy_headers_hash_bucket_size 1024;
    types_hash_bucket_size 1024; 
   set_real_ip_from 10.0.0.0/8;
    sendfile        on;
    #tcp_nopush     on;

    keepalive_timeout  65;

    #gzip  on;
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      '';
    }

    include /etc/nginx/conf.d/*.conf;
}

6.2.1. 其它版本的 Nginx.conf 

注意配置文件中 proxy_set_header Host $host:80; 

server {
    listen 80;
    server_name *.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name *.yourdomain.com;

    ssl_certificate /usr/local/nginx/conf/ssl/yourdomain.com.crt;
    ssl_certificate_key /usr/local/nginx/conf/ssl/yourdomain.com.key;

    client_max_body_size 50m; 
    client_body_buffer_size 256k;
    client_header_timeout 3m;
    client_body_timeout 3m;
    send_timeout 3m;
    proxy_connect_timeout 300s; 
    proxy_read_timeout 300s; 
    proxy_send_timeout 300s;
    proxy_buffer_size 64k; 
    proxy_buffers 4 32k; 
    proxy_busy_buffers_size 64k;
    proxy_temp_file_write_size 64k; 
    proxy_ignore_client_abort on; 

    location / {
        proxy_pass http://127.0.0.1:1234;
        proxy_redirect off;
        proxy_set_header Host $host:80;
        proxy_ssl_server_name on;
        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 $scheme;
    }
}

6.3. proxy_params.conf

这个是代理的通用配置,用来配置实现 http 到 https 的重定向和反向代理的参数设置。该文件保存在 /etc/nginx/ 下。

set $host_fixed $http_host;
if ($http_host = "") {
    set $host_fixed "default";
}

set $test "";
if ($scheme = "http") {
        set $test "H";
}
if ($test = H) {
        return 301 https://$host$request_uri;
}


proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_hide_header X-Powered-By;

add_header       X-Served-By $host;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Forwarded-Proto  $scheme;
proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
proxy_set_header X-Real-IP          $remote_addr;

6.4. default.conf

该文件在 /etc/nginx/conf.d/ 下面,该目录用来保存单独定义的配置文件。这里实现每个二级域名对应一个配置文件。default.conf 是默认配置文件,当访问的 URL 在其它个性化的配置文件中都没有匹配的时候就最终匹配该文件。

server {
    listen       80;
    server_name  *.waringid.com default_server;
    return 301 https://$host$request_uri;
}

6.5. file.conf

这个就是代理并展示内网设备的 web 页面配置,其中 SSL 证书的申请和配置请参考 letsencrypt 申请泛域名证书

server {
    
    listen 443 ssl;
    server_name file.waringid.com; 
    access_log  /var/log/nginx/file.log  waringid;

    ssl_certificate_key cert/privkey.pem;
    ssl_certificate cert/fullchain.pem;
    ssl_trusted_certificate cert/chain.pem;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;

    location / {
        proxy_pass  http://127.0.0.1:8004; 
        include proxy_params;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

6.6. dashboard.conf

这个是 frps 提供的管理 web 页面,在前面的配置中设置仅允许 127.0.0.1 的内部网络访问,该文件的大部分配置直接复用前面的内容。nginx 代理后可以在公网通过域名访问。

server {
    
    listen 443 ssl;
    server_name dashboard.waringid.com; 
    access_log  /var/log/nginx/dashboard.log  waringid;

    ssl_certificate_key cert/privkey.pem;
    ssl_certificate cert/fullchain.pem;
    ssl_trusted_certificate cert/chain.pem;
    ssl_session_timeout 5m;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;

    location / {
        proxy_pass  http://127.0.0.1:8002; 
        include proxy_params;
    }
}

提醒

验证成功后可以直接在云服务器的安全规则上禁止 8004 的端口开放,直接通过 Nginx 的内部转发即可实现访问访问

7. 进阶版

通过 frp 的 【vhostHTTPPort】选项的方式配置反向代理的模式还是不够安全和灵活。受云主机的配置所限不太可能启用高级的安全组件。能不能实现云主机将 http 访问请求直接转发到内网中的 WAF 主机,最终实现在云主机上仅需配置一次,后续公网访问的二级域名只在内网的 WAF 上配置即可。以下是上述需求的实现方式

7.1. frps 端的配置不变

7.2. frpc 增加 TCP 配置

将原来的 http 访问改为 tcp 的连接

[[proxies]]
name = "home_web"
type = "tcp"
localIP = "192.168.77.12"
localPort = 80
remotePort = 20002

7.3. 配置 nginx 的 default.conf

通过 default.conf 的配置实现按域名访问,如果没有输入正确的域名则返回403

server {
    listen       80 default_server;
    server_name  _;
    return 403;
}

7.4. 增加 ssl-ciphers.conf

将证书相关的配置整合到同一个文件,后续直接引用,简化配置文件

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

ssl_session_timeout 5m;
ssl_session_cache shared:SSL:50m;
# intermediate configuration. tweak to your needs.
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off;
ssl_certificate_key cert/privkey.pem;
ssl_certificate cert/fullchain.pem;
ssl_trusted_certificate cert/chain.pem;

增加 all-home.conf

通过该文件转发代理需求到 frp 设置的内网端口

server {
    listen 80;   
    listen 443 ssl;
    server_name *.waringid.com; 
    access_log  /var/log/nginx/all.log  waringid;
    include ssl-ciphers.conf;
    location / {      
        proxy_pass  http://127.0.0.1:20002; 
        include proxy_params;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}


8. 测试验证

http://file.waringid.com 会自动转为 https://file.waringid.com

http://dashboard.waringid.com 也类似 https://dashboard.warignid.com

泛域名验证

完成上述的 nginx 中 all.conf 配置并启用后直接在内网的 WAF 上增加 Host 记录就能实现2个域名同时正常访问了

8.1. Nginx 异常

Nginx 启动检测时出现以下的日志提醒

nginx: [warn] could not build optimal proxy_headers_hash, you should increase either proxy_headers_hash_max_size: 1024 or proxy_headers_hash_bucket_size: 64; ignoring proxy_headers_hash_bucket_size
nginx: [warn] could not build optimal proxy_headers_hash, you should increase either proxy_headers_hash_max_size: 1024 or proxy_headers_hash_bucket_size: 64; ignoring proxy_headers_hash_bucket_size

在 nginx.conf 的 http 段增加以下内容

    proxy_headers_hash_bucket_size 1024;
    types_hash_bucket_size 1024;


  • 无标签
写评论...