0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloudflare Tunnel + Access + Workers で自宅LM StudioをOpenAI互換APIとして公開する

0
Last updated at Posted at 2026-06-02

ことのはじめ

普段ローカルLLMを動かすのに使っている自宅の RTX5090。一応既に LMStudio の LM Link で外出先でもラップトップからは使えるけど、OpenAI 互換 API として叩けたらもっと色々使えるのになと思っていた。

あと、外部に立てた自作サービスからヘビーユーズしたい(RAG の ingest とか)要求に対して、

  • オンラインモデル:高い… ペイするかを考えずにだらだら使いたい
  • 無料枠(OpenRouter とか):無限には使えないし、渡せる情報か注意しないといけない
  • サブスク枠(codex app server とか):人に使わせるのはどうなんだろう、適正用途でも何かの拍子に BAN されるのは御免

ということで、LMStudio をホストして OpenAI 互換 API として公開してみるか、と。

ゴール

自宅PC上の LM Studio を、

  • OpenAI SDK
  • OpenCode
  • Cline
  • その他 OpenAI互換API 対応フレームワーク

から利用可能なエンドポイントとして WWW に公開する。

こう。オレオレ SaaS 状態。

curl https://api.独自ドメイン/v1/chat/completions \
  -H "Authorization: Bearer sk-オレオレAPIキー" \
  -H "Content-Type: application/json" \
  -d '{
    "model":"qwen3.6-35b-a3b",
    "messages":[
      {
        "role":"user",
        "content":"hello"
      }
    ]
  }'
構成
Client
  ↓ Authorization: Bearer sk-xxxx
https://api.独自ドメイン/v1/
  ↓ Cloudflare Worker
  ↓ CF-Access-Client-Id
  ↓ CF-Access-Client-Secret
https://llm.独自ドメイン
  ↓ Cloudflare Access
  ↓ Cloudflare Tunnel
LM Studio

※サブドメインの api., llm. は適当に名付けただけです。よしなに。

※Workers は無料枠を使おうとしているので一応制限はあるけど、この記事作成時点では 10万リクエスト/日なので、実質無制限かなと思い採用。例えば Cline の場合はカスタムヘッダーが付けられるので Workers なしの最小構成でも行けた。


LM StudioをCloudflare Tunnelで公開

LM Studioを http://localhost:1234 で起動。

image.png

Cloudflare Tunnelで llm.独自ドメインlocalhost:1234 へルーティング。

Cloudflare Tunnel に Public hostname を追加した直後からバックエンドは公開状態になる。Access 未設定の場合、Public hostname追加後すぐにAccessで保護するか、次章の手順により先にAccess Applicationを作成しておく。

↑ 私は既にドメインに対して Access 保護を張っていて、今回は公開アプリケーションを追加で生やすだけだった。Access 自体未設定の場合は注意。

image.png

image.png

image.png

これで WWW 側からローカルホストの LMStudio に到達可能となっているはず。

Cloudflare Accessで保護

自作 API キーを Worker で照合するだけではちょっと怖いので、llm.独自ドメイン のAccess Application を作成して二段構えにする。

image.png

image.png

image.png

  • ALLOWポリシー(メールアドレス)
    ドメイン配下に API エンドポイント以外の通常の Web ページも置く場合など。今回の構成では必須ではない

  • SERVICE AUTHポリシー(Service Token)
    こっちが大事

Service Token を発行。

image.png

トークンの期限は自己責任でよしなに。

image.png

取得できる値は以下の2つ。いつでもローテートできる。

CF_ACCESS_CLIENT_ID
CF_ACCESS_CLIENT_SECRET

SERVICE AUTHポリシー設定時に今取得したトークンを指定。

image.png

確認:

curl https://llm.独自ドメイン/v1/models \
  -H "CF-Access-Client-Id: xxx" \
  -H "CF-Access-Client-Secret: yyy"

モデル一覧が返れば成功。


Worker 作成、 api サブドメイン設定

Cline 等、カスタムヘッダーが付与できるツールと併用する場合は独自 Bearer キーと api. サブドメインが不要になるのでこの章の設定は省略可能。

ここで、Cloudflare Accessは

CF-Access-Client-Id
CF-Access-Client-Secret

を要求する。しかし一般的なクライアントは

Authorization: Bearer xxx

しか送れない。そこで両者のマッピング用に Cloudflare Worker を作成する。

Workers & Pages からアプリケーションを作成。

image.png

Hello World でOK。

image.png

適当に命名。

image.png

今作った Worker が開くので設定をクリック。

image.png

先程取得した ID と SECRET を登録。あと、ここで API 利用者に渡す(bearer 認証用の)API_KEY も適当に命名して登録。

image.png

API_KEY はキー名も値も何でも良い。OpenAI のキーを真似つつ、推測されにくいように、sk-(UUID 的な文字列)にしておいた。

Worker 実装例:

