前言
在个人的项目部署中,我们常常面临以下挑战:
- 业务应用数量众多,全部部署至云服务器成本高昂
- 需要在本地环境运行服务,但缺乏固定公网 IP
- 希望使用自定义域名提供专业服务访问体验
针对这些痛点,我们采用云服务器中转 + 内网穿透的方案:
- 利用云服务商提供的 ECS 实例与公网 IP 作为流量入口
- 通过 FRP 工具建立安全的内网穿透通道
- 将实际业务服务部署在内网环境,享受本地部署的灵活性与成本优势
- 结合自有域名,为用户提供统一的专业访问入口
一、内网穿透
1. 准备一台云服务器
先去阿里云的高校计划白嫖300元优惠券,开通一年2c2g的ECS服务器选择按量计费的IP服务。

或者其他腾讯云、京东云厂商的便宜服务器都行。
2. 云服务器安装frp工具
基本原理:
- 公网服务器 (云服务器) :具有固定公网IP,运行 frp 的服务端
frps。 - 内网服务器/设备 :位于 NAT 或防火墙后,运行 frp 的客户端
frpc。 - 通信流程 :
frpc主动连接frps建立控制通道,当外部用户访问frps的某个端口时,frps会将请求通过已建立的通道转发给frpc,再由frpc转发给内网的特定服务。
接下来创建完实例登录控制台,打开远程控制界面。安装frp工具
下载 frp :从 frp GitHub Releases 下载对应平台的二进制文件
上传并解压 frp
tar -xzf frp_0.52.3_linux_amd64.tar.gz
cd frp_0.52.3_linux_amd64
编辑服务端配置文件 frps.ini
[common]
# frp 服务端监听的端口,客户端通过此端口连接
bind_port = 7000
# 可选:Web 管理界面端口
dashboard_port = 7500
# 管理界面的用户名和密码
dashboard_user = admin
dashboard_pwd = password
# 可选:身份验证令牌,建议设置以增强安全性
token = yourtoken
# 可选:设置最大并发连接数
max_pool_count = 100
# HTTP 和 HTTPS 代理端口
vhost_http_port = 80
vhost_https_port = 443
启动 frp 服务端
./frps -c frps.ini
设置开机自启 (Systemd)
创建 systemd 服务文件:sudo vim /etc/systemd/system/frps.service
ExecStart的路径要修改成你自己的路径
[Unit]
Description=Frp Server Service
After=network.target
[Service]
Type=simple
User=nobody
Restart=on-failure
RestartSec=5s
ExecStart=/path/to/frps -c /path/to/frps.ini
[Install]
WantedBy=multi-user.target
启用服务
sudo systemctl daemon-reload
sudo systemctl enable frps
sudo systemctl start frps
开启防火墙,在控制台的安全组里面开启防火墙放行(如果安装了宝塔面板,宝塔里面也要开启)

3. 内网服务器配置frp
上传并解压 frp (同服务端步骤)
编辑客户端配置文件 frpc.ini
[common]
server_addr = 101.205.73.227
server_port = 7000
token = yourtoken
# 所有 HTTP 流量都代理到 Nginx
[web-http]
type = http
local_ip = 127.0.0.1
local_port = 80
custom_domains = zs4m.cn,www.zs4m.cn,api.zs4m.cn
# 所有 HTTPS 流量都代理到 Nginx
[web-https]
type = https
local_ip = 127.0.0.1
local_port = 443
custom_domains = zs4m.cn,www.zs4m.cn,api.zs4m.cn
# SSH 服务
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 22
remote_port = 6000
创建服务文件:sudo vim /etc/systemd/system/frpc.service
[Unit]
Description=Frp Client Service
After=network.target
[Service]
Type=simple
User=nobody
Restart=on-failure
RestartSec=5s
ExecStart=/path/to/frpc -c /path/to/frpc.ini
[Install]
WantedBy=multi-user.target
启用服务:
sudo systemctl daemon-reload
sudo systemctl enable frpc
sudo systemctl start frpc

