一台 VPS 上跑了七八个服务,每个都占一个端口。想用一个域名的多个子域直接映射过去,还不想暴露 IP。上 Nginx 有点重——一个 Cloudflare Worker 就够了。
Worker 代码
思路:读请求的 Host,从 map 里查目标 IP:端口,重新 fetch 转发过去。
export default {
async fetch(request) {
const host = request.headers.get("host");
const map = {
"app.example.com": "http://origin.example.com:51118",
"api.example.com": "http://origin.example.com:51117",
"admin.example.com":"http://origin.example.com:51116",
"shop.example.com": "http://origin.example.com:51115",
};
const target = map[host];
if (!target) return new Response("Host not found", { status: 404 });
const url = new URL(request.url);
const proxyUrl = target + url.pathname + url.search;
const proxied = new Request(proxyUrl, {
method: request.method,
headers: request.headers,
body: (request.method === "GET" || request.method === "HEAD")
? undefined : request.body,
redirect: "manual",
});
// 把 Host 改成目标域,避免源站按 Host 路由时错乱
proxied.headers.set("host", new URL(target).host);
return fetch(proxied);
},
};
部署路径
- Cloudflare Dashboard → Workers & Pages → Create Worker
- 粘代码,Deploy
- Worker 详情 → Settings → Triggers → Routes
- 添加路由:
app.example.com/*、api.example.com/*等 - 对应的 DNS 记录必须 开小黄云(Proxied),否则 Worker 拦不到
动态版本(不用维护 map)
如果子域名和端口有规律(比如 app → 51118、api → 51117),可以从子域直接算端口:
const portMap = { app: 51118, api: 51117, admin: 51116, shop: 51115 };
const sub = host.split(".")[0];
const port = portMap[sub];
if (!port) return new Response("Not found", { status: 404 });
const target = `http://origin.example.com:${port}`;
以后加子域只改 map 一处。
Error 1003:direct IP access not allowed
第一次绑域名很容易撞到:
error code: 1003
含义是”直接用 IP 访问 Cloudflare 不允许”。多半是因为你把域名 CNAME 到了 xxx.workers.dev,Cloudflare 就把这当成”客户端在裸 IP 访问”。
正解:不要 CNAME 到 workers.dev,走 Worker 路由。
DNS 那边:加一条 A 记录,IP 随便填(比如 192.0.2.1),一定要开小黄云:
| 类型 | 名称 | 内容 | 代理 |
|---|---|---|---|
| A | app | 192.0.2.1 | Proxied |
| A | api | 192.0.2.1 | Proxied |
Worker 那边:Settings → Triggers → Routes,加上 app.example.com/* 等。
之后请求走的是 Worker,A 记录里那个 IP 只是 Cloudflare 需要一条 DNS 记录而已,不会真的连过去。
需要 WebSocket / SSE
Workers 支持 WebSocket 升级,fetch 里带上 Upgrade 头就能透传:
if (request.headers.get("Upgrade") === "websocket") {
return fetch(target, request);
}
SSE (text/event-stream) 靠 Response.body 流式返回就行,Worker 会自动保持长连接。
顺带的好处
- 源站真实 IP 不会暴露给客户端
- 免费额度撑得住中小站(10 万请求/天)
- 加 WAF、限流、缓存全在 Cloudflare 控制台一键切换
一句话总结
一个 Worker + 一张 host→port 表 = 多域名反代。踩到 Error 1003 别慌,DNS 走 A 记录 + 小黄云 + Worker Route,不要 CNAME 到 workers.dev。
