SSD Nodes Learn 🎉 VPS từ $5.50/tháng
Hướng dẫn Matt ConnorBởi Matt Connor · Cập nhật ngày 2026-08-21

Tự host Hister: search engine cá nhân trên VPS

Chạy Hister trên VPS để tìm toàn văn trang bạn đã đọc và file đã lưu, với binary hoặc Docker, TLS, đăng nhập và MCP endpoint qua HTTP API.

Hister là gì và không phải là gì

Hister là một công cụ tìm kiếm cá nhân mà bạn tự host. Nó lập index toàn văn các trang bạn đã truy cập và các file bạn lưu, sau đó cho phép bạn tìm kiếm trong tập dữ liệu đó qua web interface, terminal client, HTTP API hoặc trợ lý AI (trí tuệ nhân tạo). Hister trả lời một câu hỏi: tôi đã đọc thông tin đó ở đâu.

Hầu hết người dùng biết đến ý tưởng này qua SearXNG, nhưng hai công cụ này không giống nhau. SearXNG là một metasearch proxy. Bạn gửi query đến SearXNG, nó thay mặt bạn gửi query đến các search engine khác, rồi trả về kết quả của họ sau khi loại bỏ tracking. Index thuộc về các search engine đó. Hister tự xây dựng index từ nội dung bạn cung cấp: các trang được browser extension capture, browser history được import, các URL được crawl và các file trong những directory bạn chỉ định. Một instance SearXNG tự host cho phép bạn truy cập riêng tư vào public web. Hister cho phép bạn tìm kiếm nội dung mình đã đọc. Hai công cụ phục vụ các mục đích khác nhau, nên chạy cả hai trên cùng một máy là việc bình thường.

Hister là free software theo AGPLv3 (GNU Affero General Public License, version 3) hoặc phiên bản mới hơn. Hister không có telemetry và không cần cloud service. Guide này cố định version v0.17.0, là release hiện tại vào 2026-07-28. Hãy kiểm tra trang releases để lấy tag hiện tại trước khi copy bất kỳ nội dung nào, sau đó cố định tag bạn tìm thấy ở đó.

Vì sao nên tự host Hister trên VPS

Một index chỉ hữu ích khi đầy đủ. Nó chỉ đầy đủ nếu server đang chạy trong lúc bạn đọc. Laptop thường ở trạng thái sleep nửa ngày. Những trang bạn mở trên điện thoại trong thời gian đó không bao giờ đến được laptop, và tác vụ import qua đêm cũng không bắt đầu. VPS (virtual private server) luôn hoạt động, nên mọi thiết bị bạn sở hữu đều gửi dữ liệu vào cùng một index. Crawler cũng tiếp tục làm việc trong lúc bạn ngủ.

Lý do thứ hai là tách biệt. Thiết lập user_handling: true trong phần app cấp cho mỗi account một bộ credentials riêng và một collection tài liệu riêng trên cùng một instance. Một server có thể phục vụ cả gia đình hoặc một team nhỏ mà không ai tìm kiếm trong dữ liệu đọc của người khác.

Lý do thứ ba là hạ tầng kết nối. VPS đã có hostname public và certificate. Đây là những thứ browser extension cần để kết nối đến server từ một network mà bạn không kiểm soát.

Cách cài đặt 1: binary của bản phát hành

Hister phát hành một binary cho mỗi platform. Hãy tải binary cùng file checksum, rồi xác minh trước khi cài đặt.

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

Kết quả hợp lệ chỉ có một dòng hister_0.17.0_linux_amd64: OK. Dòng FAILED cho biết bản tải xuống bị hỏng hoặc đã bị thay đổi, vì vậy hãy tải lại thay vì cài đặt.

Cài đặt binary, sau đó tạo system account và các thư mục mà binary sẽ sử dụng.

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 ghi một file cấu hình mặc định và đồng thời xác nhận binary có thể chạy trên máy này. Bản tải xuống không đúng architecture sẽ fail ngay tại đây với cannot execute binary file: Exec format error.

