背景
家中有一台 all in one 的 NAS 设备,希望实现在公网实现 https://a.example.com 自动对应 NAS 设备的 A 服务,b.example.com 访问 B 服务。
存在的问题
- 家庭使用的是移动捆绑宽带套餐,不提供公网 IP 地址
- 注册的域名没有完成 ICP 备案,无法直接使用国内云服务器的 80 和 443 端口
- 市面上的内网穿透工具和服务不够问题,出了问题只能等(还在使用 cpolar)
- 使用第三方穿透工具配置 SSL 证书比较繁琐
- 成本和穿透工具套餐的带宽不匹配
考虑的解决方案
- 注册顶级域名(费用不高)
- 使用 letsencrypt 申请泛域名的 SSL 证书(免费,3个月续一次)
- ucloud 云主机(固定 IP,1M带宽)
- 在云主机配置 frp 服务端,启用 http 端口
- 在云主机配置 Nginx 实现 80 和 443 解析
- 在客户端配置 frp 客户端,定义需要访问的 http 应用
- 通过 Nginx 转发访问请求到 frps 的 http 端口
组件配置
云服务器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 域名证书申请 |
域名服务 cloudflare
类型 | 内容 | 指向 |
---|
A | file | 117.xx.xx.xx |
A | file1 | 117.xx.xx.xx |
txt | 参照acme.sh 提示 | 参照acme.sh 提示 |
申请 SSL 泛域名证书
SSL 泛域名证书支持多个二级域名仅配置一条证书记录,acme.sh 脚本支持基于 DNS 的泛域名证书申请
防火墙配置
需要同时配置操作系统的防火墙以及云服务器端的防火墙策略
代码块 |
---|
|
sudo ufw allow 80
sudo ufw allow 443
sudo ufw allow 8003:8004/tcp
sudo ufw allow 20001:20010/tcp
sudo ufw status |
Frp 配置
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 |
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" |
验证 frpc
完成配置并确保启动正常后在浏览器输入 http://file.waringid.com:8004 应该可以访问内网的 127.0.0.0:80 端口。
信息 |
---|
当前直接使用 frp 的配置,没有经过 nginx 也没有加载 SSL 证书 |
Nginx 配置
安装 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 |
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;
} |
其它版本的 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;
}
} |
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; |
default.conf
该文件在 /etc/nginx/conf.d/ 下面,该目录用来保存单独定义的配置文件。这里实现每个二级域名对应一个配置文件。default.conf 是默认配置文件,当访问的 URL 在其它个性化的配置文件中都没有匹配的时候就最终匹配该文件。
代码块 |
---|
|
server {
listen 80;
server_name *.waringid.com default_server;
return 301 https://$host$request_uri;
} |
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;
}
} |
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 的内部转发即可实现访问访问 |
进阶版
通过 frp 的 【vhostHTTPPort】选项的方式配置反向代理的模式还是不够安全和灵活。受云主机的配置所限不太可能启用高级的安全组件。能不能实现云主机将 http 访问请求直接转发到内网中的 WAF 主机,最终实现在云主机上仅需配置一次,后续公网访问的二级域名只在内网的 WAF 上配置即可。以下是上述需求的实现方式
frps 端的配置不变
frpc 增加 TCP 配置
将原来的 http 访问改为 tcp 的连接
代码块 |
---|
|
[[proxies]]
name = "home_web"
type = "tcp"
localIP = "192.168.77.12"
localPort = 80
remotePort = 20002 |
配置 nginx 的 default.conf
通过 default.conf 的配置实现按域名访问,如果没有输入正确的域名则返回403
代码块 |
---|
|
server {
listen 80 default_server;
server_name _;
return 403;
} |
增加 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;
}
}
|
测试验证
http://file.waringid.com 会自动转为 https://file.waringid.com
http://dashboard.waringid.com 也类似 https://dashboard.warignid.com
泛域名验证
完成上述的 nginx 中 all.conf 配置并启用后直接在内网的 WAF 上增加 Host 记录就能实现2个域名同时正常访问了
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; |