Use case · Search engine

Meilisearch self-hosted on a dedicated server: sizing, install, keys and upgrades

Self-hosting Meilisearch means running one binary, or the official Docker image, with MEILI_ENV=production and a master key of at least 16 bytes, listening on 127.0.0.1:7700 behind a TLS reverse proxy. Everything else is disk and memory: the index lives in LMDB, a memory-mapped store, so search speed follows how much of the database the page cache holds. Below: sizing from the official documentation, an install that never pipes a script into a shell, and the snapshot, dump and upgrade routine.

See the machines

What Meilisearch needs from the machine

Meilisearch is a single process with an HTTP API. The documentation lists Linux on amd64 or aarch64 with glibc 2.35 or newer as officially supported; check yours with ldd --version. By default it listens on localhost:7700. The official guidance on resources, with the scope each figure carries:

ResourceWhat the documentation saysScope
Disk spaceAt least ten times the disk space of the raw datasetFAQ, given as a starting point
Disk typeA low-latency disk such as an NVMe SSD gives better results than HDD, NFS or other network-mounted storageStorage internals page
RAMBest when the whole dataset fits in RAM; a RAM-to-disk ratio around 1/3 does not materially impact performance, and about 1/10 works for many workloadsRAM against database size on disk; use-case dependent
Indexing memoryAt most two thirds of available RAM by default, set with --max-indexing-memoryIndexing only
Indexing threadsAt most half the cores by default, so searches keep being servedIndexing only
Request size100 MB per payload by default (--http-payload-size-limit)Each request

The docs give one small measured example, the movies dataset: their text calls it an 8.6 MB JSON file, their table lists 9.1 MB of raw JSON, and once indexed it took 224 MB on disk and about 305 MB of RAM. It is one example, not a ratio to scale from. The docs also say there is no reliable way to predict the final size of a database, and that LMDB keeps the space freed by deleted documents instead of returning it to the operating system, so disk use grows over time.

Why a whole machine suits it

LMDB leaves memory to the kernel. Alone on a server, the docs say, LMDB will use the maximum RAM it can; beside other programs, the OS divides memory between them. On a dedicated server no other tenant competes for that page cache, there is no hypervisor between the process and the disks, and you have root to set the data path, the temp directory and the firewall. Dumps and snapshots are copied off the machine over a port that is unmetered, so the backup schedule does not have to be traded against a transfer allowance.

Which of our machines fit

Size RAM against the indexed database, then pick the disk. Every line in the dedicated server catalogue is one physical box, rented whole, with root.

  • Site or app search with a modest index: the Xeon E3-1585L v5 cartridge with an NVMe-SSD and 16 to 64 GB, or the 16-core Xeon D-1587 with 16 to 128 GB and a 1.92 TB NVMe-SSD.
  • Two NVMe drives in Bucharest: the Xeon E-2286M line; memory runs from 32 GB up to 64 GB.
  • Large catalogues: the dual-socket Xeon Gold 6130 and E5-2630L v3 lines take up to 1 TB of memory. Choose the drives in the configurator, see the NVMe dedicated servers, or build one to spec: a build is handed over 4 to 24 hours after the order clears review.
  • ARM: the Ampere Altra Q64-22 is aarch64, which the docs support. We would skip the X-Gene cartridge: it installs only Ubuntu 16.04 and 18.04, which ship glibc 2.23 and 2.27, below the 2.35 the docs list as officially supported. That conclusion is ours, not the docs'.

Racked machines are handed over in about 30 minutes once payment is approved. Every racked single-, dual- and quad-socket server, and the m510 and m710x cartridges, has an out-of-band console, for the day a bad firewall rule cuts off SSH. IPv6 is added at checkout at no charge, as individual addresses.

Installing the binary under systemd