Chỉnh một vài setting cần thiết. Có thể giữ nguyên phần còn lại của file đã tạo.

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

Tạo token bằng openssl rand -hex 32. File lúc này chứa credential, vì vậy hãy hạn chế quyền truy cập trước khi service khởi động.

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

Chạy bằng systemd

Viết /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 là biến môi trường được tài liệu ghi nhận để chỉ đường dẫn đến file cấu hình, vì vậy unit không phụ thuộc vào thư mục home của account hister. ProtectSystem=strict đặt toàn bộ filesystem ở chế độ chỉ đọc cho service này, nên ReadWritePaths phải chỉ rõ thư mục dữ liệu. ProtectHome=yes ẩn /home khỏi service, vì vậy thư mục được theo dõi bên dưới /home sẽ hiển thị là rỗng đối với indexer. Xóa dòng đó nếu bạn cần index các file tại đó.

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/

Mọi mã trạng thái HTTP được in ra bởi lệnh cuối đều có nghĩa là process đang listen. curl: (7) Failed to connect nghĩa là process không listen, còn journalctl -u hister -n 50 --no-pager sẽ cho biết lý do.

Đường dẫn cài đặt 2: Docker Compose

Image được publish trên GitHub container registry, với một tag cho mỗi release.

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

Mỗi configuration key có một environment override theo dạng HISTER__<SECTION>__<KEY>, dùng hai dấu gạch dưới làm dấu phân cách. Vì vậy, khi deploy bằng container, bạn không cần mount config file. Giữ HISTER_ACCESS_TOKEN trong file .env nằm cạnh compose file. Nếu muốn chỉnh sửa file, docker run --rm ghcr.io/asciimoo/hister:v0.17.0 create-config > config.yml sẽ in ra các giá trị mặc định.

Hai dòng trên rất dễ cấu hình sai, và bạn nên hiểu rõ cả hai.

Địa chỉ bên trong container phải là 0.0.0.0:4433. Container có network namespace riêng. Vì vậy, process bind vào 127.0.0.1 bên trong container chỉ có thể được truy cập từ chính container đó. Khi đó, published port không có gì để forward tới.

Published port phải được ghi là 127.0.0.1:4433:4433, không phải 4433:4433. Docker publish port bằng cách chèn các netfilter rule riêng. Các rule này được đánh giá trước các rule của ufw. Vì vậy, một 4433:4433 thông thường vẫn có thể truy cập từ Internet, ngay cả trên máy mà ufw status hiển thị port đã bị đóng. Bind phía host vào 127.0.0.1 khiến reverse proxy trở thành cách truy cập duy nhất. Bẫy tương tự áp dụng cho mọi container trên server. Docker Compose trên VPS trình bày phần còn lại.

Image mặc định chạy với UID 1000 và GID 1000. Vì vậy, ./data phải cho account đó quyền ghi. Nếu không, container sẽ dừng khi startup với lỗi permission. sudo chown -R 1000:1000 ./data sẽ khắc phục việc này. Nếu bạn chưa quen với các số này, hãy đọc container ghi file bằng UID và GID nào trước.

Vì sao personal search index là thứ tệ nhất để expose

Theo mặc định, Hister lắng nghe trên 127.0.0.1:4433 và đây là một thiết lập có chủ đích. Hãy xem index chứa gì sau một tháng sử dụng: các trang wiki nội bộ, hóa đơn, support ticket bạn đã mở khi đăng nhập, các trang reset password và toàn bộ nội dung của mọi thứ khác bạn đã đọc. Tài liệu của project nêu rõ: "Hister truyền toàn bộ lịch sử duyệt web của bạn, cùng nội dung các trang, đến và đi từ server."

Cơ sở dữ liệu password bị lộ vẫn phải được crack. Personal index bị lộ là plain text và đã có thể search ngay, nên cần được bảo vệ cẩn thận hơn cả self-hosted app nhỏ mà nó có vẻ giống.

