这篇记录 FreeBSD 15 上 Nginx 前置 Gost v3 MWSS 的完整部署流程,目标是做一条可长期运行、伪装自然、客户端容易接入的 TCP 代理节点。
先说结论:在新的 FreeBSD 15 VPS 上,Gost MWSS + Nginx 的表现比 Xray Reality TCP 更适合作为这台机器的 TCP 生产候选。实测同一台服务器、同一个客户端下,Xray Reality 大文件下载多在 6-9 MB/s,Gost MWSS + Nginx 多轮在 8-18 MB/s 间波动,最终选择 Gost 作为这台 FreeBSD 的 TCP 主方案。
这个结果也说明 FreeBSD TCP 代理性能和 VPS 商家、虚拟化环境、线路关系很大。不要只看延迟,部署完一定要做本文最后的大文件吞吐测试。
整体结构如下:
sing-box -> 127.0.0.1:1080 -> 客户端 Gost
-> mwss://proxy.example.com:443/secret-gost-path
-> FreeBSD Nginx 443
-> 127.0.0.1:18080 -> 服务端 Gost v3 MWS这里有一个关键点:普通用户不能监听 443 这种低端口,但本方案监听 443/tcp 的是 Nginx。Nginx master 以 root 启动绑定低端口,worker 用 www 用户处理请求;Gost 只监听本机 127.0.0.1:18080,所以可以安全地用专用低权限用户运行。
示例信息
本文统一使用公版示例值,部署时替换成自己的真实信息:
| 项目 | 示例值 |
|---|---|
| 域名 | proxy.example.com |
| 服务器公网 IP | 203.0.113.10 |
| WebSocket 路径 | /secret-gost-path |
| Gost 用户名 | GOST_USER |
| Gost 密码 | GOST_PASS |
| Nginx 入口 | 443/tcp |
| Gost 后端 | 127.0.0.1:18080 |
| 客户端本地 SOCKS | 127.0.0.1:1080 |
本文命令默认使用 root 执行。
支持系统
本文面向 FreeBSD 15 amd64。Gost v3 官方 release 也提供 FreeBSD arm64 包,如果你的机器是 ARM64,把下载文件名里的 freebsd_amd64 改成 freebsd_arm64。
FreeBSD 和 Linux 的主要差异:
- FreeBSD 使用
rc.d管理服务,不使用 systemd。 - Nginx 配置路径通常是
/usr/local/etc/nginx/。 - 网站目录建议放在
/usr/local/www/。 - certbot 证书路径通常是
/usr/local/etc/letsencrypt/live/<domain>/。 - sysctl 持久配置写入
/etc/sysctl.conf。
1. 准备域名
先注册一个域名,例如:
example.com建议不要直接用根域名做节点入口,而是使用一个看起来像普通业务服务的子域名,例如:
proxy.example.com
cloud.example.com
files.example.com
drive.example.com本文后续统一以 proxy.example.com 为例。
在 DNS 后台添加一条 A 记录:
| 类型 | 主机记录 | 记录值 |
|---|---|---|
A | proxy | 203.0.113.10 |
如果服务器没有 IPv6,不要添加 AAAA 记录,避免客户端优先走 IPv6 后连接失败。
在服务器上检查解析:
drill proxy.example.com正常应该能看到:
proxy.example.com. 300 IN A 203.0.113.10没有 drill 时也可以用:
host proxy.example.com签证书前必须确认域名已经解析到当前服务器公网 IP。
同时确认防火墙或云安全组已经放行:
| 协议 | 端口 | 用途 |
|---|---|---|
| TCP | 22 | SSH 管理 |
| TCP | 80 | Let’s Encrypt HTTP 验证和 HTTP 跳转 |
| TCP | 443 | Nginx HTTPS 入口和 MWSS 入口 |
本方案不需要对公网放行 18080,这个端口只给本机 Nginx 访问。
2. 安装基础组件
更新包索引并安装 Nginx、certbot 和常用工具:
pkg update -f
pkg install -y nginx py311-certbot ca_root_nss curl启用 Nginx:
sysrc nginx_enable="YES"先不要急着启动 HTTPS,后面要先用 HTTP 站点签证书。
3. 安装 Gost v3
以 amd64 为例:
GOST_VERSION="3.2.6"
fetch -o /tmp/gost_freebsd_amd64.tar.gz \
"https://github.com/go-gost/gost/releases/download/v${GOST_VERSION}/gost_${GOST_VERSION}_freebsd_amd64.tar.gz"
mkdir -p /tmp/gost-v3
tar -xzf /tmp/gost_freebsd_amd64.tar.gz -C /tmp/gost-v3
install -m 0755 /tmp/gost-v3/gost /usr/local/bin/gost
/usr/local/bin/gost -V正常会看到类似:
gost v3.2.6 (go1.25.4 freebsd/amd64)如果你的服务器是 ARM64,把下载文件名改成:
gost_${GOST_VERSION}_freebsd_arm64.tar.gz4. 创建伪装站点
创建站点目录:
mkdir -p /usr/local/www/proxy.example.com写入一个英文网盘登录页:
ee /usr/local/www/proxy.example.com/index.html内容如下:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex,nofollow">
<title>Cloud Drive</title>
<style>
:root {
color-scheme: light;
--bg: #f6f8fb;
--panel: #ffffff;
--panel-soft: #f9fbfd;
--text: #172033;
--muted: #637083;
--line: #dce3ec;
--accent: #2368c4;
--accent-dark: #174f99;
--focus: rgba(35, 104, 196, .18);
--shadow: 0 24px 70px rgba(23, 32, 51, .12);
}
* { box-sizing: border-box; }
html, body { min-height: 100%; }
body {
margin: 0;
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
color: var(--text);
background:
linear-gradient(135deg, rgba(35, 104, 196, .08), transparent 34%),
linear-gradient(315deg, rgba(42, 157, 143, .08), transparent 36%),
var(--bg);
display: grid;
place-items: center;
padding: 28px;
}
.shell {
width: min(980px, 100%);
min-height: 620px;
display: grid;
grid-template-columns: minmax(0, 1.08fr) minmax(360px, .92fr);
overflow: hidden;
border: 1px solid rgba(220, 227, 236, .9);
border-radius: 6px;
background: var(--panel);
box-shadow: var(--shadow);
}
.overview {
position: relative;
padding: 56px;
background: linear-gradient(180deg, #ffffff 0%, #f4f8fc 100%);
border-right: 1px solid var(--line);
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 48px;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
font-size: 15px;
font-weight: 700;
letter-spacing: 0;
}
.logo {
width: 36px;
height: 36px;
border-radius: 6px;
background: var(--accent);
display: grid;
place-items: center;
color: #fff;
}
.hero h1 {
max-width: 520px;
margin: 0 0 18px;
font-size: clamp(36px, 5vw, 58px);
line-height: 1.02;
letter-spacing: 0;
}
.hero p {
max-width: 490px;
margin: 0;
color: var(--muted);
font-size: 17px;
line-height: 1.7;
}
.status-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 12px;
}
.status-item {
min-height: 92px;
padding: 18px;
border: 1px solid var(--line);
border-radius: 6px;
background: rgba(255, 255, 255, .72);
}
.status-label {
color: var(--muted);
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0;
}
.status-value {
margin-top: 10px;
font-size: 20px;
font-weight: 750;
}
.login {
padding: 56px 48px;
display: flex;
flex-direction: column;
justify-content: center;
background: var(--panel);
}
.login h2 {
margin: 0 0 10px;
font-size: 28px;
letter-spacing: 0;
}
.login .hint {
margin: 0 0 32px;
color: var(--muted);
line-height: 1.6;
}
form {
display: grid;
gap: 18px;
}
label {
display: grid;
gap: 8px;
color: #2d3848;
font-size: 14px;
font-weight: 650;
}
input[type="email"],
input[type="password"] {
width: 100%;
height: 48px;
border: 1px solid var(--line);
border-radius: 6px;
padding: 0 14px;
color: var(--text);
background: var(--panel-soft);
font: inherit;
outline: none;
transition: border-color .18s ease, box-shadow .18s ease, background .18s ease;
}
input:focus {
border-color: var(--accent);
box-shadow: 0 0 0 4px var(--focus);
background: #fff;
}
.row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px;
margin-top: 2px;
color: var(--muted);
font-size: 14px;
}
.remember {
display: inline-flex;
align-items: center;
gap: 8px;
font-weight: 500;
color: var(--muted);
}
input[type="checkbox"] {
width: 16px;
height: 16px;
accent-color: var(--accent);
}
a {
color: var(--accent);
text-decoration: none;
font-weight: 650;
}
button {
height: 50px;
border: 0;
border-radius: 6px;
background: var(--accent);
color: #fff;
font: inherit;
font-weight: 750;
cursor: pointer;
transition: background .18s ease, transform .18s ease;
}
button:hover { background: var(--accent-dark); }
button:active { transform: translateY(1px); }
.note {
margin-top: 28px;
padding: 16px;
border: 1px solid var(--line);
border-radius: 6px;
background: var(--panel-soft);
color: var(--muted);
font-size: 14px;
line-height: 1.6;
}
.footer {
margin-top: 28px;
color: #7d8898;
font-size: 13px;
line-height: 1.6;
}
@media (max-width: 820px) {
body { padding: 18px; align-items: start; }
.shell { grid-template-columns: 1fr; min-height: auto; }
.overview { padding: 34px; border-right: 0; border-bottom: 1px solid var(--line); gap: 34px; }
.login { padding: 34px; }
.status-grid { grid-template-columns: 1fr; }
.hero h1 { font-size: 38px; }
}
@media (max-width: 460px) {
body { padding: 0; background: var(--panel); }
.shell { border: 0; border-radius: 0; box-shadow: none; }
.overview, .login { padding: 28px 20px; }
.row { align-items: flex-start; flex-direction: column; }
}
</style>
</head>
<body>
<main class="shell" aria-label="Cloud Drive sign in">
<section class="overview" aria-label="Service overview">
<div class="brand">
<div class="logo" aria-hidden="true">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" role="img">
<path d="M7.4 18.5h9.1a4.1 4.1 0 0 0 .6-8.15 5.35 5.35 0 0 0-10.18-1.9A5.05 5.05 0 0 0 7.4 18.5Z" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<span>Cloud Drive</span>
</div>
<div class="hero">
<h1>Secure file access for your workspace.</h1>
<p>Sign in to manage shared files, private folders, transfer history, and device sessions from one protected workspace.</p>
</div>
<div class="status-grid" aria-label="System status">
<div class="status-item">
<div class="status-label">Storage</div>
<div class="status-value">Encrypted</div>
</div>
<div class="status-item">
<div class="status-label">Access</div>
<div class="status-value">Private</div>
</div>
<div class="status-item">
<div class="status-label">Sync</div>
<div class="status-value">Online</div>
</div>
</div>
</section>
<section class="login" aria-label="Sign in form">
<h2>Sign in</h2>
<p class="hint">Use your workspace account to continue.</p>
<form action="/" method="post" autocomplete="on">
<label>
Email address
<input type="email" name="email" autocomplete="email" inputmode="email" required>
</label>
<label>
Password
<input type="password" name="password" autocomplete="current-password" required>
</label>
<div class="row">
<label class="remember">
<input type="checkbox" name="remember">
Remember this device
</label>
<a href="/">Forgot password?</a>
</div>
<button type="submit">Sign in</button>
</form>
<div class="note">Need access? Contact your workspace administrator to request an account or recover permissions.</div>
<div class="footer">Protected workspace access. Unauthorized use is prohibited.</div>
</section>
</main>
</body>
</html>5. 配置 Nginx 主配置
备份默认配置:
cp /usr/local/etc/nginx/nginx.conf /usr/local/etc/nginx/nginx.conf.bak编辑主配置:
ee /usr/local/etc/nginx/nginx.conf写成下面这样:
worker_processes auto;
worker_rlimit_nofile 1048576;
error_log /var/log/nginx/error.log;
pid /var/run/nginx.pid;
events {
worker_connections 65535;
multi_accept on;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
server_tokens off;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:20m;
ssl_session_timeout 1h;
gzip on;
include /usr/local/etc/nginx/conf.d/*.conf;
}创建站点配置目录:
mkdir -p /usr/local/etc/nginx/conf.d检查 Nginx 优化是否生效
先检查配置语法:
nginx -t再查看 Nginx 实际加载后的完整配置:
nginx -T | egrep 'worker_processes|worker_rlimit_nofile|worker_connections|multi_accept|tcp_nopush|tcp_nodelay|server_tokens|ssl_session_cache'正常能看到类似这些关键项:
worker_processes auto;
worker_rlimit_nofile 1048576;
worker_connections 65535;
multi_accept on;
tcp_nopush on;
tcp_nodelay on;
server_tokens off;
ssl_session_cache shared:SSL:20m;启动 Nginx 后检查 master 和 worker:
service nginx start
service nginx status
ps -axo user,pid,command | grep '[n]ginx'生产状态一般是:
root nginx: master process /usr/local/sbin/nginx
www nginx: worker process这说明 root 只负责绑定 80/443 低端口,实际请求由 www worker 处理。
最后确认端口监听:
sockstat -4 -6 -l | egrep ':(80|443)'能看到 Nginx 监听 80/tcp 和 443/tcp 即可。后端 18080 不应该由 Nginx 监听,后面启动 Gost 后才会出现。
6. 先配置 HTTP 站点
第一次签 Let’s Encrypt 证书,需要先让 HTTP 站点能访问。
ee /usr/local/etc/nginx/conf.d/gost-mwss.conf写入临时 HTTP 配置:
server {
listen 80;
listen [::]:80;
server_name proxy.example.com;
root /usr/local/www/proxy.example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}检查并启动 Nginx:
nginx -t
service nginx start从浏览器访问:
http://proxy.example.com/如果能看到英文登录页,说明 DNS、80 端口和 Nginx root 都正确。
7. 签发 Let’s Encrypt 证书
使用 certbot 的 webroot 模式签证书:
certbot certonly --webroot \
-w /usr/local/www/proxy.example.com \
-d proxy.example.com \
--agree-tos \
--register-unsafely-without-email \
--non-interactive成功后证书路径是:
/usr/local/etc/letsencrypt/live/proxy.example.com/fullchain.pem
/usr/local/etc/letsencrypt/live/proxy.example.com/privkey.pem启用 FreeBSD 的 certbot 周期续期:
sysrc -f /etc/periodic.conf weekly_certbot_enable="YES"添加续期后 reload Nginx 的 hook:
mkdir -p /usr/local/etc/letsencrypt/renewal-hooks/deploy
cat <<'EOF' > /usr/local/etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/sh
service nginx reload
EOF
chmod +x /usr/local/etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh先不急着 dry-run,等 HTTPS 配置完成后再统一测试。
8. 配置 HTTPS 和 MWSS 反代
重新编辑站点配置:
ee /usr/local/etc/nginx/conf.d/gost-mwss.conf写入完整 HTTPS 和 MWSS 反代配置:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444;
}
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
http2 on;
server_name _;
ssl_certificate /usr/local/etc/letsencrypt/live/proxy.example.com/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/proxy.example.com/privkey.pem;
return 444;
}
server {
listen 80;
listen [::]:80;
server_name proxy.example.com;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name proxy.example.com;
ssl_certificate /usr/local/etc/letsencrypt/live/proxy.example.com/fullchain.pem;
ssl_certificate_key /usr/local/etc/letsencrypt/live/proxy.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
root /usr/local/www/proxy.example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
location /secret-gost-path {
access_log off;
proxy_pass http://127.0.0.1:18080;
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_buffering off;
proxy_socket_keepalive on;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
}这里故意没有写成 try_files $uri $uri/ /index.html;。正常访问 / 时,Nginx 会按 index index.html 显示英文网盘登录页;访问不存在的路径时直接返回 404;访问未绑定的 Host 时由 default server 返回 444。这样比所有路径都显示同一个页面更像正常站点,也能少暴露一点扫描特征。
说明:
- 前两个
default_server是兜底配置,非目标域名访问直接断开。 - 普通浏览器访问
/会看到英文网盘登录页。 - 只有
/secret-gost-path会被转发到本机 Gost 后端。 - Gost 后端只监听
127.0.0.1:18080,不暴露公网。
检查配置:
nginx -t
service nginx restart确认 HTTPS 伪装页可用:
curl -I https://proxy.example.com/正常应返回 HTTP/2 200。
9. FreeBSD 网络参数优化
追加 TCP 和 socket 缓冲优化:
cat <<'EOF' >> /etc/sysctl.conf
# Gost MWSS TCP tuning
kern.ipc.maxsockbuf=33554432
net.inet.tcp.sendbuf_max=16777216
net.inet.tcp.recvbuf_max=16777216
net.inet.tcp.sendspace=1048576
net.inet.tcp.recvspace=1048576
net.inet.tcp.mssdflt=1460
net.inet.tcp.cc.algorithm=cubic
net.inet.tcp.fast_finwait2_recycle=1
net.inet.tcp.syncookies=1
kern.ipc.somaxconn=65535
EOF
sysctl -f /etc/sysctl.conf查看当前值:
sysctl kern.ipc.maxsockbuf kern.ipc.somaxconn
sysctl net.inet.tcp.sendbuf_max net.inet.tcp.recvbuf_max
sysctl net.inet.tcp.sendspace net.inet.tcp.recvspace
sysctl net.inet.tcp.cc.algorithm这些参数用于提升 TCP 缓冲、监听队列和高延迟链路表现。FreeBSD 15 默认 TCP 栈已经比较稳,不要照搬 Linux 的 BBR 命令。
10. 创建低权限 Gost 用户
创建专用用户和组:
pw groupshow gost >/dev/null 2>&1 || pw groupadd gost
pw usershow gost >/dev/null 2>&1 || pw useradd gost -g gost -d /usr/local/var/empty/gost -s /bin/sh -c "Gost service user"
mkdir -p /usr/local/var/empty/gost
chown gost:gost /usr/local/var/empty/gost
chmod 0755 /usr/local/var/empty/gost如果用户已经存在,可以跳过创建,检查一下即可:
id gost11. 写入 Gost 后端配置
创建 root-only 配置目录:
install -d -m 0750 -o root -g wheel /usr/local/etc/gost写入后端监听和认证信息:
cat <<'EOF' > /usr/local/etc/gost/gost_mwss.conf
gost_mwss_backend="mws://GOST_USER:GOST_PASS@127.0.0.1:18080?path=/secret-gost-path"
EOF
chmod 600 /usr/local/etc/gost/gost_mwss.conf
chown root:wheel /usr/local/etc/gost/gost_mwss.conf把 GOST_USER 和 GOST_PASS 改成自己的用户名和强密码。
注意这里是 mws://,因为 TLS 已经由 Nginx 负责,Gost 后端只处理本机明文 WebSocket。
12. 创建 Gost runner
runner 由 root 执行,读取 root-only 配置文件,然后用 su -m gost 把真正的 Gost 子进程降权到 gost 用户。
cat <<'EOF' > /usr/local/sbin/gost_mwss_runner
#!/bin/sh
set -eu
CONFIG="/usr/local/etc/gost/gost_mwss.conf"
if [ -r "$CONFIG" ]; then
. "$CONFIG"
fi
: ${gost_mwss_run_user:="gost"}
: ${gost_mwss_backend:="mws://GOST_USER:GOST_PASS@127.0.0.1:18080?path=/secret-gost-path"}
exec /usr/bin/su -m "$gost_mwss_run_user" -c "exec /usr/local/bin/gost -L \"$gost_mwss_backend\""
EOF
chmod 700 /usr/local/sbin/gost_mwss_runner
chown root:wheel /usr/local/sbin/gost_mwss_runner这样密码不会出现在 rc.conf 里,也不会放在普通可读脚本里。
13. 创建 rc.d 服务
创建服务脚本:
cat <<'EOF' > /usr/local/etc/rc.d/gost_mwss
#!/bin/sh
# PROVIDE: gost_mwss
# REQUIRE: NETWORKING nginx
# KEYWORD: shutdown
. /etc/rc.subr
name="gost_mwss"
rcvar="gost_mwss_enable"
load_rc_config $name
: ${gost_mwss_enable:="NO"}
: ${gost_mwss_run_user:="gost"}
: ${gost_mwss_log:="/var/log/gost-mwss.log"}
: ${gost_mwss_restart_delay:="3"}
: ${gost_mwss_pid_dir:="/var/run/gost_mwss"}
pidfile="${gost_mwss_pid_dir}/${name}.pid"
command="/usr/sbin/daemon"
start_precmd="gost_mwss_precmd"
stop_cmd="gost_mwss_stop"
status_cmd="gost_mwss_status"
command_args="-f -r -R ${gost_mwss_restart_delay} -p ${pidfile} -o ${gost_mwss_log} /usr/local/sbin/gost_mwss_runner"
gost_mwss_precmd()
{
install -d -o root -g wheel -m 0755 ${gost_mwss_pid_dir}
touch ${gost_mwss_log}
chown ${gost_mwss_run_user}:${gost_mwss_run_user} ${gost_mwss_log}
chmod 0640 ${gost_mwss_log}
}
gost_mwss_status()
{
pgrep -u ${gost_mwss_run_user} -f "/usr/local/bin/gost -L" >/dev/null && echo "${name} is running." || echo "${name} is not running."
}
gost_mwss_stop()
{
pkill -u ${gost_mwss_run_user} -f "/usr/local/bin/gost -L" 2>/dev/null || true
pkill -f "/usr/local/sbin/gost_mwss_runner" 2>/dev/null || true
rm -f ${pidfile}
}
run_rc_command "$1"
EOF
chmod 755 /usr/local/etc/rc.d/gost_mwss
chown root:wheel /usr/local/etc/rc.d/gost_mwss启用服务:
sysrc gost_mwss_enable="YES"
sysrc gost_mwss_run_user="gost"启动:
service gost_mwss start
service gost_mwss status确认进程用户和监听地址:
ps -axo user,pid,command | egrep '[g]ost_mwss_runner|[s]u -m gost|[g]ost -L mws'
sockstat -4 -6 -l | grep ':18080'正常状态应该类似:
root daemon: /usr/local/sbin/gost_mwss_runner
root /usr/bin/su -m gost -c exec /usr/local/bin/gost ...
gost /usr/local/bin/gost -L mws://...
gost gost tcp4 127.0.0.1:18080重点是最后真正的 Gost 子进程必须是 gost 用户,并且监听 127.0.0.1:18080。
14. 配置日志轮转
创建日志文件:
touch /var/log/gost-mwss.log
chown gost:gost /var/log/gost-mwss.log
chmod 640 /var/log/gost-mwss.log创建 newsyslog 配置目录:
mkdir -p /usr/local/etc/newsyslog.conf.d写入轮转配置:
cat <<'EOF' > /usr/local/etc/newsyslog.conf.d/gost-mwss.conf
/var/log/gost-mwss.log gost:gost 640 7 1024 * JC
EOF含义是:日志超过 1024KB 后压缩轮转,保留 7 份。
15. 完整服务检查
检查 Nginx:
nginx -t
service nginx status检查 Gost:
service gost_mwss status
tail -f /var/log/gost-mwss.log检查端口:
sockstat -4 -6 -l | egrep ':(80|443|18080|22)'理想状态:
nginx *:80
nginx *:443
gost 127.0.0.1:18080
sshd *:22如果还有其他服务占用 443/tcp,需要先迁移。占用 443/udp 的服务不影响本方案,因为 Nginx 用的是 TCP。
16. 证书续期演练
执行 dry-run:
certbot renew --dry-run --no-random-sleep-on-renew正常输出应包含:
Congratulations, all simulated renewals succeeded如果不加 --no-random-sleep-on-renew,certbot 可能随机等待几分钟,这是正常行为,但手动测试时比较烦。
17. 客户端 Gost 测试
在客户端安装 Gost v3 后,前台测试:
gost -L socks5://127.0.0.1:1080 \
-F "mwss://GOST_USER:GOST_PASS@proxy.example.com:443?path=/secret-gost-path"另开一个终端测试:
curl --socks5-hostname 127.0.0.1:1080 https://ifconfig.me如果输出是服务端公网 IP,说明链路已经打通。
Linux 客户端可以写成 systemd 服务:
[Unit]
Description=Gost MWSS client
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
ExecStart=/usr/local/bin/gost -L "socks5://127.0.0.1:1080" -F "mwss://GOST_USER:GOST_PASS@proxy.example.com:443?path=/secret-gost-path"
Restart=always
RestartSec=3
LimitNOFILE=1048576
[Install]
WantedBy=multi-user.target保存为:
/etc/systemd/system/gost-mwss.service启用:
systemctl daemon-reload
systemctl enable --now gost-mwss
systemctl status gost-mwss18. sing-box 客户端接入
sing-box 不需要直接支持 MWSS,只需要把流量交给本机 Gost 提供的 SOCKS5。
分片配置示例
在 04_outbounds.json 的 selector 里加入新节点 tag:
{
"tag": "Proxy",
"type": "selector",
"outbounds": [
"auto",
"node-a",
"node-b",
"gost-mwss"
],
"interrupt_exist_connections": true,
"default": "auto"
}如果有 urltest,也把它加进去:
{
"tag": "auto",
"type": "urltest",
"outbounds": [
"node-a",
"node-b",
"gost-mwss"
],
"interval": "2m0s"
}在 outbounds 数组靠后位置加入 SOCKS 出站:
{
"type": "socks",
"tag": "gost-mwss",
"server": "127.0.0.1",
"server_port": 1080,
"version": "5",
"udp_over_tcp": false
}路由直连规则
在 05_route.json 的 route.rules 最前面加入节点域名和节点 IP 直连:
{
"domain": [
"proxy.example.com"
],
"ip_cidr": [
"203.0.113.10/32"
],
"outbound": "direct"
}带上下文示例:
{
"route": {
"final": "Proxy",
"rules": [
{
"domain": [
"proxy.example.com"
],
"ip_cidr": [
"203.0.113.10/32"
],
"outbound": "direct"
},
{
"ip_is_private": true,
"outbound": "direct"
},
{
"rule_set": "geosite-geolocation-!cn",
"outbound": "Proxy"
}
]
}
}这条直连规则是为了避免代理回环。但要注意,sing-box 的 route.rules 只影响进入 sing-box 的流量,运行在本机的 Gost 客户端是独立进程,它自己会走系统 DNS 解析 proxy.example.com。
fake-ip DNS 环境必须固定 Gost 节点解析
如果运行 Gost 客户端的机器同时跑了 sing-box/mosdns,并且系统 DNS 指向本机 fake-ip DNS,Gost 可能把节点域名解析成 28.0.0.0/8 这类虚拟 IP。结果就是 Gost 去连接 fake-ip,而不是真实服务器 IP。
这个问题很容易误判成节点被墙或服务端故障:
- sing-box 面板里 Gost 节点测速失败,或者延迟一直测不出来。
curl --socks5-hostname 127.0.0.1:1080 https://www.gstatic.com/generate_204卡住直到超时。- Gost 客户端日志里上游目标是 fake-ip,例如
remote="28.0.14.153:443"。 - 服务端 Nginx/Gost 看不到请求,或者只看到 WebSocket 异常断开。
先在运行 Gost 客户端的机器上检查解析结果:
getent hosts proxy.example.com如果返回 fake-ip,不要把 hosts 写到 FreeBSD 服务端上,而是写到运行 Gost 客户端的机器上。也就是说,写在执行下面这条命令的那台机器上:
gost -L "socks5://127.0.0.1:1080" -F "mwss://GOST_USER:GOST_PASS@proxy.example.com:443?path=/secret-gost-path"固定 hosts:
echo "203.0.113.10 proxy.example.com" >> /etc/hosts如果这台客户端是 PVE 里的 Debian/Ubuntu 云镜像,或者 /etc/hosts 顶部提示由 cloud-init 管理,还要同步写入模板,避免重启后被覆盖:
echo "203.0.113.10 proxy.example.com" >> /etc/cloud/templates/hosts.debian.tmpl然后重启 Gost 客户端并验证:
systemctl restart gost-mwss
getent hosts proxy.example.com
curl --socks5-hostname 127.0.0.1:1080 -m 15 https://www.gstatic.com/generate_204正常时,getent hosts 应该返回你写入的真实服务器 IP,例如:
203.0.113.10 proxy.example.com这就说明系统解析已经绕过 fake-ip,Gost 客户端会连接真实节点地址。如果仍然返回类似下面这种虚拟地址,就说明 hosts 没生效:
28.0.14.153 proxy.example.comcurl 正常返回或没有继续卡到超时,说明 Gost 本地 SOCKS 已经能通过真实节点访问外网。如果继续失败,再看 Gost 客户端日志,确认上游 dst 或 remote 已经是真实服务器 IP,而不是 fake-ip。
最后检查并重启 sing-box:
sing-box check -C /etc/sing-box/conf/
systemctl restart sing-box如果是单文件配置,用同样思路:outbounds 加 SOCKS,route.rules 开头加节点直连。
19. 验收与测速
服务端验收:
nginx -t
service nginx status
service gost_mwss status
sockstat -4 -6 -l | egrep ':(80|443|18080|22)'
certbot renew --dry-run --no-random-sleep-on-renew
curl -I https://proxy.example.com/客户端验收:
systemctl status gost-mwss
curl --socks5-hostname 127.0.0.1:1080 https://ifconfig.me如果客户端接入 sing-box:
sing-box check -C /etc/sing-box/conf/
systemctl is-active sing-box建议再做一次大文件吞吐测试,避免只看延迟:
curl --socks5-hostname 127.0.0.1:1080 -L -o /dev/null \
-w 'code=%{http_code} speed=%{speed_download}Bps total=%{time_total}s size=%{size_download}\n' \
-m 35 http://cachefly.cachefly.net/100mb.test如果延迟能测出来、小网页能打开,但大文件只有几十 KB/s 或几百 KB/s,这就不是配置没通,而是当前 FreeBSD TCP 代理转发链路不适合做高速节点。可以换 VPS 商家、换虚拟化环境继续复现,也可以把 FreeBSD 留给 Hysteria2 UDP,把 TCP 节点放到 Linux 上。
常见问题
curl -I https://proxy.example.com/返回200:正常,说明伪装站点工作。- 访问普通路径看到 Cloud Drive 登录页:正常。
- WebSocket 路径返回
502:通常是 Gost 没启动,或127.0.0.1:18080没监听。 - Gost 进程是 root:检查是否直接用 daemon 启动了 Gost,生产建议使用本文 runner 方式让 Gost 子进程降到
gost用户。 - certbot webroot 签发失败:检查 Nginx 是否真的 include 了
/usr/local/etc/nginx/conf.d/*.conf,以及 HTTP 根目录是否是/usr/local/www/proxy.example.com。 - sing-box 加了节点但没速度:检查
proxy.example.com和203.0.113.10/32是否在路由最前面走direct,并确认/etc/hosts没写错。 - 客户端
curl --socks5-hostname返回的不是服务端 IP:检查 Gost 客户端连接的域名、路径、用户名和密码是否与服务端一致。
到这里,FreeBSD 15 上的 Nginx 前置 Gost MWSS 节点就完成了:公网只暴露标准 HTTPS,伪装站点正常,Gost 后端低权限运行并藏在本机端口,客户端通过本地 SOCKS5 接入,再交给 sing-box 分流。是否达到你的生产标准,最终以你所在 VPS 环境的大文件吞吐测试为准。