国内无需端口号直连自建服务器(免备案)
前言与动机:为什么要这么折腾?
很多小伙伴在折腾本地 NAS 或自建家庭工作站时,往往会面临一个痛点:家里没有公网 IP,或者宽带运营商封锁了 80 和 443 等常用端口,导致出门在外无法优雅地访问家里的私有服务。如果不做内网穿透,在外访问简直寸步难行;而如果顺着常规思路去买一台国内的云服务器做跳板,想要通过 https://你的域名.com 直接访问(不带任何繁琐的端口号),又必须面临极其繁琐的域名备案流程。
那么,为什么我们不直接在云端买一台高性能的服务器呢?
原因很简单:如果你去租用一台拥有大内存、大硬盘、高性能 CPU 的云服务器,每个月的账单绝对是一笔巨款!相反,通过在本地“捡垃圾”(图吧大佬E5方案,或者我更推荐你买一个高性能迷你小主机)自己组装一台机器,你可以用极其低廉的成本,搞出一台永久使用、性能爆表的本地主机。
而且最重要的是,把所有核心数据存在本地硬盘上,意味着你拥有对服务器 100% 的完全硬件管理权和数据控制权,再也不用担心将私密数据放到云端导致隐私泄露或丢失的问题,用起来安全感拉满!
今天,我们就来分享一套客观、实用、一键化自动化的部署方案:利用一台便宜的海外 VPS(免备案)作为单纯的跳板,结合 FRP(内网穿透)和 Caddy(自动申请 HTTPS 证书并反向代理),实现本地服务的完美公网暴露。不用懂复杂的 Linux 命令,所有枯燥的过程都已封装为 PowerShell 脚本,小白也能无痛“发车”。
准备工作
在开始之前,你需要准备以下基础设施:
- 一台海外 VPS:建议系统为 Debian(或 Ubuntu),具有一个公网 IP。
不要一看到购买服务器,就觉得它一定很贵。如果你只是建个个人网站,不需要极高的稳定性的话,且预算真的紧张,可以搜索以下关键词,可以购买到一年五六十元的廉价主机:
【低价VPS、灵车VPS】 但是务必务必做好备份!!!这种超低价 VPS 随时可能跑路!【避坑指南】:由于我们搭建的服务主要是为了在中国大陆进行公网访问,且希望直连速度尽可能的快,因此强烈推荐购买中国香港节点的 VPS,延迟极低;其次推荐日本或新加坡节点。海外节点天然免备案,极其省心。
- 一个域名:并将该域名的 DNS 解析挂载托管至 Cloudflare(免费且支持 API,后文 Caddy 需要用到)。
- 一台用来做服务端的本地电脑:例如你“捡垃圾”组建的 Windows 电脑、黑裙或本地 NAS 等设备。
接下来,我们在你平时使用的 Windows 电脑桌面上新建一个文件夹(例如叫 内网穿透部署),后续的操作都围绕该文件夹进行。
第一步:准备服务端的基础配置
在刚刚新建的文件夹中,我们需要新建两个纯文本文件,用来配置你的 FRP 服务端及 Caddy 反向代理:
1. 新建 [frps.toml]
这是服务端 FRP(云端)的配置文件,配置你的穿透主干端口以及用于安全验证的 Token:
# ======================= FRPS 服务端配置 =======================
bindPort = 7000
auth.method = "token"
# 【重要】:这里千万不要用弱密码,请随意敲击一段极长没有规律的字母+数字组合
auth.token = "[填写你的强随机TOKEN]"
transport.tls.force = true
# Dashboard 管理面板页配置
webServer.addr = "0.0.0.0" # 如果你不需要在外网看面板可以改成 127.0.0.1
webServer.port = 7500
webServer.user = "admin"
webServer.password = "[填写你的面板密码]"
2. 新建 [Caddyfile]
Caddy 的配置文件堪称优雅,它规定了如何根据你的域名自动颁发 HTTPS 证书,以及如何把公网流量转发给 FRP:
# ======================= Caddy 泛域名配置 =======================
# 使用 Cloudflare DNS 签发 *.你的域名.com 泛域名证书
*.[填写你的域名], [填写你的域名] {
tls {
# 【重要】:填入从 Cloudflare 生成的 API Token(需拥有 DNS 编辑权限)
dns cloudflare [填写你的Cloudflare_API_Token]
}
# 举例 1:如果你想把家里的 Git 服务暴露出来
@git host git.[填写你的域名]
handle @git {
# 将流量转发到 FRP 预留好的 13000 服务端端口
reverse_proxy 127.0.0.1:13000
}
# 举例 2:如果你想将家中的私有博客暴露出来
@i host i.[填写你的域名] [填写你的域名]
handle @i {
# 将流量转发到 FRP 预留好的 12368 服务端端口
reverse_proxy 127.0.0.1:12368
}
# 将没有匹配上的其他乱发请求予以拒绝
handle {
respond "Not Found" 404
}
}
第二步:一键初始化服务器环境
为了帮大家省下一条条手敲 Linux 命令的折磨,我们准备了一个自动化部署脚本。
在刚刚的文件夹下新建名为 [deploy_server_frps_caddy.ps1] 的 PowerShell 脚本文件。你可以用记事本打开它,并将下方代码开头几行的 “必填参数” 改为你自己刚买的香港/日本 VPS 信息:
点击展开查看:一键部署配置脚本代码(极长,复制粘贴即可)
# 强制管道输出使用无 BOM 的 UTF-8,避免 bash 解析报错
$OutputEncoding = [System.Text.UTF8Encoding]::new($false)
# ========================= 必填参数(按需修改) =========================
# 服务器公网 IP 或域名
$ServerIp = "[填写你的VPS公网IP]"
# SSH 登录用户(通常是 root)
$SshUser = "root"
# SSH 端口,如果没有改过默认是 22
$SshPort = 22
# 是否自动接收新主机指纹(首次连接建议 true)
$AcceptNewHostKey = $true
# SSH 私钥路径(部署脚本会自动生成并写入服务器 authorized_keys)
$SshPrivateKeyPath = Join-Path $env:USERPROFILE ".ssh\id_ed25519"
# ========================= FRP 参数(可选) =========================
# FRP 版本:默认 latest(自动拉取 GitHub 最新发布)
$FrpVersion = "latest"
# FRPS 服务监听端口(给 frpc 连接)
$FrpsBindPort = 7000
# ========================= 防火墙参数(可选) =========================
# 是否放行 Dashboard 端口(仅放行端口,不写 dashboard 配置)
$AllowDashboardInUfw = $true
$DashboardPort = 7500
$ErrorActionPreference = "Stop"
function Get-OpenSshTool([string]$Name) {
$cmd = Get-Command $Name -ErrorAction SilentlyContinue
if ($cmd) { return $cmd.Source }
throw "未找到 $Name。请先启用 Windows OpenSSH Client。"
}
$ssh = Get-OpenSshTool "ssh.exe"
$sshKeygen = Get-OpenSshTool "ssh-keygen.exe"
$sshOptions = @()
if ($AcceptNewHostKey) {
$sshOptions += @("-o", "StrictHostKeyChecking=accept-new")
}
$target = "$SshUser@$ServerIp"
function Test-KeyLogin {
& $ssh @sshOptions -p $SshPort -i $SshPrivateKeyPath -o BatchMode=yes $target "echo key-auth-ok" | Out-Null
return ($LASTEXITCODE -eq 0)
}
function Ensure-SshKeyAuth {
if (-not (Test-Path $SshPrivateKeyPath)) {
$keyDir = Split-Path -Parent $SshPrivateKeyPath
if (-not (Test-Path $keyDir)) { New-Item -ItemType Directory -Path $keyDir -Force | Out-Null }
& $sshKeygen -t ed25519 -f $SshPrivateKeyPath -N "" -C "frp-deploy@$env:COMPUTERNAME" | Out-Null
}
if (Test-KeyLogin) {
Write-Host "SSH 密钥已可用,后续将使用免密连接。" -ForegroundColor Green
return
}
Write-Host "正在配置服务器 authorized_keys(首次运行将提示你输入一次 VPS 服务器的 SSH 密码)..." -ForegroundColor Yellow
$pubKeyPath = "$SshPrivateKeyPath.pub"
if (-not (Test-Path $pubKeyPath)) {
throw "未找到公钥文件: $pubKeyPath"
}
$pubKeyContent = (Get-Content $pubKeyPath -Raw).Trim()
$passwordAuthOptions = @(
"-o", "PubkeyAuthentication=no",
"-o", "PreferredAuthentications=password,keyboard-interactive"
)
# 写入公钥,同时确保 sshd_config 中 PubkeyAuthentication 为 yes 并重启 sshd
$remoteAddKeyCmd = @"
umask 077
mkdir -p ~/.ssh
touch ~/.ssh/authorized_keys
grep -qxF "$pubKeyContent" ~/.ssh/authorized_keys || printf '%s\n' "$pubKeyContent" >> ~/.ssh/authorized_keys
sort -u ~/.ssh/authorized_keys -o ~/.ssh/authorized_keys
# 启用密钥登录(修复部分 VPS 出厂默认禁用的情况)
sed -i 's/^#*\s*PubkeyAuthentication\s.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
grep -qE '^PubkeyAuthentication' /etc/ssh/sshd_config || echo 'PubkeyAuthentication yes' >> /etc/ssh/sshd_config
systemctl restart sshd 2>/dev/null || service ssh restart 2>/dev/null || true
"@
& $ssh @sshOptions @passwordAuthOptions -p $SshPort $target $remoteAddKeyCmd
if ($LASTEXITCODE -ne 0) {
throw "写入服务器 authorized_keys 失败。"
}
Start-Sleep -Seconds 3
if (-not (Test-KeyLogin)) {
throw "SSH 密钥登录验证失败,请检查服务器 SSH 配置。"
}
Write-Host "SSH 密钥配置完成,后续连接无需密码环节。" -ForegroundColor Green
}
$remoteScript = @"
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
# 初始化 Debian 基础源与依赖补齐
cat >/etc/apt/sources.list <<'EOF'
deb http://deb.debian.org/debian bullseye main contrib non-free
deb http://deb.debian.org/debian bullseye-updates main contrib non-free
deb http://security.debian.org/debian-security bullseye-security main contrib non-free
EOF
apt-get clean
rm -rf /var/lib/apt/lists/*
apt-get update
apt-get install -y wget curl tar ufw gnupg debian-keyring debian-archive-keyring apt-transport-https
# 检测和自动安装 FRPS
FRPS_INSTALLED='false'
if [ -x /opt/frp/frps ] && [ -f /etc/systemd/system/frps.service ]; then
echo '检测到 FRPS 已存在,自动跳过安装。'
else
FRP_VERSION='${FrpVersion}'
if [ -z "\$FRP_VERSION" ] || [ "\$FRP_VERSION" = "latest" ]; then
FRP_VERSION=\$(curl -fsSL https://api.github.com/repos/fatedier/frp/releases/latest | sed -n 's/.*"tag_name":[[:space:]]*"v\([^"]*\)".*/\1/p' | head -n1)
fi
if [ -z "\$FRP_VERSION" ]; then
echo '无法获取 FRP 版本号,请检查网络重试。'
exit 1
fi
ARCHIVE="frp_\${FRP_VERSION}_linux_amd64.tar.gz"
URL="https://github.com/fatedier/frp/releases/download/v\${FRP_VERSION}/\$ARCHIVE"
cd /tmp
wget -O "\$ARCHIVE" "\$URL"
tar -xzf "\$ARCHIVE"
install -d /opt/frp /etc/frp
install -m 755 "/tmp/frp_\${FRP_VERSION}_linux_amd64/frps" /opt/frp/frps
cat >/etc/systemd/system/frps.service <<'EOF'
[Unit]
Description=FRP Server
After=network.target
[Service]
Type=simple
ExecStart=/opt/frp/frps -c /etc/frp/frps.toml
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
FRPS_INSTALLED='true'
fi
# 检测并自动安装 Caddy
CADDY_INSTALLED='false'
if command -v caddy >/dev/null 2>&1; then
echo '检测到 Caddy 已存在,跳过安装。'
else
install -m 0755 -d /etc/apt/keyrings
rm -f /etc/apt/keyrings/caddy-stable-archive-keyring.gpg
curl -fsSL https://dl.cloudsmith.io/public/caddy/stable/gpg.key | gpg --dearmor --batch --yes -o /etc/apt/keyrings/caddy-stable-archive-keyring.gpg
chmod a+r /etc/apt/keyrings/caddy-stable-archive-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/caddy-stable-archive-keyring.gpg] https://dl.cloudsmith.io/public/caddy/stable/deb/debian any-version main' >/etc/apt/sources.list.d/caddy-stable.list
apt-get update
apt-get install -y caddy
CADDY_INSTALLED='true'
fi
# 核心步骤:向 Caddy 植入 Cloudflare 自动证书功能组件
if caddy list-modules | grep -q 'dns.providers.cloudflare'; then
echo '检测到 Caddy 已安装 Cloudflare DNS 插件。'
else
echo '正在安装 Caddy Cloudflare DNS 插件...'
caddy add-package github.com/caddy-dns/cloudflare || echo '安装 Caddy 插件失败。'
systemctl restart caddy || true
fi
systemctl enable frps >/dev/null 2>&1 || true
systemctl enable caddy >/dev/null 2>&1 || true
# 配置防火墙墙规则,直接接通端口
ufw allow ${SshPort}/tcp
ufw allow ${FrpsBindPort}/tcp
ufw allow 80/tcp
ufw allow 443/tcp
"@
if ($AllowDashboardInUfw) {
$remoteScript += "`nufw allow ${DashboardPort}/tcp"
}
$remoteScript += @"
ufw --force enable
echo '=== BASE DEPLOY DONE ==='
echo 'FRP binary:'
/opt/frp/frps --version || true
echo 'caddy:'
caddy version || true
echo 'frps service enabled:'
systemctl is-enabled frps || true
echo 'caddy service enabled:'
systemctl is-enabled caddy || true
"@
$tmpScript = Join-Path $env:TEMP "deploy_frps_caddy_remote.sh"
# 剥除 BOM 操作,确保 Windows 下安全执行
$remoteScript = $remoteScript -replace [char]0xFEFF, "" -replace "`r`n", "`n" -replace "`r", ""
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
[IO.File]::WriteAllText($tmpScript, $remoteScript, $utf8NoBom)
Ensure-SshKeyAuth
Write-Host "开始基础部署到 $ServerIp ..." -ForegroundColor Cyan
(Get-Content $tmpScript -Raw) -replace [char]0xFEFF, "" -replace "`r`n", "`n" | & $ssh @sshOptions -p $SshPort -i $SshPrivateKeyPath -o BatchMode=yes "$SshUser@$ServerIp" "bash -s"
Write-Host "基础部署完成!第一步结束。" -ForegroundColor Green
打开当前文件夹,并在空白处右键选择“在终端中打开”(即打开 PowerShell),输入:
[.\deploy_server_frps_caddy.ps1]然后回车。
它将帮你打通密钥认证(首次会向你提示索要服务器密码),接着全自动为你安装好 FRP 与 Caddy、开启系统自启并打通网络防火墙设置,大约喝口水的时间即可执行完毕。
第三步:一键推送配置文件
运行环境装好了,我们需要把咱们本地准备好那两个配置文件内容推送到云端执行。
同理,新建一个名为 [update_server_configs.ps1]的脚本文件,修改开头的 IP,以后但凡你在本地修改了那两份配置文件,只需要一键运行此脚本即可零痛感应用变更!
点击展开查看:配置文件静默推送脚本代码
# 强制管道输出使用无 BOM 的 UTF-8
$OutputEncoding = [System.Text.UTF8Encoding]::new($false)
# ========================= 必填参数(按需修改) =========================
$ServerIp = "[填写你的VPS公网IP]"
$SshUser = "root"
$SshPort = 22
$AcceptNewHostKey = $true
$SshPrivateKeyPath = Join-Path $env:USERPROFILE ".ssh\id_ed25519"
# 自动从所在的目录抓取配置文件
$LocalFrpsConfig = Join-Path $PSScriptRoot "frps.toml"
$LocalCaddyConfig = Join-Path $PSScriptRoot "Caddyfile"
$RemoteFrpsConfig = "/etc/frp/frps.toml"
$RemoteCaddyConfig = "/etc/caddy/Caddyfile"
$ErrorActionPreference = "Stop"
function Get-OpenSshTool([string]$Name) {
$cmd = Get-Command $Name -ErrorAction SilentlyContinue
if ($cmd) { return $cmd.Source }
throw "未找到 $Name。请先启用 Windows OpenSSH Client。"
}
if (-not (Test-Path $LocalFrpsConfig)) { throw "未找到本地文件: $LocalFrpsConfig" }
if (-not (Test-Path $LocalCaddyConfig)) { throw "未找到本地文件: $LocalCaddyConfig" }
if (-not (Test-Path $SshPrivateKeyPath)) { throw "未找到 SSH 私钥文件。请确认先成功执行了基础部署脚本。" }
$ssh = Get-OpenSshTool "ssh.exe"
$scp = Get-OpenSshTool "scp.exe"
$sshOptions = @()
if ($AcceptNewHostKey) {
$sshOptions += @("-o", "StrictHostKeyChecking=accept-new")
}
Write-Host "上传配置文件到远端临时目录..." -ForegroundColor Cyan
& $scp @sshOptions -i $SshPrivateKeyPath -o BatchMode=yes -P $SshPort $LocalFrpsConfig "${SshUser}@${ServerIp}:/tmp/frps.toml.new"
& $scp @sshOptions -i $SshPrivateKeyPath -o BatchMode=yes -P $SshPort $LocalCaddyConfig "${SshUser}@${ServerIp}:/tmp/Caddyfile.new"
$remoteScript = @"
set -euo pipefail
# 检查上传是否已抵达
if [ ! -f /tmp/frps.toml.new ]; then
exit 1
fi
if [ ! -f /tmp/Caddyfile.new ]; then
exit 1
fi
# 检查 Caddy 规则是否符合语法
caddy validate --config /tmp/Caddyfile.new
TS=\$(date +%Y%m%d%H%M%S)
cp -a ${RemoteFrpsConfig} ${RemoteFrpsConfig}.bak.\$TS || true
cp -a ${RemoteCaddyConfig} ${RemoteCaddyConfig}.bak.\$TS || true
install -m 600 /tmp/frps.toml.new ${RemoteFrpsConfig}
install -m 644 /tmp/Caddyfile.new ${RemoteCaddyConfig}
systemctl daemon-reload
systemctl enable --now frps
systemctl enable --now caddy
systemctl restart frps
systemctl restart caddy
echo '服务健康度检查:'
systemctl is-active frps
systemctl is-active caddy
"@
$tmpScript = Join-Path $env:TEMP "apply_server_configs.sh"
$remoteScript = $remoteScript -replace [char]0xFEFF, "" -replace "`r`n", "`n" -replace "`r", ""
$utf8NoBom = [System.Text.UTF8Encoding]::new($false)
[IO.File]::WriteAllText($tmpScript, $remoteScript, $utf8NoBom)
Write-Host "应用远端配置并重启服务..." -ForegroundColor Cyan
(Get-Content $tmpScript -Raw) -replace [char]0xFEFF, "" -replace "`r`n", "`n" | & $ssh @sshOptions -p $SshPort -i $SshPrivateKeyPath -o BatchMode=yes "$SshUser@$ServerIp" "bash -s"
Write-Host "更新完成。服务端堡垒已经搭设完毕!" -ForegroundColor Green
在刚才的 PowerShell 界面中敲入执行 [.\update_server_configs.ps1],云端的事宜目前就全部大功告成了!你可以关掉面板不闻不问了。
第四步:最后阶段 —— 本地电脑配置穿透对碰 (FRPC)
最后,我们需要回到你本地“捡垃圾”组好的这台高性能主机上。从官方下载与服务端同版本的 frpc.exe(本地客户端),在同级目录新建 [frpc.toml] 做匹配:
# ======================= FRPC 客户端配置 =======================
# ---------- 连接服务端 ----------
serverAddr = "[填写你的VPS公网IP]"
serverPort = 7000
# ---------- 鉴权 ----------
auth.method = "token"
# 【重要】:必须与上面那份 frps.toml 里填的那个一模一样,两头才能对上暗号
auth.token = "[填写上文中配置的强随机TOKEN]"
# ---------- 传输安全 ----------
transport.tls.enable = true
# ================================================================
# 代理规则:即把我这台高性能主机的哪块业务,映射去云端?
# ================================================================
# 穿透 Git 服务举例
[[proxies]]
name = "git"
type = "tcp"
localIP = "127.0.0.1"
localPort = 3000 # 假设你本机跑的 Git 是端口 3000
remotePort = 13000 # 把它送到 VPS 上的 13000,Caddy 会在那对接
# 穿透群晖/黑裙 NAS 举例
[[proxies]]
name = "my_nas"
type = "tcp"
localIP = "127.0.0.1"
localPort = 5000
remotePort = 15000
设为开机自启
在 frpc.exe 同一文件夹下,新建一个记事本文件并命名为 start_frpc.vbs(注意一定要改成 .vbs 后缀名),里面只需填入以下仅仅两行的神奇代码:
Set ws = CreateObject("Wscript.Shell")
ws.run "cmd /c frpc.exe -c frpc.toml", 0
(注意:末尾的 0 代表后台静默运行)
现在双击这个 start_frpc.vbs 文件,FRP 就已经在后台静默运行了,桌面上干干净净不会有任何打扰。
如果你想要它随着本地服务器开机自动运行:只需要右键点击这个 start_frpc.vbs,选择“创建快捷方式”。随后按下键盘的 Win + R 组合键,输入 shell:startup 回车,将刚才生成的快捷方式拖进这个弹出来的启动文件夹中,一切就大功告成啦!
结语
如果看到这里,恭喜你成功打造了一个完全自主掌控并且安全免费的全球公网隧道!你通过“捡垃圾”极大压缩了高性能存储与计算成本。依托这一套一劳永逸、“即用即跑”的自动化脚本方案,你只花最基础买个香港/日本极简“跳板节点”的钱,结合 Caddy 永不操心自动续期的 HTTPS 能力,享受了一整套拥有顶级安全性、私密性、低延迟的高颜值公网家庭数据中心体验!享受你的折腾旅途吧。