Instabox的免费服务器,拿来跑脚本、折腾Docker、部署轻量服务都挺合适。这篇先用它搭个代理节点,从注册领取到客户端连接,把完整过程分享给大家。
这篇是上篇,先完成服务器领取,再用Cloudflare Worker设置定时续租,尽量避免机器因空闲而暂停。节点搭建和客户端连接,放到下篇继续。
先领取:账号注册 → 邀请码激活 → 创建服务器
官网:
- 选择注册方式。 点击 Continue with GitHub 或 Continue with Google,任选一种完成账号授权。
- 邀请码激活。 注册邮箱收到邀请码后,回到激活页填写,激活成功再创建机器。
- 创建服务器。 进入 Machines → Create machine。
名称填 instabox-vps,模板选 standard,确认免费规格后点击 Create。
列表显示 Running(运行中) 后,打开机器详情。
本次创建规格为 4 vCPU / 8 GiB内存 / 20 GiB磁盘。新账号免费1台;2026年10月20日前创建的老账号,5台免费额度保留至2026年12月1日,之后恢复1台。
接下来的搭建还需要:一个 Cloudflare账号,以及支持 VLESS + WebSocket + TLS 的代理客户端。
第一步:防休眠,避免空闲30分钟后暂停
免费机器连续空闲30分钟后会自动暂停,节点也会暂时不可用。 暂停会保留机器状态,访问公开端口网址可以触发恢复。
这里的防休眠,也就是常说的“保活”:把脚本放到Cloudflare Worker上运行,每10分钟给Instabox机器续一次活动租约,每次有效15分钟,尽量避免闲置暂停。 电脑不用一直开着;续租只处理空闲暂停,不改变免费额度和活动期限。
取出机器ID,再创建API Key
打开目标机器的 Details,机器ID就在浏览器地址栏的 open= 后面:

复制 open= 后、下一个 & 前的完整内容,保留 m_ 前缀。上例取出的是 m_YOUR_MACHINE_ID,实际用自己的值。
先把这段机器ID存到备忘录,标注“Instabox机器ID”。 稍后填入Worker代码,用来指定要续租的机器。
再去 API Keys → Create API Key,命名 instabox-keepalive。选择Restricted,确认Machines的Read已开启,打开Write,关闭Delete;其他写入和删除权限不启用。Read若显示为灰色锁定项,保持即可。

把API Key也存到备忘录,标注“Instabox API Key”。稍后把它填入Cloudflare的Secret(加密变量),供Worker续租时使用。
创建Worker,补上Secret和定时规则
登录
,依次进入:
Workers & Pages → Create application。
创建方式选择 Start with Hello World!。
名称填 instabox-vps-keepalive,点击 Deploy。