The production tutorial installs with curl -L https://install.meilisearch.com | sh. That runs a remote script you have not read, so this page downloads the release binary instead, as the docs' update guide does. The release lists meilisearch-enterprise-* files beside the plain ones: those are the Enterprise Edition, which may not be used freely in production. Take meilisearch-linux-amd64, or meilisearch-linux-aarch64 on ARM.

  1. Download the release binary (as the docs' update guide does). Then create the meilisearch system user and its data, dumps and snapshots directories under /var/lib/meilisearch, as the production tutorial does, plus a tmp directory of our own.
  2. Write the settings to /etc/meilisearch.env, mode 600, owned by root. The docs' tutorial puts the master key in /etc/meilisearch.toml, which a default umask leaves world-readable, and its CLI examples pass --master-key, which shows in ps and in shell history. An environment file read by systemd avoids both. The key comes from openssl rand, one of the generators the docs suggest, and is never typed.
  3. Pointing TMPDIR at the data disk is our addition: the docs say snapshots and dumps are staged in TMPDIR first, defaulting to /tmp, and a small /tmp fails with No space left on device.
  4. Turn ufw on before the first start, allowing SSH, 80 and 443 only. The rules reach IPv6 only while IPV6=yes is set in /etc/default/ufw; the grep below confirms it.
  5. Install the service file. It is the docs' unit with one change of ours: EnvironmentFile=/etc/meilisearch.env replaces --config-file-path /etc/meilisearch.toml on the ExecStart line. Then put Nginx in front. Point the hostname's DNS at the server before certbot asks for a certificate.
ldd --version | head -n1        # glibc 2.35 or newer
VER=v1.55.0                     # pick the current tag on the releases page
curl -fL --output meilisearch \
  "https://github.com/meilisearch/meilisearch/releases/download/$VER/meilisearch-linux-amd64"
sudo install -m 755 meilisearch /usr/local/bin/meilisearch
sudo useradd -d /var/lib/meilisearch -s /bin/false -m -r meilisearch
sudo mkdir -p /var/lib/meilisearch/data /var/lib/meilisearch/dumps \
  /var/lib/meilisearch/snapshots /var/lib/meilisearch/tmp
sudo chown -R meilisearch:meilisearch /var/lib/meilisearch
sudo chmod 750 /var/lib/meilisearch
sudo sh -c 'umask 077; cat > /etc/meilisearch.env' <<EOF
MEILI_ENV=production
MEILI_MASTER_KEY=$(openssl rand -hex 32)
MEILI_HTTP_ADDR=127.0.0.1:7700
MEILI_DB_PATH=/var/lib/meilisearch/data
MEILI_DUMP_DIR=/var/lib/meilisearch/dumps
MEILI_SNAPSHOT_DIR=/var/lib/meilisearch/snapshots
MEILI_SCHEDULE_SNAPSHOT=86400
MEILI_NO_ANALYTICS=true
TMPDIR=/var/lib/meilisearch/tmp
EOF
sudo chmod 600 /etc/meilisearch.env
sudo tee /etc/systemd/system/meilisearch.service >/dev/null <<'EOF'
[Unit]
Description=Meilisearch
After=systemd-user-sessions.service

[Service]
Type=simple
WorkingDirectory=/var/lib/meilisearch
EnvironmentFile=/etc/meilisearch.env
ExecStart=/usr/local/bin/meilisearch
User=meilisearch
Group=meilisearch
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF
grep '^IPV6=' /etc/default/ufw     # must read IPV6=yes
sudo ufw allow OpenSSH && sudo ufw allow 80,443/tcp && sudo ufw enable
sudo systemctl daemon-reload && sudo systemctl enable --now meilisearch
curl -s http://127.0.0.1:7700/health
sudo apt install -y nginx certbot python3-certbot-nginx
sudo tee /etc/nginx/sites-available/meilisearch >/dev/null <<'EOF'
server {
    listen 80;
    listen [::]:80;
    server_name search.example.com;
    client_max_body_size 100m;
    location / { proxy_pass http://127.0.0.1:7700; }
}
EOF
sudo ln -s /etc/nginx/sites-available/meilisearch /etc/nginx/sites-enabled/
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d search.example.com

Meilisearch never listens on a public address here: only Nginx does, on 80 and 443. client_max_body_size roughly matches Meilisearch's 100 MB payload limit, because Nginx refuses bodies over 1 MB by default and document batches would fail at the proxy. If the only client is an application on the same machine, the docs say the proxy can be skipped; keep the loopback bind either way.

The Docker route

The official image is getmeili/meilisearch. The docs advise against the latest tag, since machines set up at different times end up on different releases, so pin a version. Publish the port on loopback only: a bare -p 7700:7700 publishes on all of the host's addresses and bypasses ufw's rules, so bind it to 127.0.0.1. A named volume keeps /meili_data across restarts; the docs say mounting a host directory might result in performance loss and recommend it only for development. Settings go in through --env-file, so the key stays off the command line, though anyone who can run docker inspect can read it.

sudo install -d -m 700 /etc/meilisearch
sudo sh -c 'umask 077; cat > /etc/meilisearch/docker.env' <<EOF
MEILI_ENV=production
MEILI_MASTER_KEY=$(openssl rand -hex 32)
MEILI_SCHEDULE_SNAPSHOT=86400
MEILI_SNAPSHOT_DIR=/meili_data/snapshots
MEILI_NO_ANALYTICS=true
EOF
sudo docker run -d --name meilisearch --restart unless-stopped \
  -p 127.0.0.1:7700:7700 \
  --env-file /etc/meilisearch/docker.env \
  -v meili_data:/meili_data \
  getmeili/meilisearch:v1.55.0

Master key, admin key, search key

With a master key set, every route except GET /health needs a key, and Meilisearch creates four defaults: the Default Search API Key (search only, all indexes, safe in a browser), the Default Admin API Key (everything except key management; server side only), a read-only admin key and a chat key. The docs are blunt: use the master key only to manage API keys, never for searches or indexing.

List the keys without the master key appearing in any argument list: sudo sed -n 's/^MEILI_MASTER_KEY=/Authorization: Bearer /p' /etc/meilisearch.env | curl -s -H @- http://127.0.0.1:7700/keys. For a public search box, create a key limited to "actions": ["search"] on the one index it serves, with an expiresAt date, and rotate it. Changing the master key invalidates every API key, so plan that as a coordinated change.

Snapshots, dumps and upgrades

SnapshotDump
What it isExact copy of the databasePortable export of indexes, documents, settings and tasks
Restore speedFast, already indexedSlow: every document is indexed again
VersionsSame Meilisearch version onlyOlder into newer; never newer into older
How it is madeScheduled at launch, or POST /snapshotsPOST /dumps, on demand

MEILI_SCHEDULE_SNAPSHOT=86400 takes one a day, the starting point the backup guide suggests, and each new snapshot overwrites the last, so copy it off the machine before the next one lands. The configuration reference says storing snapshots in S3 directly requires the Enterprise Edition. Dumps leave out API key values, which depend on the master key.

  1. Take a snapshot with POST /snapshots and wait for its task to succeed: the docs say upgrades are not atomic and can corrupt a database.
  2. sudo systemctl stop meilisearch, then install the new release binary as above.
  3. Add MEILI_UPGRADE_DB=true to the environment file and start the service. If the release you are moving to is older than v1.51, the option has its old name: MEILI_EXPERIMENTAL_DUMPLESS_UPGRADE=true (flag --experimental-dumpless-upgrade).
  4. Follow the UpgradeDatabase task under /tasks; searches keep working while it runs. Remove the line once it has succeeded.
  5. A database created before v1.12 cannot be upgraded in place: run the old version, create a dump, move the old data directory aside (an import refuses to start over an existing database), then start the new version with --import-dump.

Only the latest release is maintained, with no LTS line, and new versions arrive four to six times a year, patch releases not counted. Treat upgrades as routine work, not a yearly event.

Licence, telemetry and honest limits

The Community Edition is free under the MIT licence and carries full-text and AI-powered search. The Enterprise Edition is under a BUSL licence and cannot be used freely in production; its binaries are published for testing, and production use goes through Meilisearch's sales team. The editions page names sharding as the only Enterprise-only feature; the sharding guide says replication and sharding both need it.

Telemetry is on unless you opt out. It sends anonymised system, performance and usage metrics; the docs say it never collects personal information such as IP addresses, and never stores data from your documents. MEILI_NO_ANALYTICS=true in the environment file, or --no-analytics, turns it off.

One Community Edition server is one copy of the index, so recovery means restoring a snapshot, not failing over. The docs recommend keeping each index under 2 TiB. Meilisearch is built for instant search inside an app or a site; for log retention and analytics, see self-hosted OpenSearch, and keep the source of truth in a database such as PostgreSQL on its own server.

Questions

How much RAM does a self-hosted Meilisearch need?

The documentation gives a ratio rather than a figure: it works best when the whole database fits in memory, a RAM-to-disk ratio of about 1/3 does not materially impact performance, and about 1/10 is enough for many workloads. Measure the database size after indexing your own data, because the docs say it cannot be predicted reliably. Indexing uses at most two thirds of RAM by default.

Can I start Meilisearch in production without a master key?

No. With MEILI_ENV=production it refuses to launch if the master key is missing or shorter than 16 bytes, and suggests a generated one instead. In development mode a missing key leaves every route open to anyone who can reach the port.

Is there a web dashboard when it is self-hosted?

The built-in search preview at the instance's address answers plain searches only, and production mode turns it off. Use the API or an SDK for administration, through an SSH tunnel if you want a browser on the loopback port during development.

Do I need a dump for every upgrade?

Not any more. Recent releases upgrade the database in place when started with --upgrade-db (--experimental-dumpless-upgrade when the target release is older than v1.51), after you take a snapshot. A dump is needed only for a database created before v1.12.

Why not use the one-line installer?

It pipes a remote script into a shell, so whatever that URL serves runs on your server unread. The GitHub release publishes the binary itself, which the docs' own update guide downloads with curl, and you can pin the exact version you tested.

Pick the machine for the index

Size memory against the indexed database, prefer NVMe, and configure the line in the catalogue.

See the machines

Related on Server Room

Other use cases