Từ đó có 2 hệ quả. Hister không yêu cầu authentication theo mặc định, nên chỉ cần một reverse proxy là có thể publish bản sao có thể search về nội dung bạn đã đọc cho bất kỳ ai biết hostname. MCP endpoint cũng được serve tại /mcp theo mặc định. Nếu không có token, bất kỳ client nào truy cập được endpoint này đều có thể chạy search trên index.

Hãy cấu hình authentication trước khi service rời localhost lần đầu tiên. Một user chỉ cần app.access_token, tức một shared secret được browser extension, terminal client và mọi MCP client gửi đi. Với nhiều người, hãy đặt user_handling: true rồi tạo account:

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

Command sẽ yêu cầu password có ít nhất 8 ký tự. Mỗi account có bộ tài liệu riêng và một personal API token. Owner có thể tạo lại token từ profile page hoặc dùng flag --regen-token với hister update-user. Khi tạo token mới, token trước đó bị vô hiệu hóa ngay lập tức. Vì vậy, sau đó phải cập nhật mọi device mà account đó sử dụng.

Không thay đổi app.public trừ khi bạn thực sự muốn vậy. Public mode cho phép search không cần authentication, xem preview, serve file và search qua MCP, nhưng vẫn chặn thao tác ghi, truy cập history và các thao tác admin.

Reverse proxy, TLS và firewall

Hister không tự phục vụ HTTPS, vì vậy hãy terminate TLS (transport layer security) ở phía trước nó. Caddy là cách triển khai ngắn gọn nhất vì nó tự yêu cầu và gia hạn certificate qua ACME (automatic certificate management environment).

hister.example.com {
    reverse_proxy 127.0.0.1:4433
}

Reload bằng sudo systemctl reload caddy. Có 2 điều kiện phải đáp ứng trước khi cấp certificate: A record của hister.example.com phải trỏ đến server này, và port 80 phải được mở vì HTTP-01 challenge được trả lời tại đó. Nếu thiếu một trong hai điều kiện, browser sẽ nhận lỗi TLS thay vì trang web, còn log của Caddy sẽ lặp lại lỗi challenge.

Sau đó đóng tất cả các port khác.

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

Port 4433 cố ý không có trong danh sách đó.

server.base_url phải khớp với địa chỉ bạn nhập vào browser, bao gồm cả scheme. Nếu không khớp, interface sẽ tải lên dưới dạng văn bản chưa được style và thiếu hình ảnh vì server tạo các asset link từ base_url, sau đó browser yêu cầu các asset đó từ một origin không phản hồi. Dùng cùng URL đó trong browser extension.

Nạp dữ liệu vào index

Browser extension là collector chính. Cài extension từ Mozilla Add-ons hoặc Chrome Web Store, mở trang options, đặt server URL thành https://hister.example.com rồi dán access token. Sau đó extension sẽ thu thập title, toàn bộ text, HTML và favicon của từng trang bạn truy cập, rồi gửi chúng đến server. Việc trích xuất diễn ra ở client side, bên trong browser. Extension không liên hệ với bên thứ ba nào. Request duy nhất ra bên ngoài là request lấy favicon của trang.

Trích xuất ở client side giúp tạo private index. Extension nhìn thấy trang đúng như bạn thấy sau khi đăng nhập và sau khi trang được render. Vì vậy, trang wiki nội bộ hoặc bài viết trả phí vẫn được index đúng, còn server không bao giờ cần credential của bạn. Điều này cũng có nghĩa mọi nội dung bạn xem đều có thể được đưa vào index. Vì thế, hãy cấu hình skip rule trước khi thêm nội dung khác.

Skip rule nằm trong rules.json trên bản cài single-user, hoặc nằm theo từng user trong database. Tab Rules trong web interface là cách dễ nhất để chỉnh sửa chúng. Đây là các biểu thức chính quy Go được so khớp với toàn bộ URL:

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

Pattern như ^mail.example.com không bao giờ khớp, vì chuỗi được kiểm tra bắt đầu bằng https://. $ ở cuối cũng không khớp với URL có query string, vì query parameter vẫn được giữ lại khi so khớp.

