Install Meilisearch on Linux
Install Meilisearch on Linux, secure it with a master key, create indexes, tune typo tolerance, and sync data from PostgreSQL using a systemd timer.
Before you start
- ▸A Linux server with sudo access
- ▸PostgreSQL installed and a database with data to sync
- ▸curl available for downloading binaries and testing the API
- ▸Python 3.9 or newer for the sync script
Meilisearch is a fast, open-source search engine written in Rust. It handles typos, synonyms, and ranking out of the box, making it a practical drop-in search backend for web applications. This guide walks through installing Meilisearch on a Linux server, securing it with a master key, creating indexes, tuning typo tolerance, and syncing data from a PostgreSQL database.
Install Meilisearch
Option A: Official install script (quickest)
The official script downloads the correct binary for your architecture and places it in /usr/local/bin.
curl -L https://install.meilisearch.com | sh
The script works on amd64 and arm64. Inspect it before piping to shell if that is your policy — the source is on GitHub under meilisearch/meilisearch.
Option B: Download a specific release directly
MEILI_VERSION=v1.8.3
curl -OL "https://github.com/meilisearch/meilisearch/releases/download/${MEILI_VERSION}/meilisearch-linux-amd64"
chmod +x meilisearch-linux-amd64
sudo mv meilisearch-linux-amd64 /usr/local/bin/meilisearch
Check the releases page for the latest version tag before running this.
Option C: Package managers
Meilisearch is available via APT on Debian/Ubuntu:
curl -fsSL https://apt.fury.io/meilisearch/gpg.key | sudo gpg --dearmor -o /usr/share/keyrings/meilisearch.gpg
echo "deb [signed-by=/usr/share/keyrings/meilisearch.gpg] https://apt.fury.io/meilisearch/ * *" | sudo tee /etc/apt/sources.list.d/meilisearch.list
sudo apt update && sudo apt install meilisearch
On Arch Linux, install from the AUR:
paru -S meilisearch
Fedora and RHEL users should use Option A or B; no official RPM repository exists at time of writing.
Create a Dedicated System User
Never run Meilisearch as root. Create a service account and a data directory.
sudo useradd --system --no-create-home --shell /usr/sbin/nologin meilisearch
sudo mkdir -p /var/lib/meilisearch/data /var/lib/meilisearch/dumps /var/lib/meilisearch/snapshots
sudo chown -R meilisearch:meilisearch /var/lib/meilisearch
Set the Master Key
The master key is the root credential for your Meilisearch instance. All API keys are derived from it. Without it, the API is wide open on localhost, which is acceptable only for local development. In production you must set one.
Generate a strong random key:
openssl rand -hex 32
The output will look like: a1b2c3d4...64hex chars... — save it securely, for example in a secrets manager or password vault. You will not be able to retrieve it later.
Store it in an environment file that only root and the service account can read:
sudo bash -c 'cat > /etc/meilisearch.env <
Setting MEILI_ENV=production disables the interactive search preview UI and enforces master key authentication on all routes.
Create a systemd Service
sudo tee /etc/systemd/system/meilisearch.service > /dev/null <
sudo systemctl daemon-reload
sudo systemctl enable --now meilisearch
Verify it is running
systemctl status meilisearch
You should see active (running). Then confirm the API responds:
curl -s http://127.0.0.1:7700/health
Expected output: {"status":"available"}
Create an Index and Add Documents
Meilisearch creates an index automatically when you first push documents to it. Replace MASTER_KEY with your actual key in all subsequent requests, or create a scoped API key (recommended for application use).
curl -s -X POST 'http://127.0.0.1:7700/indexes/products/documents' \
-H 'Authorization: Bearer MASTER_KEY' \
-H 'Content-Type: application/json' \
--data-binary '[
{"id": 1, "name": "Mechanical Keyboard", "category": "peripherals", "price": 129.99},
{"id": 2, "name": "USB-C Hub", "category": "peripherals", "price": 39.99},
{"id": 3, "name": "27-inch Monitor", "category": "displays", "price": 349.00}
]'
Meilisearch processes document additions asynchronously. The response contains a taskUid. Check task status:
curl -s 'http://127.0.0.1:7700/tasks/0' \
-H 'Authorization: Bearer MASTER_KEY'
When "status":"succeeded" appears, the documents are indexed and searchable.
Run a search
curl -s -X POST 'http://127.0.0.1:7700/indexes/products/search' \
-H 'Authorization: Bearer MASTER_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{"q": "keybord"}'
Notice the deliberate typo — Meilisearch still returns the keyboard result. That is typo tolerance working by default.
Configure Typo Tolerance
Meilisearch enables typo tolerance automatically, but you can tune it per index. The key settings are the minimum word length for allowing one or two typos, and which fields to exclude.
curl -s -X PATCH 'http://127.0.0.1:7700/indexes/products/settings/typo-tolerance' \
-H 'Authorization: Bearer MASTER_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{
"enabled": true,
"minWordSizeForTypos": {
"oneTypo": 4,
"twoTypos": 8
},
"disableOnAttributes": ["price"]
}'
This means words with 4+ characters can have one typo, words with 8+ characters can have two typos, and the price field is never matched by typo. After sending this PATCH, poll the returned task until it succeeds before testing.
To disable typo tolerance entirely on an index (useful for SKU or ID fields):
curl -s -X PATCH 'http://127.0.0.1:7700/indexes/skus/settings/typo-tolerance' \
-H 'Authorization: Bearer MASTER_KEY' \
-H 'Content-Type: application/json' \
--data-binary '{"enabled": false}'
Sync Data from PostgreSQL
For production workloads, you need a reliable way to keep Meilisearch in sync with your source-of-truth database. The two practical approaches are a polling script or a trigger-based approach using pg_notify. The polling approach is simpler and shown here.
Install dependencies
On Debian/Ubuntu:
sudo apt install python3-pip python3-venv
python3 -m venv /opt/meili-sync
/opt/meili-sync/bin/pip install psycopg2-binary meilisearch requests
On Fedora/RHEL:
sudo dnf install python3-pip
python3 -m venv /opt/meili-sync
/opt/meili-sync/bin/pip install psycopg2-binary meilisearch requests
Write the sync script
sudo tee /opt/meili-sync/sync.py > /dev/null <<'PYEOF'
import psycopg2
import meilisearch
import os
PG_DSN = os.environ["PG_DSN"] # e.g. "host=localhost dbname=myapp user=app password=secret"
MEILI_URL = os.environ.get("MEILI_URL", "http://127.0.0.1:7700")
MEILI_KEY = os.environ["MEILI_MASTER_KEY"]
INDEX_NAME = "products"
client = meilisearch.Client(MEILI_URL, MEILI_KEY)
index = client.index(INDEX_NAME)
with psycopg2.connect(PG_DSN) as conn:
with conn.cursor() as cur:
cur.execute("SELECT id, name, category, price FROM products")
rows = cur.fetchall()
columns = [desc[0] for desc in cur.description]
docs = [dict(zip(columns, row)) for row in rows]
task = index.add_documents(docs, primary_key="id")
print(f"Enqueued task {task.task_uid} with {len(docs)} documents")
PYEOF
This performs a full re-index on each run. For large tables, add a WHERE updated_at > last_sync_ts clause and track the high-water mark in a small state file or a dedicated Postgres table.
Schedule the sync with a systemd timer
sudo tee /etc/systemd/system/meili-sync.service > /dev/null < /dev/null <
sudo systemctl daemon-reload
sudo systemctl enable --now meili-sync.timer
systemctl list-timers meili-sync.timer
Firewall and Reverse Proxy Notes
Meilisearch is bound to 127.0.0.1:7700 by this configuration — it is not exposed to the internet. Put it behind nginx or Caddy and terminate TLS there. If you need to allow traffic through a firewall for the proxy:
On Ubuntu with ufw:
sudo ufw allow 443/tcp
On Fedora/RHEL with firewalld:
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
Never open port 7700 to the internet directly.
Troubleshooting
- Service fails to start: Check
journalctl -u meilisearch -n 50. A common cause is a missing or malformed/etc/meilisearch.envfile or wrong permissions on/var/lib/meilisearch. - 401 Unauthorized errors: You are in
productionmode but theAuthorization: Bearerheader is missing or the key is wrong. Verify withgrep MEILI_MASTER_KEY /etc/meilisearch.env. - Slow indexing: Full re-indexes on millions of rows will take time. Use incremental syncs via
updated_atfiltering. Meilisearch also uses significant RAM during indexing — plan for at least 1 GB available. - Search returns no results after sync: The Postgres sync task may still be processing. Poll
/tasks/{taskUid}untilsucceeded. - Binary not found after install script: The script places the binary in
./meilisearchin the current directory, not/usr/local/bin. Move it manually:sudo mv ./meilisearch /usr/local/bin/.
Frequently asked questions
- Can I run Meilisearch without a master key?
- Yes, but only for local development. Without a master key the API accepts all requests unauthenticated. Setting MEILI_ENV=production without a master key causes Meilisearch to refuse to start, which enforces secure configuration.
- How much RAM does Meilisearch need?
- For small indexes (under 100k documents) 512 MB is workable, but plan for at least 1 GB during active indexing. Meilisearch maps index files into memory, so available RAM directly affects indexing and search speed.
- How do I keep Meilisearch in sync with Postgres without polling?
- Use PostgreSQL logical replication or LISTEN/NOTIFY with a long-running process. Libraries like pglogical or tools like Debezium can stream row-level changes, letting you update only the affected documents rather than re-indexing everything.
- What is the difference between the master key and API keys?
- The master key is the root credential that can do anything, including managing API keys. You should never expose it to application code. Instead, create scoped API keys via POST /keys with specific index and action permissions, and use those in your application.
- Can Meilisearch handle multiple indexes and should I use one per Postgres table?
- Yes, Meilisearch supports multiple indexes and each has independent settings. One index per logical search domain (not necessarily per table) is a reasonable approach. Join related tables in your sync query rather than creating a separate index for every table.
Related guides
Configure Prometheus Alertmanager
Configure Prometheus Alertmanager with routing trees, receivers, inhibition rules, grouping, Go templates, and PagerDuty/Slack on-call integrations.
Build an Intranet Server on Linux
Set up a complete small-office intranet on one Linux box: Nginx web server, dnsmasq local DNS, Samba file sharing, and a Wiki.js team wiki.
Build an nftables Firewall Script
Build a complete nftables firewall from scratch: tables, chains, sets, default-deny input policy, service allowlisting, and persistent systemd configuration.
Caddy as a Reverse Proxy
Set up Caddy as a reverse proxy with automatic HTTPS, load balancing, WebSocket passthrough, reusable snippets, and header control — no certbot required.