SSD Nodes Learn 🎉 VPS $5.50/月〜
ガイド Matt Connor著者 Matt Connor ・更新日 2026-08-21

HisterをVPSで自己ホストする方法

Hister v0.17.0をVPSで運用し、訪問ページと保存ファイルの全文を検索します。バイナリ版とDockerの導入、TLS、ログイン認証、MCP endpointの設定を解説します。

Hister とは何か、何ではないか

Hister は自分でホストする個人用検索エンジンです。訪問したページと保存しているファイルの全文をインデックス化し、そのコレクションを Web インターフェイス、ターミナルクライアント、HTTP API、または AI(人工知能)アシスタントから検索できます。Hister が答えるのは、「あれはどこで読んだか」という1つの問いです。

多くの読者は、SearXNG を通じてこの考え方を知ります。ただし、両者は同じツールではありません。SearXNG はメタ検索プロキシです。検索クエリを受け取ると、ユーザーに代わって他の検索エンジンへ問い合わせ、追跡情報を除去した結果を返します。インデックスを所有するのは、それらの検索エンジンです。Hister は、ユーザーが提供したコンテンツから独自のインデックスを構築します。対象には、ブラウザー拡張機能で取得したページ、インポートしたブラウザー履歴、クロールした URL、指定したディレクトリ内のファイルが含まれます。自己ホストした SearXNG インスタンスは、公開 Web へのプライベートなアクセスを提供します。Hister は、自分が読んだコンテンツを検索する機能を提供します。役割が異なるため、1台のサーバーで両方を実行する構成は一般的です。

Hister は AGPLv3(GNU Affero General Public License, version 3)以降で提供されるフリーソフトウェアです。テレメトリはなく、クラウドサービスも必要ありません。このガイドでは、2026-07-28 時点の現行リリースだったバージョン v0.17.0 を固定します。設定をコピーする前に releases ページで現在のタグを確認し、そこで見つけたタグを固定してください。

VPS で Hister を自己ホストする理由

インデックスは完全でなければ役に立ちません。また、読んでいる間もサーバーが稼働していなければ完全にはなりません。ノートパソコンは 1 日の半分近くスリープします。その間にスマートフォンで開いたページはノートパソコンに届かず、夜間のインポートも開始されません。VPS(仮想プライベートサーバー)は常時稼働するため、所有するすべてのデバイスから同じインデックスに登録でき、就寝中もクローラーが動作します。

2 つ目の理由は分離です。app セクションで user_handling: true を設定すると、1 つのインスタンス上で各アカウントに固有の認証情報とドキュメントコレクションが割り当てられます。これにより、1 台のサーバーで家庭内や小規模チームの利用に対応しながら、他の人の閲覧履歴を検索せずに済みます。

3 つ目の理由は接続経路です。VPS にはすでに公開ホスト名と証明書があるため、ブラウザー拡張機能は、自分で管理していないネットワークからでもサーバーに接続できます。

インストール方法 1: リリースバイナリ

Hister はプラットフォームごとに 1 つのバイナリをリリースしています。チェックサムファイルとともにダウンロードし、インストール前に検証してください。

cd /tmp
curl -LO https://github.com/asciimoo/hister/releases/download/v0.17.0/hister_0.17.0_linux_amd64
curl -LO https://github.com/asciimoo/hister/releases/download/v0.17.0/hister_0.17.0_checksums.txt
sha256sum --ignore-missing -c hister_0.17.0_checksums.txt

正常な結果は、hister_0.17.0_linux_amd64: OK という 1 行だけです。FAILED の行が表示された場合、ダウンロードしたファイルが破損しているか改変されています。インストールせず、再度ダウンロードしてください。

バイナリをインストールし、続いてシステムアカウントと使用するディレクトリを作成します。

sudo install -m 755 /tmp/hister_0.17.0_linux_amd64 /usr/local/bin/hister
sudo useradd --system --home-dir /var/lib/hister --shell /usr/sbin/nologin hister
sudo install -d -o hister -g hister -m 750 /var/lib/hister
sudo install -d -m 755 /etc/hister
sudo hister create-config /etc/hister/config.yml

create-config はデフォルトの設定ファイルを書き込みます。また、このマシンでバイナリを実行できることも確認できます。アーキテクチャが異なるバイナリをダウンロードした場合は、ここで cannot execute binary file: Exec format error と表示されて失敗します。

必要な設定だけを編集します。生成されたファイルの残りはそのままで構いません。

