在国内访问 Hugging Face Spaces 速度较慢,且免费账户无法绑定自定义域名。本文记录了如何利用 Cloudflare Workers 反向代理,配合 Cloudflare for SaaS 功能,实现自定义域名访问及线路优化(优选 IP)。
前置准备
- Hugging Face 项目:已部署好的 Space(建议 Docker 或静态应用)。
- Cloudflare 账号:拥有一个托管在 CF 的域名(作为回退源,例如
delln.us.ci)。 - 国内 DNS 账号:拥有一个想绑定的对外域名(例如阿里云/腾讯云托管的
zznuo.com)。
第一步:获取 Hugging Face 直链
- 进入你的 HF Space 页面。
- 点击右上角
...-> Embed this space。 - 复制 Direct URL。
- 格式通常为:
https://用户名-项目名.hf.space - 注意:不要直接用浏览器地址栏的地址,那个带导航栏。
- 格式通常为:
第二步:部署 Cloudflare Workers
- 在 Cloudflare 后台 -> Workers & Pages -> Create Application。
- 创建一个新的 Worker,粘贴以下代码:
/**
* 全能通用版 Hugging Face 反代 Worker
* 功能:多域名映射 + WebSocket支持 + 跨域优化
*/
// 1. 配置你的域名映射表
// 左边是你绑定的自定义域名,右边是 HF 的直链
const UPSTREAM_MAP = {
// 示例 1:n8n (需要 WebSocket)
'n8n.zznuo.com': 'https://ganzihai-n8n.hf.space',
'kk.zznuo.com': 'https://v52-kk.hf.space',
};
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const url = new URL(request.url);
const domain = url.hostname;
// 2. 查找当前域名对应的源站地址
const upstream = UPSTREAM_MAP[domain];
// 如果域名不在列表里,返回 404
if (!upstream) {
return new Response(`Error: Domain ${domain} is not configured in Worker mapping.`, {
status: 404,
headers: { 'content-type': 'text/plain;charset=UTF-8' }
});
}
// 3. 构建新的请求 URL
const upstreamUrl = new URL(upstream);
const newUrl = new URL(upstream);
newUrl.pathname = url.pathname;
newUrl.search = url.search;
// 4. 复制原始请求的 Header
const newHeaders = new Headers(request.headers);
// 关键:覆盖 Host 头部,让 HF 知道访问的是哪个 Space
newHeaders.set('Host', upstreamUrl.hostname);
// 补充 Referer 和 Origin (部分应用需要检查)
newHeaders.set('Referer', upstream);
// 检查是否是 WebSocket 连接
const upgradeHeader = request.headers.get('Upgrade');
const isWebsocket = upgradeHeader === 'websocket';
// 5. 构建请求对象
const newRequest = new Request(newUrl.toString(), {
method: request.method,
headers: newHeaders,
body: request.body,
redirect: 'manual'
});
try {
const response = await fetch(newRequest);
// 6. WebSocket 特殊处理
// 如果源站返回 101 Switching Protocols,说明握手成功,直接返回 Response 对象
if (response.status === 101) {
return response;
}
// 7. 普通 HTTP 请求处理
// 重建响应头,解决跨域问题 (CORS)
const responseHeaders = new Headers(response.headers);
responseHeaders.set('Access-Control-Allow-Origin', '*');
responseHeaders.set('Access-Control-Allow-Methods', 'GET, HEAD, POST, PUT, DELETE, OPTIONS');
responseHeaders.set('Access-Control-Allow-Headers', '*');
return new Response(response.body, {
status: response.status,
statusText: response.statusText,
headers: responseHeaders
});
} catch (err) {
return new Response('Proxy Error: ' + err.toString(), { status: 500 });
}
}
- 点击 Deploy 部署。
第三步:配置回退源 (Fallback Origin) —— 关键步骤
为了使用 SaaS 功能(CNAME 接入),我们需要配置一个"回退源域名"。假设使用 pan.delln.us.ci。
1. 添加虚拟 DNS 记录:
- 进入
delln.us.ci的 DNS 设置。 - 添加 A 记录:
- Name:
pan - IPv4:
192.0.2.1(这是个保留 IP,俗称黑洞 IP,用于占位) - Proxy Status: 必须开启 Proxied (橙色云朵)。
- Name:
2. 设置 Worker 路由 (Triggers):
- 进入刚才创建的 Worker -> Settings -> Triggers。
- 点击 Add Route (不要用 Custom Domains)。
- Route1:
pan.delln.us.ci/*(注意末尾的/*)。 - Route2:
*.zznuo.com/*(注意末尾的/*)。 - Zone: 选择
delln.us.ci。
原理:当请求到达 pan 子域名时,Worker 会拦截请求,因此流量不会真的去连
192.0.2.1。
第四步:开启 Cloudflare for SaaS
进入 delln.us.ci 的 SSL/TLS -> Custom Hostnames (自定义主机名)。
1. 设置回退源:
- Fallback Origin: 输入
pan.delln.us.ci。 - 等待状态变为 Effective (有效)。
2. 添加自定义域名:
- 点击 Add Custom Hostname。
- 输入你想对外的域名:
fp.zznuo.com。 - 按照提示,去阿里云/腾讯云添加 TXT 记录完成所有权验证。
第五步:国内 DNS 解析 (完成)
最后,去你的域名注册商(如阿里云 DNS):
- 记录类型:CNAME
- 主机记录:
fp - 记录值:
pan.delln.us.ci
进阶加速 (优选 IP):如果你想优化国内访问速度,可以等将上面的 CNAME 记录改为 Cloudflare 优选域名。
此处注意:必须等自定义主机完成主机验证和证书验证后,才能将 CNAME 记录改为 Cloudflare 优选域名。
第六步:部署 Cloudflare Workers——应对 Cloudflare 不定时主机验证和证书续期
- 在 Cloudflare 后台 -> Workers & Pages -> Create Application。
- 创建一个新的 Worker,粘贴以下代码:
/**
* Cloudflare SaaS 全自动双向切换脚本
* 逻辑:
* 1. 异常救援:当 CF 状态异常或证书异常时 -> 自动切回 [回退源] 救命。
* 2. 自动恢复:当 CF 状态完全恢复(Active) 且 当前 DNS 是回退源时 -> 自动切回 [优选域名]。
*/
// ================= 配置区域 =================
const TARGET_DOMAINS = [
// 格式:{ domain: "完整域名", rr: "主机头", baseDomain: "一级域名" }
{ domain: "n8n.zznuo.com", rr: "n8n", baseDomain: "zznuo.com" },
{ domain: "kk.zznuo.com", rr: "kk", baseDomain: "zznuo.com" },
];
// 1. 官方回退源地址 (救命用,用于通过验证)
const FALLBACK_ORIGIN = "pan.delln.us.ci";
// 2. 你的优选域名/优选IP CNAME (平时用,加速用)
const PREFERRED_ORIGIN = "yg9.ygkkk.dpdns.org";
// ===========================================
export default {
async scheduled(event, env, ctx) {
await processDomains(env);
},
async fetch(request, env, ctx) {
const result = await processDomains(env);
return new Response(JSON.stringify(result, null, 2), {
headers: { "content-type": "application/json;charset=UTF-8" }
});
},
};
async function processDomains(env) {
const results = [];
for (const item of TARGET_DOMAINS) {
const status = await checkAndSwitch(env, item);
results.push(status);
}
return results;
}
async function checkAndSwitch(env, item) {
const { domain, rr, baseDomain } = item;
// 1. 获取 Cloudflare 状态
const cfStatus = await getCloudflareStatus(env, domain);
if (!cfStatus.success) return { domain, error: "CF API Failed" };
// 2. 获取阿里云当前 DNS 记录
const aliRecord = await getAliyunRecord(env, rr, baseDomain);
if (!aliRecord.success) return { domain, error: "Aliyun API Failed: " + aliRecord.msg };
const currentDNS = aliRecord.value; // 当前阿里云上的 CNAME 值
const isHealthy = cfStatus.status === 'active' && cfStatus.ssl === 'active';
// 归一化域名比较 (移除末尾的点,转小写)
const normCurrent = currentDNS.replace(/\.$/, "").toLowerCase();
const normFallback = FALLBACK_ORIGIN.replace(/\.$/, "").toLowerCase();
const normPreferred = PREFERRED_ORIGIN.replace(/\.$/, "").toLowerCase();
let action = "None";
let msg = "状态保持不变";
// ================= 核心判断逻辑 =================
if (!isHealthy) {
// --- 场景 A:状态异常 (需要救援) ---
// 如果当前不是回退源,就必须切回回退源
if (normCurrent !== normFallback) {
const update = await updateAliyunDNS(env, aliRecord.recordId, rr, FALLBACK_ORIGIN);
if (update.success) {
action = "Rescue";
msg = `⚠️ 检测到异常 (${cfStatus.ssl}/${cfStatus.status}),已切回回退源救命。`;
await sendBark(env, "暂停域名加速,待恢复...", `${domain}\n${msg}`);
} else {
msg = "尝试切回回退源失败: " + update.msg;
}
} else {
msg = `状态异常,但当前已在回退源,等待证书恢复中... (${cfStatus.ssl})`;
}
} else {
// --- 场景 B:状态健康 (尝试恢复优选) ---
// 如果当前是回退源,说明之前可能坏过,现在好了,可以切回优选了
// 注意:只有当前指向回退源时才切。如果用户手动指了别的IP,脚本不动,防止冲突。
if (normCurrent === normFallback) {
const update = await updateAliyunDNS(env, aliRecord.recordId, rr, PREFERRED_ORIGIN);
if (update.success) {
action = "Restore";
msg = `✅ 状态已恢复 (Active),自动切回优选域名加速。`;
await sendBark(env, "SaaS 恢复模式启动", `${domain}\n${msg}`);
} else {
msg = "尝试切回优选失败: " + update.msg;
}
} else if (normCurrent === normPreferred) {
msg = "状态健康,当前已是优选域名。";
} else {
msg = "状态健康,当前为第三方值(非回退/非优选),脚本跳过。";
}
}
return { domain, action, msg, current_dns: currentDNS, cf_status: cfStatus };
}
// ================= 辅助函数区域 =================
// 获取 CF 状态
async function getCloudflareStatus(env, domain) {
const url = `https://api.cloudflare.com/client/v4/zones/${env.CF_ZONE_ID}/custom_hostnames?hostname=${domain}`;
try {
const resp = await fetch(url, {
headers: { "Authorization": `Bearer ${env.CF_API_TOKEN}`, "Content-Type": "application/json" }
});
const data = await resp.json();
if (!data.success || data.result.length === 0) return { success: false };
return {
success: true,
status: data.result[0].status,
ssl: data.result[0].ssl?.status
};
} catch (e) { return { success: false }; }
}
// 获取阿里云记录
async function getAliyunRecord(env, rr, baseDomain) {
try {
const res = await aliyunRequest(env, {
Action: "DescribeDomainRecords",
DomainName: baseDomain,
RRKeyWord: rr,
Type: "CNAME"
});
const records = res.DomainRecords?.Record || [];
const target = records.find(r => r.RR === rr);
if (!target) return { success: false, msg: "Record Not Found" };
return { success: true, value: target.Value, recordId: target.RecordId };
} catch (e) { return { success: false, msg: e.message }; }
}
// 更新阿里云记录
async function updateAliyunDNS(env, recordId, rr, value) {
try {
await aliyunRequest(env, {
Action: "UpdateDomainRecord",
RecordId: recordId,
RR: rr,
Type: "CNAME",
Value: value
});
return { success: true };
} catch (e) { return { success: false, msg: e.message }; }
}
// 阿里云通用请求 (含签名算法)
async function aliyunRequest(env, params) {
const publicParams = {
Format: "JSON", Version: "2015-01-09", AccessKeyId: env.ALI_KEY_ID,
SignatureMethod: "HMAC-SHA1", Timestamp: new Date().toISOString(),
SignatureVersion: "1.0", SignatureNonce: Math.random() + Date.now(),
};
const allParams = { ...publicParams, ...params };
const sortedKeys = Object.keys(allParams).sort();
const qs = sortedKeys.map(key => percentEncode(key) + "=" + percentEncode(allParams[key])).join("&");
const stringToSign = "GET&%2F&" + percentEncode(qs);
const signature = await computeSignature(stringToSign, env.ALI_KEY_SECRET + "&");
const url = `https://alidns.aliyuncs.com/?${qs}&Signature=${percentEncode(signature)}`;
const resp = await fetch(url);
const data = await resp.json();
if (resp.status !== 200) throw new Error(data.Message);
return data;
}
function percentEncode(str) {
return encodeURIComponent(str).replace(/[!'()*]/g, c => '%' + c.charCodeAt(0).toString(16).toUpperCase());
}
async function computeSignature(stringToSign, secret) {
const enc = new TextEncoder();
const key = await crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-1" }, false, ["sign"]);
const signature = await crypto.subtle.sign("HMAC", key, enc.encode(stringToSign));
return btoa(String.fromCharCode(...new Uint8Array(signature)));
}
async function sendBark(env, title, body) {
if (!env.BARK_URL) return;
let url = env.BARK_URL.endsWith("/") ? env.BARK_URL : env.BARK_URL + "/";
await fetch(`${url}${encodeURIComponent(title)}/${encodeURIComponent(body)}`);
}
- 点击 Deploy 部署,注意脚本要设置变量:
ALI_KEY_ID、ALI_KEY_SECRET、BARK_URL、CF_API_TOKEN、CF_ZONE_ID。 - 在设置——触发事件中添加定时任务:
0 0-16 * * *
验证
访问 https://fp.zznuo.com。如果看到 Hugging Face 的界面,说明配置成功!
注意:如果 Hugging Face Space 处于休眠状态 (Sleep),初次访问可能需要等待几十秒用于容器冷启动,也可自行配置保活。
第七步:部署 Cloudflare Workers——Hugging Face 保活
- 在 Cloudflare 后台 -> Workers & Pages -> Create Application。
- 创建一个新的 Worker,粘贴以下代码:
export default {
// 1. 浏览器访问触发
async fetch(request, env, ctx) {
const report = await doKeepAlive();
return new Response(report, {
headers: { "content-type": "text/plain; charset=utf-8" },
});
},
// 2. 定时任务触发
async scheduled(event, env, ctx) {
const report = await doKeepAlive();
console.log(report);
},
};
// --- 核心保活逻辑 ---
async function doKeepAlive() {
// ⚠️ 注意:每个网址都要用引号包起来,并且行尾必须有逗号
const urls = [
"https://ganzihai-n8n.hf.space",
"https://v52-kk.hf.space"
];
const results = await Promise.all(
urls.map(async (url) => {
try {
// 设置 5 秒超时,防止 Worker 卡死
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 5000);
const response = await fetch(url, {
signal: controller.signal,
headers: {
'User-Agent': 'Cloudflare-Worker-KeepAlive'
}
});
clearTimeout(timeoutId);
if (response.status === 200) {
return `✅ 成功: ${url} (200 OK)`;
} else {
return `⚠️ 异常: ${url} (Status: ${response.status})`;
}
} catch (error) {
return `❌ 失败: ${url} (Error: ${error.message})`;
}
})
);
const finalReport = `[${new Date().toISOString()}] 保活检查报告:\n` + results.join("\n");
return finalReport;
}
点击 Deploy 部署并设置定时触发。
结束:至此,整个教程结束。