Lịch sử hiện có được import bằng cách đọc database của browser. Vì vậy, lệnh này phải chạy trên máy chứa browser profile, tức laptop của bạn chứ không phải VPS. Cài cùng binary trên máy đó rồi trỏ nó đến server:

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

Một lần import chạy dưới dạng resumable job có tên browser-import-YYYY-MM-DD. Bạn có thể dừng job rồi chạy lại sau. Các dịch vụ bookmark cũng được import theo cách tương tự, gồm Linkwarden, Karakeep, Wallabag, Linkding, Readeck và Shaarli. Mỗi lần import lại chỉ lấy những nội dung mới hơn lần import trước.

Các file trên server được index bằng cách khai báo directory trong config:

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

PDF, DOCX, Markdown, Org mode và các file text UTF-8 hợp lệ được đọc dưới dạng toàn bộ text. Photos và video không nằm trong danh sách này. Vì vậy, image library cần một server có khả năng index face, địa điểm và ngày tháng thay vì text. PhotoPrism và Immich là hai lựa chọn thường được so sánh cho công việc đó. Dùng hister index https://example.com để thêm một trang đơn lẻ. Việc chuyển toàn bộ website thành text sạch cho các tool khác là một công việc riêng, do các crawler self-hosted chuyển trang thành text sạch xử lý.

Search hoạt động theo field, nên bạn nên dành mười phút đọc query language:

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

Cho một coding agent sử dụng index riêng qua MCP

MCP (model context protocol) là interface để assistant gọi các tool trên server. Hister cung cấp MCP tại POST /mcp với cùng base URL, sử dụng streamable HTTP transport, và expose search, get_previewget_history. Cơ chế authentication dùng cùng bearer token như phần còn lại của API.

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

Header X-Access-Token là lựa chọn thay thế cho Authorization.

Giá trị nằm ở nội dung mà agent tìm kiếm. Open web search trả về kết quả đang được xếp hạng tại thời điểm hiện tại. Với software thay đổi nhanh, kết quả đó thường là documentation cho version bạn không chạy. Index riêng trả về trang bạn đã đọc và chọn lưu lại. get_preview cung cấp bản copy đã lưu, nên câu trả lời vẫn dùng được nếu trang gốc offline. Nếu cũng muốn có kết quả public, hãy cung cấp cả hai nguồn cho agent: một browser search skill dùng SearXNG làm backend thêm open web dưới dạng tool riêng. Khi chạy nhiều hơn một endpoint như vậy, hosting MCP server trên VPS là nội dung nên đọc, vì tất cả endpoint đều có cùng vấn đề exposure này.

Ổ đĩa, backup và bảo trì

Tài liệu tính trung bình mỗi trang đã index chiếm khoảng 100 KB, bao gồm preview đã nén, nên một trăm nghìn trang sẽ chiếm khoảng 10 GB. Không có hệ thống quota. Hai setting thường bị nhầm là một: indexer.max_file_size_mb (mặc định 1 MiB) giới hạn kích thước của một file được watch, còn server.max_batch_body_size (mặc định 40 MiB) giới hạn một request API.

Directory được chỉ định bởi app.directory chứa index.db với các file index theo từng ngôn ngữ, db.sqlite3 dành cho account và job, data/html/ dành cho preview, cùng với rules.json. Backup gồm service đã dừng, một bản sao của toàn bộ directory đó và file config. hister export backup.json ghi document dưới dạng JSON để migration, không phải backup server.

Có 2 lệnh bảo trì cần biết. hister reindex rebuild search index và bắt buộc phải chạy sau khi thay đổi setting của indexer. Nếu mức sử dụng memory tăng trong lúc import lớn, hãy đặt detect_languages: false trong section indexer rồi reindex. hister cleanup xóa các file preview và favicon mồ côi còn sót lại sau khi xóa.

Xóa là một query, nên trước tiên hãy chạy ở chế độ dry run:

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

Page đã xóa sẽ xuất hiện lại nếu collector vẫn submit page đó, vì vậy hãy thêm skip rule trước khi xóa.