app:
  directory: /var/lib/hister
  access_token: 'paste-a-long-random-string-here'
server:
  address: 127.0.0.1:4433
  base_url: https://hister.example.com

openssl rand -hex 32 でトークンを生成します。ファイルには認証情報が保存されるため、サービスを起動する前にアクセス権を制限してください。

sudo chown root:hister /etc/hister/config.yml
sudo chmod 640 /etc/hister/config.yml

systemd で実行する

/etc/systemd/system/hister.service を記述します。

[Unit]
Description=Hister personal search engine
After=network-online.target
Wants=network-online.target

[Service]
User=hister
Group=hister
Environment=HISTER_CONFIG=/etc/hister/config.yml
ExecStart=/usr/local/bin/hister listen
Restart=on-failure
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/hister

[Install]
WantedBy=multi-user.target

HISTER_CONFIG は設定ファイルのパスを指定する正式な環境変数です。そのため、この unit は hister アカウントのホームディレクトリに依存しません。ProtectSystem=strict により、このサービスから見えるファイルシステム全体が読み取り専用になります。そのため、データディレクトリを ReadWritePaths で指定する必要があります。ProtectHome=yes はサービスから /home を隠します。そのため、/home 配下の監視対象ディレクトリはインデクサーから空に見えます。そこにあるファイルをインデックス化する必要がある場合は、その行を削除してください。

sudo systemctl daemon-reload
sudo systemctl enable --now hister
systemctl status hister --no-pager
curl -sS -o /dev/null -w '%{http_code}\n' http://127.0.0.1:4433/

最後のコマンドが HTTP ステータスコードを出力すれば、プロセスは待ち受け中です。curl: (7) Failed to connect の場合は待ち受けておらず、journalctl -u hister -n 50 --no-pager で理由を確認できます。

Docker Compose を使用する方法

イメージは GitHub container registry に公開されており、リリースごとに 1 つのタグが付与されています。

services:
  hister:
    image: ghcr.io/asciimoo/hister:v0.17.0
    container_name: hister
    user: '1000:1000'
    restart: unless-stopped
    environment:
      - HISTER__SERVER__ADDRESS=0.0.0.0:4433
      - HISTER__SERVER__BASE_URL=https://hister.example.com
      - HISTER__APP__ACCESS_TOKEN=${HISTER_ACCESS_TOKEN}
    volumes:
      - ./data:/hister/data
    ports:
      - 127.0.0.1:4433:4433

すべての設定キーには HISTER__<SECTION>__<KEY> 形式の環境変数による上書き方法があります。区切り文字は 2 つのアンダースコアです。そのため、コンテナでデプロイする場合に設定ファイルをマウントする必要はありません。HISTER_ACCESS_TOKEN は compose ファイルと同じディレクトリにある .env ファイルへ保存します。ファイルを編集したい場合は、docker run --rm ghcr.io/asciimoo/hister:v0.17.0 create-config > config.yml でデフォルト値を出力できます。

上の 2 行は間違えやすいため、どちらも理解しておく必要があります。

コンテナ内のアドレスは 0.0.0.0:4433 にする必要があります。コンテナには独自のネットワーク名前空間があります。そのため、コンテナ内で 127.0.0.1 にバインドされたプロセスには、そのコンテナの内部からしか接続できません。公開ポートの転送先もなくなります。

公開ポートは 127.0.0.1:4433:4433 と記述し、4433:4433 にはしません。Docker は独自の netfilter ルールを挿入してポートを公開します。これらのルールは ufw のルールより先に評価されます。そのため、4433:4433 だけでは、ufw status でポートが閉じていると表示されるサーバーでも、インターネットから到達可能なままです。ホスト側を 127.0.0.1 にバインドすると、接続経路はリバースプロキシだけになります。同じ問題はサーバー上のすべてのコンテナに当てはまります。詳細は VPS 上の Docker Compose を参照してください。

デフォルトのイメージは UID 1000、GID 1000 で実行されます。そのため、./data はこのアカウントから書き込み可能でなければなりません。書き込みできない場合、コンテナは起動時に権限エラーで停止します。sudo chown -R 1000:1000 ./data で修正できます。これらの番号に慣れていない場合は、先に コンテナがファイルを書き込む UID と GID を読んでください。

個人用検索インデックスを公開してはいけない理由