进入刚创建的Worker,点击 Edit code。
删掉编辑器里的默认代码,再粘贴提供的防休眠Worker完整代码。
找到这一行,从备忘录取出刚才保存的完整机器ID,替换引号里的示例值,其余代码保持原样:
// Cloudflare Cron Trigger: */10 * * * *
// Secret binding: INSTABOX_API_KEY. No public control endpoints.
const API = "https://api.instabox.sh";
const MACHINE = "m_REPLACE_WITH_YOUR_MACHINE_ID";
const LEASE = "codex-keepalive";
const TTL_SECONDS = 900;
const HTTP_TIMEOUT_MS = 20_000;
const STATES = new Set([
"creating", "starting", "running", "stopping", "stopped",
"suspending", "suspended", "resuming", "deleting", "deleted", "error",
]);
function failure(code, status) {
/** @type {Error & { code?: string, httpStatus?: number }} */
const error = new Error(`keepalive_failed:${code}`);
error.code = code;
if (Number.isInteger(status)) error.httpStatus = status;
return error;
}
async function requestJSON(path, key, method = "GET", body) {
const controller = new AbortController();
let timer;
const timeout = new Promise((_, reject) => {
timer = setTimeout(() => {
reject(failure("http_timeout"));
controller.abort();
}, HTTP_TIMEOUT_MS);
});
const request = (async () => {
let response;
try {
response = await fetch(API + path, {
method,
// workerd supports manual/follow; reject redirects below without following.
redirect: "manual",
signal: controller.signal,
headers: {
"X-API-Key": key,
"Accept": "application/json",
...(body === undefined ? {} : { "Content-Type": "application/json" }),
},
...(body === undefined ? {} : { body: JSON.stringify(body) }),
});
} catch {
throw failure("http_transport");
}
if (response.status !== 200) {
throw failure("http_status", response.status);
}
try {
return await response.json();
} catch {
throw failure("invalid_json");
}
})();
try {
return await Promise.race([request, timeout]);
} finally {
clearTimeout(timer);
}
}
export async function renewLease(env) {
const record = {
timestamp: new Date().toISOString(),
machine: MACHINE,
lease: LEASE,
state: "unknown",
autoSuspendedAt: null,
phase: "configuration",
ok: false,
};
try {
const key = env?.INSTABOX_API_KEY;
if (typeof key !== "string" || !key.trim()) {
throw failure("missing_secret");
}
record.phase = "check_machine";
const machine = await requestJSON(`/v1/machines/${MACHINE}`, key);
record.state = STATES.has(machine?.state) ? machine.state : "unknown";
const suspendedAt = machine?.autoSuspendedAt;
if (suspendedAt != null) {
const time = typeof suspendedAt === "string" ? Date.parse(suspendedAt) : NaN;
record.autoSuspendedAt = Number.isFinite(time)
? new Date(time).toISOString()
: "invalid";
}
if (
machine?.id !== MACHINE ||
record.state !== "running" ||
machine.desiredState !== "running" ||
machine.operationId !== null ||
suspendedAt != null
) {
throw failure("machine_not_safely_running");
}
// Best-effort guard only: GET and PUT are not atomic. The server may
// change state between them; its lease endpoint can wake an idle VM.
record.phase = "renew_lease";
const lease = await requestJSON(
`/v1/machines/${MACHINE}/activity-leases/${LEASE}`,
key,
"PUT",
{ ttlSeconds: TTL_SECONDS },
);
const expires = typeof lease?.expiresAt === "string"
? Date.parse(lease.expiresAt)
: NaN;
if (
lease?.machineId !== MACHINE ||
lease?.id !== LEASE ||
!Number.isFinite(expires) ||
expires <= Date.now()
) {
throw failure("invalid_lease_response");
}
record.expiresAt = new Date(expires).toISOString();
record.ok = true;
console.log(JSON.stringify(record));
} catch (error) {
// Never log raw exceptions, request headers, secrets or response bodies.
record.error = error?.code || "unexpected_failure";
if (Number.isInteger(error?.httpStatus)) record.httpStatus = error.httpStatus;
console.error(JSON.stringify(record));
throw failure(record.error, record.httpStatus);
}
}
export default {
fetch() {
return new Response("Not found", { status: 404 });
},
scheduled(_event, env, ctx) {
ctx.waitUntil(renewLease(env));
},
};
核对机器ID和代码是否完整,然后点击 Deploy。执行间隔由后面的Cron规则决定。
接着进入 Settings → Runtime variables and secrets → Add variable。
选择Production,名称填 INSTABOX_API_KEY,类型选 Secret,值填刚才保存的Instabox API Key,点击 Add 1 variable and deploy。

保存后确认变量类型为 Secret、值显示 Value encrypted,并开启下方的Workers Logs。

再进入 Trigger events → Cron triggers → Add。
Cron表达式填 */10 * * * *,也就是每10分钟执行一次,点击弹窗里的 Add。
Cron弹窗点Add后,别漏掉页面底部的Save。
保存后,确认 Every 10 minutes 仍在,页面底部的未保存提示已消失。
定时规则保存后,回到Instabox的 Machines 列表检查机器状态。如果显示 Paused(已暂停),点击这一行右侧的播放三角。
恢复成功后,机器会显示 Running(运行中)。脚本只给运行中的机器续租,不会主动恢复已暂停的机器。
打开 Observability,等到定时调用出现。

展开应用日志,应看到 ok: true 和 phase: renew_lease。expiresAt是租约到期时间,应晚于同一条日志里的 timestamp(执行时间)。
Worker没有控制界面,直接打开它的网址返回404属于正常现象。
扩展说明: 也可以把脚本改成通过API自动获取API Key可见的全部机器,再给运行中的机器续租,不用手填机器ID。
最后再确认一下:机器显示Running,定时日志中出现 ok: true 和 phase: renew_lease,并且 expiresAt 晚于同一条日志里的执行时间,就说明这次续租成功了。