ローカルAIにWeb検索を足す:SearXNG × Open WebUI構築ガイド【有料API比較あり】
目次
はじめに
ローカルLLMを運用していると、どうしても欲しくなるのが「最新の情報を検索する力」です。学習データのカットオフ以降の出来事は当然知らないし、今日の天気すら答えられない。そこにWeb検索を足してやると、ローカルLLMの実用性が一段跳ね上がります。
選択肢は大きく2つ。TavilyやBrave Search APIのような有料Web検索APIを使うか、SearXNGを自前で立てるか。今回は後者、SearXNGをNucBox上のDocker Compose環境に追加して、Open WebUIのWeb検索バックエンドとして繋ぐところまでをやりました。
結論から言うと、立ち上げ自体は素直なんですが、「権限」「バージョン」「本文フェッチ」の3か所で順番にハマりました。同じ構成を組む人がスムーズに通れるよう、詰まりどころとその理由を全部残しておきます。
なぜ有料APIではなくSearXNGなのか
SearXNGはオープンソースのメタ検索エンジンです。自分では検索インデックスを持たず、裏でGoogleやBing、DuckDuckGoなど複数の検索エンジンに問い合わせて結果を集約して返す仕組み。自前ホストなので無料で、クエリが外部のAPIプロバイダーに送られないというプライバシー上の利点があります。
そして地味に効いてくるのが、ここ最近の「無料Web検索APIが次々消えている」という流れです。2025年にMicrosoftがBing Search APIを終了し、2026年2月にはBraveが長年提供していた無料枠を廃止してメタード課金制に移行しました。AIエージェント向けの安価な検索手段が年々厳しくなっているなかで、自分の手元で完結するSearXNGの価値は相対的に上がっています。
もちろんトレードオフはあります。運用は自分持ちですし、メタ検索の性質上レスポンスは専用APIより不安定なこともある。そのあたりも含めて、次のセクションで比較します。
有料Web検索APIとの比較
Open WebUIが対応している主要なWeb検索バックエンドを、2026年5月時点の料金とあわせて並べてみます。
サービス | 料金(2026年5月時点) | 無料枠 | プライバシー | 運用 |
|---|---|---|---|---|
SearXNG(自前) | 無料 | 無制限(自前ホスト) | クエリが外部に出ない | 自分で運用 |
Tavily | $0.008/credit(PAYGO)、Researcher $30/月 | 1,000検索/月 | プロバイダーに送信 | おまかせ |
Brave Search API | $0.003〜0.005/query($5プリペイド制) | 廃止($5クレジット) | プロバイダーに送信 | おまかせ |
Serper(Google SERP) | $0.30〜1.00/1kクエリ | 2,500クエリ/月 | プロバイダーに送信 | おまかせ |
ざっくり言うと、有料APIは「ランク済み・整形済みの結果がすぐ手に入る代わりに従量課金」、SearXNGは「無料・プライベートだが運用と結果の質は自分で面倒を見る」という構図です。
個人のローカルAI用途で、検索回数がそこそこ多く、クエリの内容を外に出したくないなら、SearXNGはかなり相性が良い選択です。逆に、結果の質を最優先したい商用RAGなどでは有料APIのほうが向く場面もあります。
環境
マシン: GMKtec NucBox EVO X1(Ubuntu 24.04)
既存スタック: Docker Compose(Ollama / Open WebUI / ComfyUI など)
ネットワーク: 既存サービスと同じ
ai_networkに同居SearXNG:
searxng/searxng(Docker)Open WebUI:
v0.9.5
既にOllama + Open WebUIが動いている前提で、そこにSearXNGを足していきます。
インストール:docker-composeに追加する
既存のdocker-compose.ymlのservices:配下に、SearXNGサービスを追加します。同じai_networkに乗せることで、Open WebUIからコンテナ名で直接叩けるようになります。
# --- SearXNG (Web Search Backend) ---
searxng:
image: searxng/searxng:latest
container_name: ai_searxng
restart: always
ports:
- "8888:8080"
volumes:
- ./data/searxng:/etc/searxng:rw
environment:
- SEARXNG_BASE_URL=${SEARXNG_BASE_URL}
- SEARXNG_SECRET=${SEARXNG_SECRET}
cap_drop:
- ALL
cap_add:
- CHOWN
- SETGID
- SETUID
logging:
driver: json-file
options:
max-size: "1m"
max-file: "1".envにも変数を追記します。
$ echo "SEARXNG_BASE_URL=http://[サーバのIPアドレス]:8888/" >> .env
$ echo "SEARXNG_SECRET=$(openssl rand -hex 32)" >> .envこれで起動……といきたいところですが、ここで最初の壁にぶつかります。
ハマりポイント①:settings.ymlが権限エラーで作れない
docker compose up -d searxngすると、コンテナが起動と終了を延々ループします。ログを見ると:
cp: can't create '/etc/searxng/settings.yml': Permission denied
sed: /etc/searxng/settings.yml: No such file or directory
!!! ERROR "/etc/searxng/settings.yml" is not a valid file, exiting...原因はcap_drop: ALLです。セキュリティのためケーパビリティを全部落としているせいで、コンテナがホストマウントしたディレクトリに設定ファイルを自動生成できません。
解決策は、ホスト側で先にsettings.ymlを用意してしまうこと。公式イメージの中からテンプレートを取り出します。
まずディレクトリを作って所有権を自分にします。
$ sudo chown -R $USER:$USER ./data/searxng次にイメージ内のテンプレートをコピー。ここで注意なのが、テンプレートのパスがバージョンによって変わる点です。手元の環境では/usr/local/searxng/searx/settings.ymlでした(searxngではなくsearxディレクトリ)。不安なら先にfindで探すのが確実です。
# テンプレートの場所を確認
$ docker run --rm --entrypoint find searxng/searxng:latest / -name "settings.yml" 2>/dev/null
# 出てきたパスからコピー
$ docker run --rm --entrypoint cat searxng/searxng:latest \
/usr/local/searxng/searx/settings.yml > ./data/searxng/settings.ymlsecret_keyを埋めて、コンテナの実行ユーザーが書けるよう権限を緩めます。
$ sed -i "s|ultrasecretkey|$(openssl rand -hex 32)|g" ./data/searxng/settings.yml
$ chmod -R 777 ./data/searxng最後にsettings.ymlを編集して、JSON出力を有効化します。これが一番重要で、APIとして叩くにはJSON出力が必須です。デフォルトではhtmlしか無いのでjsonを追加します。
search:
formats:
- html
- json
default_lang: "auto"
server:
limiter: false
public_instance: falsedefault_langはautoのままにしておくと、日本語クエリも英語クエリも自動判定してくれます。エージェント用途ではautoが素直に動きます。limiter: falseは、信頼できるローカル内からの呼び出しのみという前提で、内蔵BOT検知に弾かれる事故を防ぐためです。
これで起動します。
$ docker compose up -d searxng
$ docker compose logs -f searxngListening at: http://:::8080が出れば成功。なおahmiaやtorch(Tor検索系)、radio browserのエラーがログに出ますが、Web検索用途では一切無関係なので無視で問題ありません。
ハマりポイント②:Open WebUIの設定保存が500エラー
SearXNG側は動いた。次はOpen WebUIで設定だ、と管理画面のウェブ検索設定を行いましたが、ログを見るとこんなエラーが出ていました。
File "/app/backend/open_webui/routers/retrieval.py", line 1177, in update_rag_config
AttributeError: Config key 'FETCH_URL_MAX_CONTENT_LENGTH' not foundこれはOpen WebUIのイメージタグにmain(開発版)を使っていたのが原因でした。mainは更新が激しく、DBに保存済みの設定スキーマとコード側がズレると、こういうConfig key not foundが出ます。
解決策はシンプルで、安定版タグに固定すること。2026年5月時点の最新安定版v0.9.5に上げました。
open-webui:
image: ghcr.io/open-webui/open-webui:v0.9.5バージョンアップ前に、念のためデータをバックアップしておくのを強く推奨します。チャット履歴も設定も全部ここに入っています。
$ docker compose stop open-webui
$ sudo cp -a ./data/open_webui_data ./data/open_webui_data.bak_$(date +%Y%m%d)
$ docker compose pull open-webui
$ docker compose up -d open-webui更新後はDBマイグレーションが走るので、ログでUvicorn runningまで完走するのを確認します。これでFETCH_URL_MAX_CONTENT_LENGTHエラーは解消し、設定が保存できるようになりました。
ハマりポイント③:検索は動くのにソースが見つからない
設定が保存できて、チャットのウェブ検索トグルもONにした。なのに「ソースが見つかりませんでした」と返ってくる。
ここでログを見ると、犯人がはっきりしました。
WARNING langchain_community.document_loaders.web_base:_fetch_with_rate_limit
Error fetching https://weathernews.jp/onebox/tenki/... skipping
Error fetching https://tenki.jp/forecast/... skipping
Error fetching https://weather.yahoo.co.jp/weather/... skipping
Fetching pages: 100%|##########| 3/3つまり流れはこうです。SearXNGはちゃんと検索結果(天気サイトのURL)を返している。ところがOpen WebUIがそのURLにアクセスして本文を取得しようとして、全部失敗していた。結果、本文ゼロで「ソースなし」になっていたわけです。検索そのものは成功していて、取得した結果URLへの本文フェッチが失敗していたのが真因でした。
切り分けとして、Open WebUIコンテナから外部サイトに出られるかを確認します。
$ docker compose exec open-webui curl -sI -L "https://tenki.jp/" -o /dev/null -w "%{http_code}\n"
200200が返るので外部到達は問題なし。素のcurlは通るのにOpen WebUIのローダーだと全滅する、というのは、ローダーが使うHTTPクライアントの挙動(リダイレクト、UA、JSレンダリング必須ページなど)と相性が悪いケースです。天気サイトはまさにそういうページが多い。
解決策は、本文フェッチ自体をスキップしてしまうこと。Open WebUIの管理者設定 → ウェブ検索に、ちょうどいいトグルが2つあります。
埋め込みと検索をバイパス → ON
Webローダーをバイパス → ON
この2つをONにすると、Open WebUIは「個別ページにアクセスして本文取得 → ベクトル化 → 検索」という重い処理を全部スキップして、SearXNGが返したスニペット(タイトルと要約文)をそのままLLMのコンテキストに渡します。本文フェッチをやらなくなるので、フェッチ失敗で詰まることがなくなります。
ローカルLLM用途では、この構成が速度・安定性ともに有利です。記事全文を踏まえた精密な要約には向きませんが、時事情報の把握や軽い調べ物なら十分実用的です。
Open WebUI側の連携設定
最終的なWeb検索設定はこうなります。管理画面 → 管理者設定 → ウェブ検索で:
項目 | 設定値 |
|---|---|
Web検索 | 有効化(ON) |
ウェブ検索エンジン |
|
Searxng クエリ URL |
|
埋め込みと検索をバイパス | ON |
Webローダーをバイパス | ON |
ポイントは、Query URLにlocalhostや127.0.0.1を使わないこと。これらはコンテナ内では「Open WebUIコンテナ自身」を指してしまい、SearXNGには届きません。同じai_network上にいるので、コンテナ名ai_searxngとコンテナ内ポート8080で直接繋ぐのが正解です。ホスト経由([サーバのIPアドレス]:8888)でも一応動きますが、わざわざ遠回りする必要はありません。
なお、チャット入力欄のWeb検索トグルは送信のたびにリセットされる仕様なので、検索したいときは毎回ONにする必要があります。