Hister はデフォルトで 127.0.0.1:4433 で待ち受けます。このデフォルト設定は意図的なものです。1 か月使った後にインデックスへ何が保存されているかを考えてください。社内 Wiki のページ、請求書、ログイン中に開いたサポートチケット、パスワードリセットページ、その他に読んだすべての内容の全文が含まれます。プロジェクトのドキュメントにも、次のように明記されています。「Hister は、ページの内容を含む閲覧履歴全体をサーバーとの間で送受信します。」

漏えいしたパスワードデータベースは、まずクラックする必要があります。漏えいした個人用インデックスは平文で、すでに検索可能です。そのため、見た目が似た小規模なセルフホストアプリよりも慎重に扱う必要があります。

ここから 2 つの事実が分かります。Hister は初期状態では認証を必要としません。そのため、reverse proxy だけを前段に置くと、ホスト名を知っている人なら誰でも、あなたが読んだ内容の検索可能なコピーにアクセスできます。MCP endpoint もデフォルトで /mcp から提供されます。token がなければ、そこへ到達できるクライアントはインデックスを検索できます。

サービスを初めて localhost の外部で公開する前に、認証を設定してください。1 人で使う場合は app.access_token だけで済みます。これは browser extension、terminal client、任意の MCP client から送信する共有 secret です。複数人で使う場合は user_handling: true を設定して、アカウントを作成します。

sudo -u hister hister create-user alice --admin --config /etc/hister/config.yml

このコマンドでは、8 文字以上のパスワードを入力します。各アカウントには固有のドキュメントと個人用 API token が割り当てられます。所有者は profile page から、または hister update-user--regen-token flag を使って token を再生成できます。新しい token を生成すると、以前の token は直ちに無効になります。そのため、そのアカウントで使用しているすべてのデバイスを後から更新する必要があります。

明確な意図がない限り、app.public は変更しないでください。Public mode では認証なしの検索、preview、ファイル提供、MCP 検索が許可されます。一方で、書き込み、履歴へのアクセス、管理操作は引き続きブロックされます。

リバースプロキシ、TLS、ファイアウォール

Hister 自体は HTTPS を提供しないため、前段で TLS(トランスポート層セキュリティ)を終端します。Caddy が最も簡単です。ACME(自動証明書管理環境)を使用して、証明書の取得と更新を自動で行うためです。

hister.example.com {
    reverse_proxy 127.0.0.1:4433
}

sudo systemctl reload caddyで再読み込みします。証明書を発行するには、2つの条件を満たす必要があります。hister.example.comの A レコードがこのサーバーを指していることと、HTTP-01 チャレンジへの応答に使用するポート 80 が開いていることです。どちらかが欠けていると、ブラウザーにはページではなく TLS エラーが表示され、Caddy のログには失敗したチャレンジが繰り返し記録されます。

その後、他のすべてを閉じます。

sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status

ポート 4433 がこの一覧にないのは意図的です。

server.base_urlは、スキームを含め、ブラウザーに入力するアドレスと一致させる必要があります。一致しない場合、サーバーは base_url を基にアセットのリンクを生成し、ブラウザーは応答しないオリジンへアセットを要求するため、インターフェースはスタイルなしのテキストで表示され、画像も読み込まれません。同じ URL をブラウザー拡張機能にも設定します。

インデックスへの登録

ブラウザー拡張機能が主な収集手段です。Mozilla Add-ons または Chrome Web Store からインストールし、オプションページを開いて、サーバー URL に https://hister.example.com を設定し、アクセス token を貼り付けます。拡張機能は、アクセスした各ページのタイトル、全文、HTML、favicon を取得し、サーバーへ送信します。抽出はブラウザー内でクライアント側処理として行われます。拡張機能が接続する第三者サービスはありません。外部へのリクエストは、ページの favicon を取得するときだけ発生します。

クライアント側で抽出するため、非公開のインデックスを作成できます。拡張機能は、ログイン後かつレンダリング後のページを、表示されている状態のまま取得します。そのため、社内 wiki のページや有料記事も正しくインデックス化でき、サーバーに認証情報を渡す必要もありません。一方で、閲覧したすべてのページがインデックスの候補になります。そこで、追加のコンテンツを登録する前にスキップルールを設定します。

スキップルールは、single-user インストールでは rules.json に保存されます。複数ユーザー構成ではユーザーごとにデータベースへ保存されます。Web インターフェースの Rules タブが、ルールを編集する最も簡単な方法です。ルールには Go の正規表現を使用し、完全な URL に対して照合します。