AGPLv3 chỉ bắt đầu có ý nghĩa khi bạn thay đổi code. Việc tự chạy một bản chưa sửa đổi không tạo ra nghĩa vụ nào. Nếu bạn sửa Hister và cho người khác sử dụng phiên bản của bạn qua network, licence yêu cầu bạn cung cấp source đã sửa đổi cho họ.

Các tình huống lỗi và chuỗi bạn sẽ thấy

Server không khởi động. Cổng 4433 đã bị tiến trình khác chiếm dụng hoặc file cấu hình có lỗi cú pháp YAML. sudo ss -lntp | grep 4433 cho biết tiến trình nào đang giữ cổng, còn journalctl -u hister -n 50 --no-pager in ra lỗi phân tích cú pháp.

Giao diện tải được nhưng hiển thị lỗi. Văn bản bị xáo trộn và hình ảnh bị thiếu nghĩa là server.base_url không khớp với URL trên thanh địa chỉ. Dấu gạch chéo ở cuối cũng được tính là không khớp.

Extension không kết nối được. Server URL trong extension phải giống base_url, server phải đang chạy và ở phiên bản hiện tại. Firewall ở giữa có thể chặn kết nối mà trang không hiển thị thông báo nào. Firefox không đưa log của extension vào console thông thường. Mở about:debugging#/runtime/this-firefox và kiểm tra extension Hister.

Container thoát khi khởi động. Lỗi quyền trên ./data có nghĩa là thư mục thuộc về UID khác 1000. UID 1000 là tài khoản bên trong image mặc định.

403 Forbidden từ admin route. POST /api/reindexPOST /api/cleanup chỉ dành cho admin khi bật tính năng quản lý user, nên tài khoản thông thường sẽ bị từ chối.

Mức sử dụng memory tăng trong khi import. Nguyên nhân thường gặp là phát hiện ngôn ngữ trên history lớn. Đặt detect_languages: false rồi chạy hister reindex.

FAQ

Hister khác SearXNG như thế nào?

SearXNG là một proxy metasearch: nó chuyển tiếp truy vấn của bạn đến các công cụ tìm kiếm công khai và trả về kết quả đã loại bỏ tracking, nên index vẫn thuộc về các công cụ đó. Hister tự duy trì full-text index của các trang bạn đã truy cập và các file bạn lưu, nên nó trả lời câu hỏi “tôi đã đọc thông tin đó ở đâu”, còn SearXNG trả lời “web nói gì”. Hai công cụ giải quyết các vấn đề khác nhau, và nhiều người chạy cả hai trên cùng một server.

Đưa toàn bộ lịch sử duyệt web lên VPS có an toàn không?

Chỉ an toàn khi bạn xử lý việc giới hạn exposure trước. Hister bind vào 127.0.0.1:4433 và mặc định không yêu cầu authentication. Thiết lập app.access_token hoặc user_handling: true, đặt một reverse proxy có TLS ở phía trước, và giữ cổng 4433 đóng trên firewall. Full-text index về nội dung bạn đã đọc là plain text, nên bất kỳ ai truy cập được vào cổng này đều có thể đọc toàn bộ mà không cần crack gì cả.

Tôi có cần browser extension không, hay chỉ cần import history?

Import chỉ là thao tác backfill một lần. Nó đọc database history của chính browser, nên chạy trên máy đang lưu browser profile thay vì trên server. Sau đó extension tiếp tục cập nhật index và thu thập được cả các trang yêu cầu đăng nhập, vì nó trích xuất nội dung trong browser sau khi trang được render. Cách triển khai phổ biến là import một lần, sau đó dùng extension.

Coding agent có thể tìm kiếm Hister index của tôi không?

Có. Hister là một MCP (model context protocol) server tại POST /mcp trên base URL của bạn, cung cấp search, get_previewget_history. Trỏ client đến https://your-host/mcp và gửi kèm header Authorization: Bearer chứa access token của bạn. Khi đó agent có thể tìm kiếm trong tài liệu bạn thực sự đã đọc, tại đúng version bạn đã đọc, thay vì tìm trong các kết quả đang được public search engine xếp hạng hôm nay.