export default {
  async fetch(request, env) {
    const auth = request.headers.get("Authorization");

    if (auth !== `Bearer ${env.MY_API_KEY}`) {
      return new Response("Unauthorized", {
        status: 401
      });
    }

    const url = new URL(request.url);

    const upstream = new URL(
      url.pathname + url.search,
      "https://llm.独自ドメイン"
    );

    const headers = new Headers(request.headers);

    headers.set(
      "CF-Access-Client-Id",
      env.CF_ACCESS_CLIENT_ID
    );

    headers.set(
      "CF-Access-Client-Secret",
      env.CF_ACCESS_CLIENT_SECRET
    );

    return fetch(upstream, {
      method: request.method,
      headers,
      body: request.body
    });
  }
}

先程シークレット定義を行った設定画面の右上からコードの編集画面を開いて上記の実装をコピペ。キー名とドメインは書き換えて。

image.png

image.png

次に、Workerに api.独自ドメイン を割り当てる。ユーザーが意識するエンドポイントになる。

image.png

image.png

image.png


動作確認

モデル一覧:

curl https://api.独自ドメイン/v1/models \
  -H "Authorization: Bearer sk-xxxx"

チャット:

curl https://api.独自ドメイン/v1/chat/completions \
  -H "Authorization: Bearer sk-xxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "model":"qwen3.6-35b-a3b",
    "messages":[
      {
        "role":"user",
        "content":"hello"
      }
    ]
  }'

ps1 用:

test.ps1

$ApiKey = "sk-xxxx"
$BaseUrl = "https://api.独自ドメイン"

$body = @{
  model = "qwen3.6-35b-a3b"
  messages = @(
    @{
      role = "user"
      content = "こんにちは。自己紹介してください。"
    }
  )
  temperature = 0.7
  stream = $false
} | ConvertTo-Json -Depth 10

$res = Invoke-RestMethod `
  -Uri "$BaseUrl/v1/chat/completions" `
  -Method Post `
  -Headers @{
    "Authorization" = "Bearer $ApiKey"
    "Content-Type" = "application/json"
  } `
  -Body $body

$res.choices[0].message.content

Cline の場合、カスタムヘッダーが追加できるので Bearer キーに変換する必要がなく、Workers なし(Service Token 直通)の最小構成でも行けた。その場合、今回作ったサブドメインで言うと llm. を使用。キーはダミーでOK。

image.png

もちろん api. + Bearer キーでも良い。


補足:ハマったポイント

既存 ALLOW ポリシーと競合した話。今回新規に SERVICE AUTH ポリシーだけ追加するケースでは無関係。

先程必須ではないと言ったALLOWポリシーに邪魔された。

私は既にサブドメインをワイルドカードにして *.独自ドメイン を Access で保護していた。そのため api.独自ドメイン も巻き込まれて 302 → cloudflareaccess.com へリダイレクトされた。

蓋を開けてみればそれだけなんだけど。後述のバイパス設定も地味にミスってて結構時間溶けた。

対処として、Access Application を追加。対象は *.独自ドメイン。ここで、ポリシー無し ≠ Bypassだった。

(必要)って書いてあるのに最初空欄にしていて地味にハマった。空のままでも保存はできてしまうという。ちゃんとバイパスを明示選択しよう。

image.png

また、ポリシールールを空のままにすると保存しても反映されなかったため、とりあえず国でフィルターしておいた。

image.png

Access コントロール → アプリケーションに戻り、api.独自ドメイン 用アプリケーションを追加。

image.png

image.png

(元々あった)ALLOWポリシー用のアプリケーションと、今足した BYPASSポリシー用のアプリケーションが並んだ。

image.png


Codex に使わせようとして失敗

OpenAI互換APIとしては完成。

ところでここ見ていて、独自ドメイン+ヘッダーを Codex に渡せたら使えるかなと思ったんだけど、

config.toml
[model_providers.hoge]
name = "LM Studio"
base_url = "https://llm.独自ドメイン/v1"
wire_api = "responses"

http_headers = {
  "CF-Access-Client-Id" = "xxxx.access",
  "CF-Access-Client-Secret" = "xxxx"
}

手元の Codex CLI 0.128.0 は /v1/responses を利用するため、tools.N.type 周辺の互換性で LM Studio 側と衝突するっぽい。

エラー吐いた。

LMStudio
[Server Error]  {
  "error": {
    "message": "Invalid",
    "type": "invalid_request_error",
    "param": "tools.14.type",
    "code": "invalid_string"
  }
}

色々やってみたけど結局動かず。前に LMStudio 使えたはずだけどなーと思ったら

config.toml
model = "qwen3.6-35b-a3b"
oss_provider = "lmstudio"
codex --oss

という、正規ルートっぽい構成で成功しただけだった。でもこれだと独自エンドポイント+独自ヘッダー CF-Access-Client-Id, CF-Access-Client-Secret を指定することはできない。

諦め。誰か動いたら教えてください…


まとめ

最終構成:

Client
  ↓ Bearer API Key
Cloudflare Worker
  ↓ Service Token
Cloudflare Access
  ↓ Tunnel
LM Studio

Cloudflare Access の Service Token をそのまま配るのではなく、Workerで Bearer API Key に変換することで、一般的な OpenAI互換クライアントから利用可能になった。

また、ドメインを Access のワイルドカード保護下に置いている場合、api.独自ドメイン 専用の Access Application を作成し、BYPASS ポリシーを明示指定して除外する必要があった。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?