動作確認
SearXNGがJSONを返すか、まずホスト経由で確認。
$ curl "http://[サーバのIPアドレス]:8888/search?q=test&format=json" | head -c 300次に、Open WebUIから見える経路(コンテナ名接続)を確認します。これが通れば連携の準備は完了です。
$ docker compose exec open-webui curl -s "http://ai_searxng:8080/search?q=test&format=json" | head -c 300両方でresults配列を含むJSONが返ってくればOK。
最後に、チャット画面で実際に検索を走らせます。ここで見落としやすいのが、チャット入力欄でWeb検索をONにする操作です。管理画面で有効化しただけでは検索は走りません。
チャット入力欄の左下にある連携アイコンをクリックしてメニューを開く
メニューの中の「ウェブ検索」をクリックしてONにする(アイコンが点灯/緑色になる)
その状態で時事的な質問を送信する
モデルが自動で検索を判断するわけではなく、明示的にONにしたときだけ走る挙動です。しかも送信のたびにリセットされるので、検索したいときは毎回ONを確認してください。
ここまでやれば、SearXNG経由の検索結果がLLMの回答に反映され、ソース付きで返ってくるようになります。
まとめ
SearXNGをローカルAIのWeb検索バックエンドにする構成は、無料・プライベートで、無料Web検索APIが次々消えていく今の状況では特に価値があります。立ち上げ自体は素直ですが、今回ハマった3か所はおさえておくと早いです。
1つめ、cap_drop: ALL環境ではsettings.ymlを自動生成できないので、ホスト側でテンプレートを先に配置する(パスは/usr/local/searxng/searx/settings.yml)。2つめ、Open WebUIはmainタグだと設定保存が壊れることがあるので、安定版タグ(v0.9.5など)に固定する。3つめ、検索が動くのにソースが出ない場合は、本文フェッチが失敗している可能性が高いので「埋め込みと検索をバイパス」「Webローダーをバイパス」をONにする。
最後にひとつ運用の教訓。今回の設定保存エラーはmainタグを追いかけていたのが原因でした。SearXNGもOpen WebUIも、安定動作を確認したらイメージタグを固定しておくと、ある日docker compose pullしたら壊れる、という事故を防げます。基盤サービスはバージョン固定が安心です。
searxng:
image: searxng/searxng:2026.5.23-323ce7600 # 動作確認したバージョンに固定
open-webui:
image: ghcr.io/open-webui/open-webui:v0.9.5「検索は動いているのにソースが出ない」が一番デバッグしにくいポイントでした。同じ症状で困っている人がいたら、まずログでフェッチが失敗していないか確認して、バイパス設定を試してみてください。