访问 Dashboard :http://x.x.x.x:7500 (使用设置的用户名密码登录)

HTTP 服务测试:访问域名需要先配置好DNS解析和nginx
curl http://zs4m.cn
二、域名解析
1. 购买一个域名并且备案
直接搜索域名,各个厂商都有提供,随机挑选一个就可,1块钱一年
在哪个云服务商购买的服务器 ,就在哪个服务商进行备案阿里备案

配置域名解析:

获取SSL证书,各个云厂商也有提供。子域名需要认证多个,点击下载key和pem

2. nginx代理
在云服务器上我们配置了80,443端口的http流量都会转到内网服务器上,内网服务器再根据域名接收,不同的子域名对应不同的端口,需要nginx来做ssl解密和转发。这里使用docker来安装运行,后续所有服务也运行在docker上。
1) 安装docker
curl -fsSL https://get.docker.com | bash
sudo systemctl enable docker
sudo systemctl start docker
配置代理:
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": [
"https://docker.m.daocloud.io",
"https://mirror.ccs.tencentyun.com",
"https://hub-mirror.c.163.com",
"https://registry.docker-cn.com"
]
}
EOF
重启docker:
sudo systemctl daemon-reload
sudo systemctl restart docker
2) 安装portainer
Portainer 是一个轻量级、开源的容器管理图形化界面,专门用于管理 Docker 和 Kubernetes 环境。它的核心理念是让容器管理变得简单,即使是对命令行不熟悉的开发人员或运维人员也能轻松上手。
docker pull portainer/portainer
docker run -d --restart=always --name portainer -p 9009:9000 -v /var/run/docker.sock:/var/run/docker.sock portainer/portainer

