用 Cloudflare Worker 做多域名反代 + Error 1003 的解法

一台 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);
  },
};

部署路径

  1. Cloudflare Dashboard → Workers & Pages → Create Worker
  2. 粘代码,Deploy
  3. Worker 详情 → Settings → Triggers → Routes
  4. 添加路由:app.example.com/*api.example.com/*
  5. 对应的 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),一定要开小黄云:

类型名称内容代理
Aapp192.0.2.1Proxied
Aapi192.0.2.1Proxied

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。