Windows11ドメイン環境でOllama社内チャット:IIS ARRの502と“Windowsサービス化”失敗からの現実解

こんにちは、yukiです。
今回は「Ollama を LAN 内共有チャットにする」ために、Windows 11(ドメイン参加)+ IIS 逆プロキシ + Node/Express + Ollama の構成を組んだときの記録を書きます。
狙いは成功談というより、失敗と切り分けのログを残すことです。特に、IIS 絡みで出がちな 502、Windows 常駐化の 7009/7000、URL Rewrite の {R:1} など、「同じ地雷を踏む人が減る」ことを優先します。
やりたかったことと要件
目的はシンプルで、社内LAN内から使える Web チャットを用意することでした。
要件はこんな感じです。
- Ollama はローカル実行(外に出さない)
- 公開するのは IIS(Webサーバ)のみ
- 同時実行制御あり(将来の利用増を見据える)
- 将来的に Windows 認証対応したい
- 常駐稼働(再起動しても戻る)
最終的な構成(ここが結論)
先に結論です。Windows で Node を「ちゃんと」常駐させるのは意外と面倒で、今回の現実解はこうなりました。
- IIS(ARR + URL Rewrite)で Reverse Proxy
- Node は 127.0.0.1:3000 で待ち受け
- Ollama は 11434
- Node の常駐は Windowsサービス化を諦めてタスクスケジューラ
- 起動時実行
- 最上位特権
- 再起動設定あり
/healthエンドポイントで死活監視(切り分けが爆速になる)
構成イメージはこれです。
Browser(LAN内)
→ IIS (Reverse Proxy)
→ Node (127.0.0.1:3000)
→ Ollama (11434)
「IIS だけ公開」「Ollama はローカル」の要件を満たしつつ、運用も回せる形に落ちました。
ハマりポイントと切り分けログ
ここからが本題で、実際に詰まった順に書きます。
結論としては「IIS が悪いと思ったら Node が止まってただけ」みたいな話が何度か出ます。
① Node が起動していないのに IIS 502
症状
ブラウザで叩くと、いきなりこれ。
HTTP 502 - Bad Gateway
IIS 逆プロキシの設定を疑いがちなんですが、この時点ではまずバックエンドの疎通を見ます。
切り分け
Node 側に健康診断のためのエンドポイントを用意して、ローカルで叩きます。
curl http://127.0.0.1:3000/health- →
Failed to connect
- →
これで「IIS の問題」ではなく「Node が死んでる」が確定しました。
IIS 502 は原因が広いので、IIS から追うと時間が溶けます。まず 127.0.0.1 直叩きが最短でした。
学び
- 502 を見たら、最初に バックエンドの直叩き
/healthは運用のためというより、切り分け速度のために価値がある
② PowerShell で sc が動かない罠(エイリアス問題)
次に「Node を Windows サービス化したい」と思って、これを打ちました。
sc create OllamaChat binPath= ...
症状
見覚えないエラー。
Set-Content : 引数 'binPath=' を受け入れる位置指定パラメーターが見つかりません。
一瞬「sc の構文ミス?」と思ったんですが、原因はもっとくだらないやつでした。
原因
PowerShell では sc が Set-Content のエイリアスになっていて、Windows の Service Control(sc.exe)じゃない。
対策
sc.exe create ...と 拡張子付きで呼ぶ
学び
- PowerShell は「同じコマンド名でも別物」が起きる
- サービス周りは
sc.exeを明示したほうが事故らない
③ サービス起動できない(7009 / 7000)— “サービス化は意外と難しい”
sc.exe でサービス登録はできました。でも起動が安定しません。
イベントログ
- 7009 / 7000 系のエラーが出て、起動に失敗します。
ざっくり言うと、
- 「指定時間内に応答しない」
- タイムアウト(30000ms)
原因(ここが重要)
Node を cmd /c でラップして起動すると、サービスコントロールマネージャ(SCM)に対して“起動完了”を正しく通知できない。
その結果、30秒で「起動してない扱い」になって落とされます。
つまり、sc.exe 単体で Node を “正しい Windows サービス” にするのは難しい、という話でした。
学び
- 7009/7000 は「アプリが重い」だけじゃなく、サービスとしての起動条件を満たしてないケースがある
- Node の常駐を「サービス」としてやるなら、それ用の仕組み(サービスラッパー等)が必要になる
④ URL Rewrite の “パス消失” 問題({R:1} がないと死ぬ)
IIS の ARR + URL Rewrite を入れて、Reverse Proxy の設定もできた。
…と思ったら API が 404 を返します。
症状
POST /api/chat→404
Node 側は /api/chat を生やしているのに、IIS 経由だと消える。
原因
IIS の転送先がこうなっていました。
http://127.0.0.1:3000
これだと、元のパス /api/chat が落ちて、常に / に飛ばされる感じになります。
正解
http://127.0.0.1:3000/{R:1}
{R:1} を付けて、元のパスを保ったままバックエンドへ渡す必要がありました。
学び
- 逆プロキシで「404」は、アプリ側より先に Rewrite でパスが消えてないか疑う
{R:1}は “知ってるかどうか” の罠
⑤ 502 の真因は IPv6/localhost ではなく Node 停止だった(ありがちな誤診)
IIS 502 は「localhost/IPv6 の罠」として語られがちです。
なので自分も以下を疑って切り分けしました。
典型チェック
localhostではなく 127.0.0.1 固定- ARR の Enable Proxy 確認
- Rewrite のパターン確認
それでも 502 のとき、結局の真因はこれでした。
- Node が止まってる
学び
- “典型原因” は潰しつつ、最後は プロセス生存確認に戻ってくる
- 502 は IIS のエラーに見えて、実態は「上流が死んでる」ことが多い
最終構成の運用ポイント(現実的に回る形)
最終的に安定したのはこの組み合わせです。
- Node は
node.exe server.jsを 直起動 - タスクスケジューラで常駐相当を作る
- 起動時実行
- 最上位特権
- 失敗時再起動(リトライ)
- IIS Reverse Proxy は 127.0.0.1 固定
- Rewrite は
{R:1}を必ず付ける /healthで死活監視(IIS 502 の切り分けが速い)
「Windows サービスとして正しく作る」より、「業務要件を満たして堅く動く」を取りました。
同じことをやる人向けチェックリスト
最後に、今回のログから「ここだけ見れば詰まりにくい」チェックリストを置きます。
疎通・502 切り分け
curl http://127.0.0.1:3000/healthが通る(Node 生存確認)- IIS の転送先は
localhostではなく 127.0.0.1 - ARR の Enable Proxy が有効
- 502 が出たら IIS より先に Node のプロセスを見る
URL Rewrite
- 転送先が
http://127.0.0.1:3000/{R:1}になっている /api/chatなどのパスが消えていないか確認
常駐化
- PowerShell で
scを打つならsc.exeを使う sc.exeだけで Node をサービス化しようとして 7009/7000 にハマったら、設計を疑う- 社内導入制約があるなら、タスクスケジューラを最初から候補に入れる
- 起動時
- 最上位特権
- 再起動設定
おわりに
今回の学びは「IIS 逆プロキシの設定」よりも、「Windows で Node を常駐させる難しさ」と「502 を見たときの切り分け順」でした。
- 502 は IIS のせいに見えるけど、まず Node の生存確認
- Rewrite の
{R:1}は知らないとハマる - Windows サービス化は “動けばOK” ではなく、SCM との握りが必要
- 社内運用を考えると、タスクスケジューラが一番早くて堅い場合がある
同じ構成を作ろうとしている人の「時間が溶けるポイント」を、少しでも減らせたら嬉しいです。