^https://mail\.example\.com
^https://bank\.example\.com
.*?utm_source=

^mail.example.com のようなパターンは一致しません。照合対象の文字列は https:// で始まるためです。末尾の $ も、クエリ文字列を含む URL では一致しません。照合時にクエリパラメーターが保持されるためです。

既存の履歴はブラウザー自身のデータベースを読み取ってインポートします。そのため、このコマンドはブラウザープロファイルを保持するマシンで実行します。実行場所は VPS ではなく、使用中の laptop です。そこに同じ binary をインストールし、サーバーを指定します。

export HISTER_TOKEN='your-access-token'
hister import browser firefox -u https://hister.example.com -t "$HISTER_TOKEN"

インポートは browser-import-YYYY-MM-DD という名前の再開可能な job として実行されます。そのため、途中で中断し、後で再実行できます。ブックマークサービスも同じ方法でインポートできます。対象には Linkwarden、Karakeep、Wallabag、Linkding、Readeck、Shaarli が含まれます。再度インポートすると、前回より新しい項目だけが取得されます。

サーバー上のファイルは、設定でディレクトリを指定するとインデックス化されます。

indexer:
  directories:
    - path: '/var/lib/hister/documents'
      label: 'documents'
      filetypes: ['pdf', 'docx', 'md', 'txt']

PDF、DOCX、Markdown、Org mode、および有効な UTF-8 テキストファイルは、全文として読み込まれます。写真と動画は対象外です。そのため、画像ライブラリにはテキストではなく、顔、場所、日付をインデックス化するサーバーが必要です。この用途では、通常 PhotoPrism と Immich が比較対象になります。単一のページは hister index https://example.com で追加します。サイト全体を他のツール向けのクリーンなテキストに変換する処理は別の job です。この処理は、ページをクリーンなテキストに変換する self-hosted crawler が担当します。

検索はフィールド単位で行われます。そのため、クエリ言語を10分ほど読んでおくと役立ちます。

"connection reset" domain:github.com added:<30d
title:(wireguard|nftables) -tutorial sort:-visits

MCP を介して独自のインデックスをコーディングエージェントに接続する

MCP (model context protocol) は、アシスタントがサーバー上のツールを呼び出すためのインターフェースです。Hister は同じベース URL 上の POST /mcp で、streamable HTTP トランスポートを使用して MCP を提供し、searchget_previewget_history を公開します。認証には、API の他の機能と同じ bearer token を使用します。

{
  "mcpServers": {
    "hister": {
      "url": "https://hister.example.com/mcp",
      "headers": {
        "Authorization": "Bearer YOUR_ACCESS_TOKEN"
      }
    }
  }
}

X-Access-Token ヘッダーは、Authorization の代わりに使用できます。

ここで重要なのは、エージェントが検索する対象です。公開 Web 検索では、その時点で順位の高い結果が返されます。動きの速いソフトウェアでは、実行中のバージョンとは異なるバージョンのドキュメントが返されることが少なくありません。独自のインデックスなら、すでに読み、保持すると判断したページを返せます。また、get_preview が保存済みのコピーを提供するため、元のページがオフラインになっても回答を維持できます。公開結果も必要な場合は、両方のソースをエージェントに渡してください。SearXNG をバックエンドにしたブラウザー検索スキルを追加すると、公開 Web を別のツールとして利用できます。これらのエンドポイントを複数運用する場合は、VPS で MCP サーバーをホストするも読む価値があります。どのエンドポイントにも、同じ公開リスクがあるためです。

ディスク、バックアップ、保守

ドキュメントのインデックス済みページは、圧縮済みプレビューを含めて 1 ページあたり約 100 KB です。そのため、100,000 ページでおよそ 10 GB になります。クォータシステムはありません。2 つの設定が混同されがちです。indexer.max_file_size_mb(デフォルトは 1 MiB)は監視対象の単一ファイルに対する上限で、server.max_batch_body_size(デフォルトは 40 MiB)は 1 回の API リクエストに対する上限です。

app.directory で指定したディレクトリには、言語ごとのインデックスファイルを含む index.db、アカウントとジョブ用の db.sqlite3、プレビュー用の data/html/、および rules.json が格納されます。バックアップでは、サービスを停止し、このディレクトリ全体と設定ファイルをコピーします。hister export backup.json は移行用にドキュメントを JSON として書き出すものであり、サーバーのバックアップではありません。

