从开局到实战:环境搭建、PM2 进程守护、站外看门狗——一台欧洲免费小主机的完整玩法记录(已脱敏,可公开)
serv00 是波兰的免费主机商,给的不是普通网页空间,而是一个带 SSH、能跑自己程序的 FreeBSD 小环境——本质是台阉割版 VPS。 本手册记录一次完整的实操:开局配置 → PM2 环境 → 踩坑 → 实战项目(站外看门狗)。
| 项目 | 规格 |
|---|---|
| 存储 | 3 GB SSD |
| 月流量 | 不限 |
| 内存 | 512 MB |
| 系统进程 | 15 个 |
| TCP/UDP 端口 | 3 个(需预留) |
| 网站数 | 100 个 |
| 数据库 | MySQL 10 个、PostgreSQL 3 个、MongoDB 3 个 |
| 系统 | FreeBSD 14.3(注意:不是 Linux) |
| 备份 | 每日备份,保留 7 天 |
| 有效期 | 10 年 |
最值钱的三样:SSH 登录、cron 定时任务、允许跑自己的程序。绝大多数免费主机只给网页上传,这三样是它能当小服务器用的根本。
SSH 登录(login 换成自己的用户名,sX 是你所在的服务器编号):
ssh login@sX.serv00.com
查账号全貌:
devil info account
一份真实输出(已脱敏)逐项解读:
Login: yourlogin ← 你的登录名,SSH 和面板都用它
Plan: FREE ← 免费计划
Expiration date: 2036-11-02 ← 账号到期日(10 年期)
Mail servers ← 邮件服务器都是 mailX.serv00.com
Database servers ← mysqlX / pgsqlX / mongoX.serv00.com,远程连库用
DNS servers ← dns1/dns2.serv00.com,绑自己域名时把 NS 指过来
DevilWEB (panel): https://panelX.serv00.com/ ← 控制面板地址
Roundcube (webmail) / PhpMyAdmin / PhpPgAdmin ← 网页版管理工具
Binexec: Enabled ← 跑自有程序权限,已开(见第三章)
2FA: Disabled ← 两步验证没开,建议去面板打开
注意:df -h ~ 看到的是整台宿主机的磁盘池(好几 TB),不是你的配额。你的真实上限就是规格表里的 3 GB,别被 df 骗了。
默认关闭。不开这个,你只能放网页文件,任何守护进程都跑不起来。
面板路径:Additional services → Run your own applications → Enable。
验证:devil info account 里 Binexec: Enabled。
自己跑的程序只能监听预留过的端口。面板 Additional services → Port reservation,或命令行:
devil port add tcp 0 # 端口填 0 = 系统随机分配一个空闲端口
devil port list # 查看已预留的端口
建议:先占 1~2 个,想清楚再用最后一个名额。TCP 给 Web 类服务,UDP 给 QUIC 类协议。
uname -a # 确认 FreeBSD 版本
node -v; python3 --version; git --version
serv00 预装 Node.js、Python、Ruby、Java、Go 等一堆语言,一般不用自己装运行时。
Node 程序的守护进程管家用 PM2:进程挂了自动拉起,配合 cron 还能做到宿主机重启后自恢复。
直接 npm install -g 会因权限失败,先把全局前缀指回家目录:
mkdir -p ~/.npm-global
npm config set prefix '~/.npm-global'
echo 'export PATH=$HOME/.npm-global/bin:$PATH' >> ~/.bashrc
export PATH=$HOME/.npm-global/bin:$PATH
npm install -g pm2
pm2 -v
用一个预留的 TCP 端口(示例 30001)跑最小 HTTP 服务,验证"端口 + 自有程序权限 + PM2"都通:
mkdir -p ~/hello && cd ~/hello
cat > app.js <<'EOF'
require('http').createServer((req, res) => {
res.end('hello from serv00\n');
}).listen(30001, '0.0.0.0');
EOF
pm2 start app.js --name hello
sleep 2
curl http://localhost:30001
curl http://sX.serv00.com:30001
两条 curl 都返回 hello from serv00,链就通了:本机回环、对外访问各验一次。
现象:pm2 start 刚打印 Done,马上 curl 报 Could not connect,但进程明明 online、没崩。
真相:PM2 报 Done 只代表进程已拉起,程序 bind 端口还要一点时间,共享主机负载高时这个间隙能超过 1 秒。粘贴整段命令时 curl 几乎同时执行,就撞上了。
结论:start/restart 后等 1~2 秒再测(sleep 2 写进习惯)。判断监听状态看 sockstat -l | grep 端口。
FreeBSD 的 sockstat 会显示 tcp46,表示这个监听是 IPv4/IPv6 双栈,不是出错。不指定地址时 Node 默认绑双栈;显式写 '0.0.0.0' 后变纯 tcp4。两种都能用,写死 0.0.0.0 最省心。
用 (crontab -l; echo '新行') | crontab - 追加前,先确认旧行不在里面,否则同一条 @reboot 会攒好几份,触发时可能把服务拉起多份。去重一条命令:
crontab -l | sort -u | crontab -
实测:什么都不干的 Node hello 服务占约 99 MB,同机的 Python 小程序只占约 25 MB。总共只有 512 MB,选型时优先 Python/Go 这类轻的,Node 服务别超过 3 个。
系统是 FreeBSD 不是 Linux,Linux 二进制直接跑不了。装任何现成程序前先确认有 FreeBSD 构建,没有就找源码编译,或者换实现语言自己写小脚本——很多场景一个 Python 标准库脚本就够了。
serv00 偶尔维护重启宿主机。没有自恢复的话,你的服务重启后就躺平了。两步配齐:
pm2 save
(crontab -l 2>/dev/null; echo '@reboot export PATH=$HOME/.npm-global/bin:$PATH && pm2 resurrect') | crontab -
crontab -l # 确认只有一条 @reboot(重复了用第五章的去重命令)
原理:pm2 save 把当前进程清单存档,@reboot 任务在开机时用 pm2 resurrect 把清单里的进程全部复活。以后新增/删除服务,记得再跑一次 pm2 save 更新存档。
网上流传"3 个月不登录就回收账号"的说法,但在官方文档里找不到出处。
实测:一个账号放了半年没管,再登录时一切完好——端口预留、Binexec 权限都在,有效期仍显示 10 年期。结论:不需要专门保活,不用挂自动 SSH 登录脚本。偶尔想起来登一次看一眼即可;今天的任何一次 SSH 登录本身也会刷新活跃记录。
serv00 最合适的活:常开、免费、坏了不心疼的站外探针。从欧洲方向定时探测你散在各处的 VPS 还活着没,异常了用 Telegram 报警,你还能在 Telegram 里手动发命令随时查状态。
边界说清:探的是"机器和网络活着"(ping + 一个代表性 TCP 端口)。像 Hysteria2 这类 UDP 协议,外人没凭据没法真握手验证——但机器被停、宕机、断网占了故障的绝大多数,这个粒度够用。对被探的机器零侵入:不装任何东西、不改任何配置。
~/watchdog/
├── config # bot token + chat id(权限 600,只存这一份)
├── targets.txt # 目标清单:名字|IP|端口,每行一个
├── check.sh # cron 每 5 分钟跑:探测 + 状态机 + 报警
└── tg_bot.py # PM2 常驻:长轮询收命令,回 /status 查询
设计要点:
watchdog.log 记一行 HTTP 状态码——200 正常、401 是 token 错、400 是 chat id 错。报警失败默认是静默的(curl 输出被吞),没这行日志根本无从排查;怀疑没收到报警时 tail -5 ~/watchdog/watchdog.log 一眼定性0 = 只 ping(目标没开 TCP 服务时用);填具体端口 = ping + TCP 握手双信号/newbot,拿到 tokenhttps://api.telegram.org/bot<token>/getUpdates(<token> 连尖括号换成你的 token),在返回 JSON 里找 "chat":{"id":数字},那就是你的 chat idcat > ~/watchdog/config <<'EOF'
BOT_TOKEN=填bot token
CHAT_ID=填你的chat id
EOF
chmod 600 ~/watchdog/config
cat > ~/watchdog/targets.txt <<'EOF'
我的VPS|1.2.3.4|22
EOF
#!/bin/sh
DIR="$HOME/watchdog"
. "$DIR/config"
send() {
code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
-d "chat_id=${CHAT_ID}" -d "text=$1" 2>/dev/null)
echo "$(date '+%Y-%m-%d %H:%M:%S') telegram -> HTTP ${code:-000} : $1" >> "$DIR/watchdog.log"
}
while IFS='|' read -r name ip port; do
case "$name" in ''|'#'*) continue ;; esac
STATE="$DIR/state.$name"
FAILS="$DIR/fails.$name"
ok=1
ping -c 2 -W 2000 -t 5 "$ip" >/dev/null 2>&1 || ok=0
if [ "$ok" = 1 ] && [ "$port" != "0" ]; then
nc -z -w 5 "$ip" "$port" >/dev/null 2>&1 || ok=0
fi
prev=$(cat "$STATE" 2>/dev/null || echo up)
fails=$(cat "$FAILS" 2>/dev/null || echo 0)
if [ "$ok" = 0 ]; then
fails=$((fails+1)); echo "$fails" > "$FAILS"
if [ "$fails" -ge 2 ] && [ "$prev" = up ]; then
send "🔴 [$name] 疑似下线:探测连续失败(serv00 站外探针)"
echo down > "$STATE"
fi
else
echo 0 > "$FAILS"
if [ "$prev" = down ]; then
send "🟢 [$name] 已恢复:探测重新通过"
fi
echo up > "$STATE"
fi
done < "$DIR/targets.txt"
挂 cron,每 5 分钟一轮:
chmod +x ~/watchdog/check.sh
(crontab -l 2>/dev/null; echo '*/5 * * * * $HOME/watchdog/check.sh') | crontab -
Python 标准库实现,不装任何第三方包,常驻内存约 25 MB。长轮询 Telegram 收消息,只响应 config 里那个 chat id 的命令;/status 会现场把全部目标探一遍再回复。
#!/usr/bin/env python3
import json, os, subprocess, time
import urllib.parse, urllib.request
DIR = os.path.expanduser("~/watchdog")
cfg = {}
for line in open(os.path.join(DIR, "config")):
line = line.strip()
if "=" in line and not line.startswith("#"):
k, v = line.split("=", 1)
cfg[k.strip()] = v.strip()
TOKEN, MY_CHAT = cfg["BOT_TOKEN"], cfg["CHAT_ID"]
API = "https://api.telegram.org/bot" + TOKEN
def api(method, **params):
data = urllib.parse.urlencode(params).encode()
req = urllib.request.Request(API + "/" + method, data=data)
with urllib.request.urlopen(req, timeout=40) as r:
return json.load(r)
def send(chat_id, text):
try:
api("sendMessage", chat_id=chat_id, text=text)
except Exception:
pass
def probe(ip, port):
if subprocess.run(["ping", "-c", "2", "-W", "2000", "-t", "5", ip],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode != 0:
return False
if port != "0":
return subprocess.run(["nc", "-z", "-w", "5", ip, port],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL).returncode == 0
return True
def status_text():
lines = []
for line in open(os.path.join(DIR, "targets.txt")):
line = line.strip()
if not line or line.startswith("#"):
continue
name, ip, port = line.split("|")
ok = probe(ip, port)
sf = os.path.join(DIR, "state." + name)
saved = open(sf).read().strip() if os.path.exists(sf) else "up"
lines.append("%s %s (%s) 实时:%s / 记录:%s" %
("🟢" if ok else "🔴", name, ip, "通" if ok else "不通", saved))
return "\n".join(lines) if lines else "targets.txt 是空的"
def main():
offset = 0
try:
r = api("getUpdates", timeout=0)
if r.get("result"):
offset = r["result"][-1]["update_id"] + 1
except Exception:
pass
while True:
try:
r = api("getUpdates", offset=offset, timeout=30)
for u in r.get("result", []):
offset = u["update_id"] + 1
msg = u.get("message") or {}
chat_id = str((msg.get("chat") or {}).get("id", ""))
text = (msg.get("text") or "").strip()
if chat_id != MY_CHAT:
continue
if text.startswith("/status") or text.startswith("/check"):
send(chat_id, status_text())
elif text.startswith("/start") or text.startswith("/help"):
send(chat_id, "发 /status 立即探测全部目标")
except Exception:
time.sleep(5)
main()
托管并验收:
pm2 start ~/watchdog/tg_bot.py --name tg-bot --interpreter python3
pm2 save
然后在 Telegram 给 bot 发 /status,收到带 🟢/🔴 的清单就是全线通了。"实时"是现场探测结果,"记录"是探针脚本的状态机视角——刚出故障还没满 2 次失败时,两者会短暂不一致,正常。
小现象:bot 跑起来后,浏览器再打开 getUpdates 会返回空——消息被 bot 实时收走了,chat id 已经存好,不用再查。
往 targets.txt 加一行必死的目标,手动跑两次脚本(中间隔一分钟),收到 🔴 后删掉这行和它的状态文件:
测试|192.0.2.1|0
~/watchdog/check.sh; sleep 65; ~/watchdog/check.sh
rm -f ~/watchdog/state.测试 ~/watchdog/fails.测试
| 命令 | 作用 |
|---|---|
devil info account |
账号全貌:计划、到期、服务器一览、Binexec 状态 |
devil port list |
已预留端口 |
devil port add tcp 0 |
预留一个随机 TCP 端口(udp 同理) |
devil port del tcp 端口 |
释放端口 |
devil www list |
已建网站列表 |
devil mysql list / devil pgsql list / devil mongo list |
数据库列表 |
| 命令 | 作用 |
|---|---|
pm2 start 程序 --name 名字 |
托管一个程序 |
pm2 list |
进程清单(状态、内存、重启数) |
pm2 logs 名字 --lines 20 |
看最近日志(排障第一步) |
pm2 restart 名字 |
重启(重启后等 1~2 秒再测端口) |
pm2 delete 名字 |
摘掉一个进程 |
pm2 save |
把当前清单存档(配合 @reboot resurrect 自恢复) |
| 命令 | 作用 |
|---|---|
sockstat -l \| grep 端口 |
看某端口到底在监听什么地址/协议 |
crontab -l |
当前定时任务 |
crontab -l \| sort -u \| crontab - |
cron 去重 |
quota / 面板 |
看自己的 3 GB 配额用量(别看 df) |
实操环境:serv00 免费档(FreeBSD 14.3),2026 年 11 月。文中账号、端口、IP 均为示例,部署时换成你自己的。