3)安装nginx
创建网站目录和文件
# 创建项目目录
mkdir -p ~/docker/nginx/zs4m.cn/html
cd ~/docker/nginx/zs4m.cn
# 创建网站首页
cat > html/index.html << 'EOF'
<!DOCTYPE html>
<html>
<head>
<title>欢迎访问 zs4m.cn</title>
<meta charset="utf-8">
<style>
body { font-family: Arial, sans-serif; margin: 40px; }
.container { max-width: 800px; margin: 0 auto; }
.header { background: #f5f5f5; padding: 20px; border-radius: 5px; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🚀 欢迎访问 zs4m.cn!</h1>
<p>网站运行在内网服务器的 Docker 容器中</p>
<p>通过云服务器 frp 中转访问</p>
</div>
<div>
<h2>系统信息</h2>
<p>服务器时间: <span id="time"></span></p>
<p>访问者IP: <span id="ip"></span></p>
</div>
</div>
<script>
document.getElementById('time').textContent = new Date().toLocaleString();
// 简单的IP显示(实际需要后端支持)
fetch('https://api.ipify.org?format=json')
.then(response => response.json())
.then(data => document.getElementById('ip').textContent = data.ip)
.catch(() => document.getElementById('ip').textContent = '无法获取');
</script>
</body>
</html>
EOF
创建 Nginx 配置文件:
mkdir -p conf
default.conf
# 主站点 HTTP 重定向
server {
listen 80;
server_name zs4m.cn www.zs4m.cn;
return 301 https://$server_name$request_uri;
}
# 主站点 HTTPS
server {
listen 443 ssl;
server_name zs4m.cn www.zs4m.cn;
client_max_body_size 100M;
# SSL 证书配置
ssl_certificate /etc/nginx/ssl/zs4m.cn.crt;
ssl_certificate_key /etc/nginx/ssl/zs4m.cn.key;
# SSL 安全配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "no-referrer-when-downgrade" always;
# 代理到前端服务
location / {
proxy_pass http://cheeseai-web:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $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 $scheme;
proxy_cache_bypass $http_upgrade;
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
# 健康检查
location /health {
access_log off;
return 200 "healthy\n";
add_header Content-Type text/plain;
}
# 禁止访问隐藏文件
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
# API 站点 HTTP 重定向
server {
listen 80;
server_name api.zs4m.cn;
return 301 https://$server_name$request_uri;
}
# API 站点 HTTPS
server {
listen 443 ssl;
server_name api.zs4m.cn;
client_max_body_size 100M;
# 使用相同的 SSL 证书
ssl_certificate /etc/nginx/ssl/api.zs4m.cn.crt;
ssl_certificate_key /etc/nginx/ssl/api.zs4m.cn.key;
# SSL 安全配置(与主站相同)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-CHACHA20-POLY1305;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# 安全头
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header X-Content-Type-Options "nosniff" always;
# 代理到 API 服务
location / {
proxy_pass http://192.168.0.46:8090;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $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 $scheme;
proxy_cache_bypass $http_upgrade;
# API 特定的超时设置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# API 健康检查
location /health {
proxy_pass http://192.168.0.46:8090/health;
proxy_set_header Host $host;
access_log off;
}
# 禁止访问隐藏文件
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}
}
创建主配置文件
# 创建主配置文件
cat > conf/nginx.conf << 'EOF'
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"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
# 包含虚拟主机配置
include /etc/nginx/conf.d/*.conf;
}
EOF
使用docker部署nginx
cd /home/vvv/docs/dev-ops/nginx
cat > docker-compose.yml << 'EOF'
version: '3.8'
services:
nginx:
image: nginx:alpine
container_name: nginx_zs4m
restart: unless-stopped
ports:
- "80:80"
- "443:443" # 新增 HTTPS 端口映射
volumes:
- ./html:/usr/share/nginx/html
- ./conf/nginx.conf:/etc/nginx/nginx.conf
- ./conf/default.conf:/etc/nginx/conf.d/default.conf
- ./logs:/var/log/nginx
- ./cache:/var/cache/nginx
- ./ssl:/etc/nginx/ssl # 挂载 SSL 证书目录
networks:
- dev-ops_my-network
networks:
dev-ops_my-network:
external: true
name: dev-ops_my-network
EOF
启动
# 在项目目录下启动服务
cd ~/docker/nginx/zs4m.cn
docker-compose up -d
# 检查容器状态
docker ps
docker logs nginx_zs4m
# 测试本地访问
curl http://localhost
添加ssl配置,在ssl目录下面上传从云服务器证书管理下载的key和pem,将pem重命名为crt后缀
# 创建 SSL 配置目录
mkdir -p ssl
三、web服务
1. docker运行
前端服务已经准备好,直接build
docker build --platform linux/amd64 -t mumu/cheeseai-web:1.0 .
# 停止并重新启动
docker-compose down
docker-compose up -d
# 检查状态
docker-compose ps
# 查看日志
docker logs nginx_zs4m
services:
ai-agent-station-front:
image: mumu/cheeseai-agent-front:1.0
container_name: ai-agent-station-front-app
ports:
- "3002:3002"
restart: unless-stopped
networks:
- my-network
environment:
- NODE_ENV=production
- NEXT_PUBLIC_API_HOST_URL=https://api.zs4m.cn
healthcheck:
test: [ "CMD", "curl", "-f", "http://localhost:3002" ]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
2. 访问服务
当用户在浏览器访问 https://zs4m.cn 时,完整的请求处理流程如下:
HTTPS 请求接收
用户浏览器向云服务器 443 端口发起 HTTPS 请求
FRP 服务端转发
云服务器上的 FRP 服务端(frps)接收请求,通过已建立的隧道将请求转发至内网服务器
FRP 客户端处理
内网服务器上的 FRP 客户端(frpc)接收转发来的请求
Nginx 反向代理
FRP 客户端将请求发送至本地 Nginx 服务的 443 端口,Nginx 作为反向代理将请求转发至实际的前端服务端口 3002
前端页面渲染
前端服务处理请求并返回页面内容,最终在用户浏览器中完成渲染展示