知っておくべき保守コマンドは 2 つあります。hister reindex は検索インデックスを再構築します。インデクサーの設定を変更した後に必要です。大規模なインポート中にメモリ使用量が増加する場合は、indexer セクションで detect_languages: false を設定してから、インデックスを再構築します。hister cleanup は、削除後に残った孤立したプレビューと favicon ファイルを削除します。

削除はクエリとして実行されるため、最初に dry モードで実行します。

hister delete 'domain:example.com' --dry --verbose

コレクターが引き続きページを送信すると、削除したページは再び登録されます。そのため、削除する前に skip ルールを追加してください。

AGPLv3 が問題になるのは、コードを変更した場合だけです。変更していないコピーを自分で実行するだけなら、義務はありません。Hister を変更し、そのバージョンをネットワーク経由で他の人に利用させる場合、ライセンスにより、変更後のソースコードを利用者に提供する必要があります。

障害パターンと表示されるメッセージ

サーバーが起動しません。 ポート 4433 がすでに使用中か、設定ファイルに YAML の構文エラーがあります。sudo ss -lntp | grep 4433 でポートを使用しているプロセスを確認し、journalctl -u hister -n 50 --no-pager で解析エラーを表示します。

インターフェースは読み込まれますが、表示が崩れています。 文字化けや画像の欠落は、server.base_url がアドレスバーの URL と一致していないことを示します。末尾のスラッシュの有無も不一致として扱われます。

拡張機能が接続できません。 拡張機能に設定したサーバー URL は base_url と一致している必要があります。また、サーバーが起動していて最新の状態であり、途中のファイアウォールがページにメッセージを表示せずに接続を遮断していないことも確認します。Firefox は拡張機能のログを通常のコンソールに出力しません。about:debugging#/runtime/this-firefox を開き、Hister 拡張機能を確認します。

コンテナが起動時に終了します。 ./data の権限エラーは、デフォルトイメージ内のアカウントである UID 1000 以外の UID がディレクトリを所有していることを示します。

管理者用のルートから 403 Forbidden が返ります。 ユーザー管理を有効にすると、POST /api/reindexPOST /api/cleanup は管理者専用になるため、一般アカウントは拒否されます。

インポート中にメモリ使用量が増え続けます。 大量の履歴に対する言語検出が通常の原因です。detect_languages: false を設定し、その後に hister reindex を実行します。

FAQ

Hister は SearXNG とどう違いますか?

SearXNG はメタ検索プロキシです。検索クエリを公開検索エンジンへ転送し、追跡情報を除去した結果を返します。そのため、インデックスは各検索エンジンが管理します。Hister は、閲覧したページと保存したファイルの全文インデックスを独自に保持します。そのため、「あれはどこで読んだか」という検索には Hister が適し、「Web では何と書かれているか」という検索には SearXNG が適しています。両者は異なる問題を解決するため、多くの人が同じサーバーで併用しています。

VPS に閲覧履歴をすべて保存しても安全ですか?

事前に公開範囲を適切に設定した場合に限り、安全に運用できます。Hister は 127.0.0.1:4433 にバインドし、デフォルトでは認証を要求しません。app.access_token または user_handling: true を設定し、前段に TLS 対応のリバースプロキシを置き、ファイアウォールでポート 4433 を閉じてください。閲覧内容の全文インデックスは平文です。そのため、ポートに到達できる人は、暗号解読をしなくてもすべての内容を読めます。

ブラウザー拡張機能は必要ですか。それとも履歴をインポートするだけで済みますか?

インポートは 1 回限りの履歴補完です。ブラウザー自身の履歴データベースを読み取るため、サーバーではなくブラウザープロファイルを保持しているコンピューター上で実行します。以降は拡張機能がインデックスを最新に保ちます。また、ページのレンダリング後にブラウザー内で内容を抽出するため、ログインが必要なページも取得できます。一般的な構成は、最初に 1 回インポートし、その後は拡張機能を使う方法です。

コーディングエージェントから Hister のインデックスを検索できますか?

はい。Hister はベース URL 上の POST /mcp にある MCP (model context protocol) サーバーで、searchget_previewget_history を公開します。Authorization: Bearer ヘッダーにアクセストークンを設定し、クライアントを https://your-host/mcp に接続してください。これにより、エージェントは現在公開検索エンジンで上位に表示される情報ではなく、実際に読んだドキュメントを、読んだ時点のバージョンで検索できます。