Meilisearch 1.55.0 on AWS User Guide
Overview
This guide covers launching and using the Meilisearch 1.55.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg vector databases and RAG listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
Meilisearch is an open source search engine with a REST API. It returns relevant, typo tolerant results in milliseconds and supports filters, facets, sorting, synonyms, multi language tokenization, and hybrid search that combines keyword relevance with vector similarity from an embedder you configure (OpenAI, Hugging Face, Ollama or any REST service). Applications send documents to an index and query it over HTTPS; official client libraries exist for most languages.
The cloudimg image ships it the secure way:
- Keys unique to your instance. First boot generates a 64 character master key on the instance and writes it, with the Default Admin API Key and Default Search API Key that Meilisearch derives from it, to
/root/meilisearch-credentials.txt(root only). No key is baked into the image. - Production mode. Meilisearch refuses to start without the master key, every data route needs a key, and the search preview page is off. The service also refuses to start if its configuration ever loses the key or switches to development mode.
- HTTPS from the first boot. nginx serves the API on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS. Meilisearch itself listens on
127.0.0.1:7700only. - Closed until first boot, and fail closed. Meilisearch cannot start before first boot has written its key, and nginx ships switched off. First boot opens the public listener only after Meilisearch works on loopback and an authenticated check passes; if anything fails, the listener is closed again.
- Hardened defaults. Anonymous analytics are off. Meilisearch runs as its own unprivileged account in a systemd sandbox that can write only to its data directory. Meilisearch and nginx are blocked from the instance metadata service, and Meilisearch's own outbound policy (which refuses private and link-local addresses for embedders and webhooks) is kept. The AMI requires IMDSv2. Requests for any host name the instance does not serve are dropped.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: Meilisearch 1.55.0 Community Edition (the official meilisearch-linux-amd64 release binary, checksum pinned) at /usr/local/bin/meilisearch, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. The database (data.ms), dumps and snapshots live under /var/lib/meilisearch. No database, index or key ships in the image.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
- A security group that allows TCP 22 (SSH), TCP 443 (the Meilisearch API) and TCP 80 (redirects to HTTPS) from your own networks only. Never open 7700: Meilisearch listens on the instance's loopback address only.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Meilisearch 1.55.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (Meilisearch uses up to two thirds of the instance's memory while indexing, so size up for large datasets), your key pair and a security group as above. Size the root EBS volume for your indexes: they live under /var/lib/meilisearch on the root volume, which you can grow later.
With the AWS CLI (replace the values in angle brackets):
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <your-security-group> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--block-device-mappings 'DeviceName=/dev/sda1,Ebs={VolumeSize=30,VolumeType=gp3}' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=meilisearch}]'
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| Meilisearch 1.55.0 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot generates the certificate and the master key, starts Meilisearch, opens nginx and runs an authenticated check before it finishes (well under a minute):
systemctl is-active meilisearch nginx meilisearch-firstboot
sudo test -f /var/lib/cloudimg/meilisearch-firstboot.done && echo FIRSTBOOT_DONE
active
active
active
FIRSTBOOT_DONE
Only SSH and nginx listen on public addresses; Meilisearch listens on loopback:
sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v '127.0.0.5' | sort
0.0.0.0:22 users:(("sshd",...))
0.0.0.0:443 users:(("nginx",...))
0.0.0.0:80 users:(("nginx",...))
127.0.0.1:7700 users:(("meilisearch",...))
[::]:22 users:(("sshd",...))
[::]:443 users:(("nginx",...))
[::]:80 users:(("nginx",...))
Step 4: Get your URL and keys
sudo grep -E '^MEILI_' /root/meilisearch-credentials.txt
The file is readable by root only:
MEILI_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
MEILI_MASTER_KEY=<MEILI_MASTER_KEY>
MEILI_ADMIN_API_KEY=<MEILI_ADMIN_API_KEY>
MEILI_SEARCH_API_KEY=<MEILI_SEARCH_API_KEY>
MEILI_URLis the API endpoint. It uses the instance's public DNS name from EC2 instance metadata (the public IP address when the subnet has no public DNS names); the certificate also names the public and private IP addresses. To serve Meilisearch under your own DNS name, see Step 9.MEILI_MASTER_KEYis the root key: every API key is derived from it. Keep it on the server.MEILI_ADMIN_API_KEY(the Default Admin API Key) manages indexes, documents, settings and tasks. Use it from your backend only.MEILI_SEARCH_API_KEY(the Default Search API Key) can only search. It is the one to use from a web or mobile front end.
Send a key in the Authorization: Bearer <key> header. The examples below read the key from the credentials file and hand it to curl on standard input (-H @-), so it never appears on a command line or in your shell history.
Step 5: Confirm the API requires a key
C=/etc/ssl/meilisearch/meilisearch.crt; U=https://127.0.0.1
curl -s --cacert $C -o /dev/null -w 'no key -> HTTP %{http_code}\n' $U/indexes
printf 'Authorization: Bearer %s\n' wrong-key | curl -s --cacert $C -o /dev/null -w 'wrong key -> HTTP %{http_code}\n' -H @- $U/indexes
sudo sed -n 's/^MEILI_MASTER_KEY=/Authorization: Bearer /p' /root/meilisearch-credentials.txt | curl -s --cacert $C -H @- $U/version; echo
curl -s --cacert $C $U/health; echo
no key -> HTTP 401
wrong key -> HTTP 403
{"commitSha":"420ff1362801adb58c938aec89700e8f7e32e566","commitDate":"2026-10-12T08:45:13.000000000Z","pkgVersion":"1.55.0"}
{"status":"available"}
/health stays open for load balancers; it reveals nothing. From another machine, use MEILI_URL instead of https://127.0.0.1 (and -k or the instance's certificate, since it is self signed).
Step 6: Index documents and search
Add a few documents to a movies index with the admin key, make two fields filterable and one sortable, and wait for each task:
C=/etc/ssl/meilisearch/meilisearch.crt; U=https://127.0.0.1
admin() { sudo sed -n 's/^MEILI_ADMIN_API_KEY=/Authorization: Bearer /p' /root/meilisearch-credentials.txt | curl -s --cacert "$C" -H @- -H 'Content-Type: application/json' "$@"; }
wait_task() { for i in $(seq 1 30); do S=$(admin "$U/tasks/$1" | jq -r .status); [ "$S" = succeeded ] && break; sleep 1; done; echo "task $1: $S"; }
T=$(admin -X POST --data '[{"id":1,"title":"Carol","genres":["Romance","Drama"],"year":2015},{"id":2,"title":"Wonder Woman","genres":["Action","Adventure"],"year":2017},{"id":3,"title":"Life of Pi","genres":["Adventure","Drama"],"year":2012},{"id":4,"title":"Mad Max: Fury Road","genres":["Adventure","Science Fiction"],"year":2015},{"id":5,"title":"Moana","genres":["Fantasy","Action"],"year":2016}]' "$U/indexes/movies/documents?primaryKey=id" | jq -r .taskUid); wait_task "$T"
T=$(admin -X PATCH --data '{"filterableAttributes":["genres","year"],"sortableAttributes":["year"]}' "$U/indexes/movies/settings" | jq -r .taskUid); wait_task "$T"
task 0: succeeded
task 1: succeeded
Search with the search key: a query with two typos still finds the film, filters and sorting work, and the search key cannot write:
C=/etc/ssl/meilisearch/meilisearch.crt; U=https://127.0.0.1
search() { sudo sed -n 's/^MEILI_SEARCH_API_KEY=/Authorization: Bearer /p' /root/meilisearch-credentials.txt | curl -s --cacert "$C" -H @- -H 'Content-Type: application/json' "$@"; }
search -X POST --data '{"q":"wondr womn"}' "$U/indexes/movies/search" | jq -c '{hits: [.hits[] | {id, title}], estimatedTotalHits}'
search -X POST --data '{"q":"","filter":"genres = Adventure AND year >= 2015","sort":["year:desc"]}' "$U/indexes/movies/search" | jq -c '[.hits[] | {title, year}]'
search -o /dev/null -w 'search key writing a document -> HTTP %{http_code}\n' -X POST --data '[{"id":9,"title":"x"}]' "$U/indexes/movies/documents"
{"hits":[{"id":2,"title":"Wonder Woman"}],"estimatedTotalHits":1}
[{"title":"Wonder Woman","year":2017},{"title":"Mad Max: Fury Road","year":2015}]
search key writing a document -> HTTP 403
For hybrid (keyword plus vector) search, add an embedder to the index settings (embedders) that points at your embedding provider, then search with a hybrid parameter; see the Meilisearch documentation on AI-powered search. Embedder and webhook URLs must be public addresses: Meilisearch refuses private and link-local addresses by default.
Step 7: Run the built in check
The image ships an end to end check you can run at any time; first boot runs a read-only version of it. It reads the credentials file itself and talks to the API over HTTPS through nginx with the instance's certificate pinned: port 80 redirects, an unknown host name is dropped, no key is refused, a wrong key is refused, the search key cannot write or read keys, and the admin key creates a temporary index, adds documents that the search key then finds, and deletes the index:
sudo meilisearch-verify.sh
port 80 -> 301 https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ ; an unknown Host is dropped on 80 and 443 ; certificate verifies for ec2-<public-ip-dashed>.<region>.compute.amazonaws.com
production mode: / is the status JSON (no dashboard); no key 401, wrong key 403, master key 200; version 1.55.0
search key: write 403, /keys 403 ; admin key: /indexes 200
round trip: admin key created cloudimg_verify_<n> and added 3 documents, the search key found 'Wonder Woman' for 'wondr woman' and could not write, index deleted
MEILISEARCH_ROUNDTRIP_OK
Step 8: Security checks
The files that matter are locked down; the configuration file holds the master key:
sudo stat -c '%a %U:%G %n' /etc/meilisearch/config.toml /root/meilisearch-credentials.txt /var/lib/meilisearch /etc/ssl/meilisearch/meilisearch.key
640 root:meilisearch /etc/meilisearch/config.toml
600 root:root /root/meilisearch-credentials.txt
750 meilisearch:meilisearch /var/lib/meilisearch
600 root:root /etc/ssl/meilisearch/meilisearch.key
The binary is the official Community Edition release asset (its SHA-256 equals the digest GitHub publishes for meilisearch-linux-amd64 v1.55.0), running in production mode with analytics off:
sha256sum /usr/local/bin/meilisearch; /usr/local/bin/meilisearch --version
sudo grep -E '^(env|http_addr|db_path|dump_dir|snapshot_dir|no_analytics) ' /etc/meilisearch/config.toml
journalctl -u meilisearch -b --no-pager -o cat | grep -E '^(Environment|Anonymous telemetry|Package version):'
7e33f5889235f14290074a19a6d3574bd9b10fb2ee8731eabb7dafd0714b0513 /usr/local/bin/meilisearch
meilisearch 1.55.0
env = "production"
http_addr = "127.0.0.1:7700"
db_path = "/var/lib/meilisearch/data.ms"
dump_dir = "/var/lib/meilisearch/dumps"
snapshot_dir = "/var/lib/meilisearch/snapshots"
no_analytics = true
Environment: "production"
Package version: "1.55.0"
Anonymous telemetry: "Disabled"
Meilisearch runs unprivileged in a sandbox that can write only to its data directory, and neither Meilisearch nor nginx can reach the instance metadata service (so an embedder or webhook URL cannot be pointed at it):
systemctl show meilisearch -p User -p ProtectSystem -p ReadWritePaths -p NoNewPrivileges -p IPAddressDeny
systemctl show nginx -p IPAddressDeny
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
User=meilisearch
ReadWritePaths=/var/lib/meilisearch
ProtectSystem=strict
NoNewPrivileges=yes
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
nginx passes Meilisearch a fixed host name and the real client address from the connection, never values a client sends:
grep -E 'proxy_set_header (Host|X-Forwarded-For|X-Real-IP|Forwarded)' /etc/nginx/meilisearch-proxy.conf
proxy_set_header Host $meili_host;
proxy_set_header Forwarded "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
The certificate was generated on this instance and names its own addresses:
H=$(sudo sed -n 's#^MEILI_URL=https://\([^/]*\)/.*#\1#p' /root/meilisearch-credentials.txt)
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name:
DNS:ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, DNS:ip-<private-ip-dashed>.ec2.internal, IP Address:127.0.0.1, DNS:localhost
No key reached a log, the journal, shell history, backups or /etc (other than the configuration file):
sudo meilisearch-secret-leak-check.sh
LEAKCHECK_OK no master or API key in the journal, auth.log, syslog, cloud-init, nginx logs, shell history, MOTD, /var/backups, /etc (master key only in config.toml), /usr/local or /tmp
Step 9: Your own DNS name and certificate
Point a DNS record at the instance, then set the name Meilisearch is served as. This mints a new self signed certificate for the name, updates nginx's host rule and the URL in /root/meilisearch-credentials.txt, reloads nginx and keeps the name across reboots:
sudo meilisearch-set-hostname <your-domain>
Without a configured name, the instance re-reads its address from EC2 instance metadata at every boot, so a stop and start that changes the public IP address keeps the API reachable. To use a certificate from your own certificate authority instead, copy it over /etc/ssl/meilisearch/meilisearch.crt and meilisearch.key (key mode 0600) and run sudo systemctl reload nginx; running meilisearch-set-hostname again replaces it with a self signed one. A certbot client works too if you open port 80 to the certificate authority.
Step 10: API keys and the master key
Create API keys for each application with the master key; a key can be limited to some indexes, some actions and an expiry date. For example, a key that can only search the movies index:
sudo sed -n 's/^MEILI_MASTER_KEY=/Authorization: Bearer /p' /root/meilisearch-credentials.txt \
| curl -s --cacert /etc/ssl/meilisearch/meilisearch.crt -H @- -H 'Content-Type: application/json' \
-X POST --data '{"name":"movies-search","actions":["search"],"indexes":["movies"],"expiresAt":null}' \
https://127.0.0.1/keys
To generate a new master key (if it was ever exposed), run:
sudo meilisearch-rotate-master-key
It writes the new key to /etc/meilisearch/config.toml, restarts Meilisearch and rewrites /root/meilisearch-credentials.txt. Every API key value changes with the master key (Meilisearch derives them from it), so update every client afterwards; the keys' permissions and names are kept.
Step 11: Dumps and snapshots
Dumps (portable, for upgrades and migrations) and snapshots (fast, same version) are written to the data directory, /var/lib/meilisearch/dumps and /var/lib/meilisearch/snapshots. Create a dump with the admin key and copy the .dump file off the instance (it holds your documents and API key definitions, so treat it as secret):
sudo sed -n 's/^MEILI_ADMIN_API_KEY=/Authorization: Bearer /p' /root/meilisearch-credentials.txt \
| curl -s --cacert /etc/ssl/meilisearch/meilisearch.crt -H @- -X POST https://127.0.0.1/dumps
sudo ls -l /var/lib/meilisearch/dumps/
EBS snapshots of the root volume are the simplest whole-instance backup.
Step 12: Configuration
Settings live in /etc/meilisearch/config.base.toml (no key in it); /etc/meilisearch/config.toml is rendered from it plus the master key. Edit the base file (for example max_indexing_memory, http_payload_size_limit or log_level; see the Meilisearch configuration reference), then re-render with the current key and restart:
sudo nano /etc/meilisearch/config.base.toml
sudo meilisearch-set-master-key --keep
Keep env = "production": the service refuses to start in development mode, which would serve the API without authentication. Keep http_addr on 127.0.0.1 so nginx stays the only public listener. If your embedder or webhook runs on a private address in your VPC, allow that range explicitly with experimental_allowed_ip_networks (never the metadata address).
Step 13: Server components
/usr/local/bin/meilisearch --version; nginx -v 2>&1; lsb_release -ds
ls /usr/share/doc/cloudimg/meilisearch/
meilisearch 1.55.0
nginx version: nginx/1.24.0 (Ubuntu)
Ubuntu 24.04.5 LTS
LICENSE
LICENSE-MIT
VERSION
| Component | Version | Notes |
|---|---|---|
| Meilisearch | 1.55.0 Community Edition | /usr/local/bin/meilisearch, service meilisearch, data /var/lib/meilisearch |
| nginx | 1.24.0 | TLS on 443, redirect on 80, site /etc/nginx/sites-available/cloudimg-meilisearch |
| Ubuntu | 24.04 LTS | unattended security upgrades on |
Upgrading Meilisearch: create a dump, stop the service, replace the binary with the newer official meilisearch-linux-amd64 release (check its SHA-256 against the release page), and import the dump as the Meilisearch upgrade guide describes.
Troubleshooting
curl: (60) SSL certificate problem: the certificate is self signed. Pass--cacert /etc/ssl/meilisearch/meilisearch.crton the instance,-kfor a quick test elsewhere, or install your own certificate (Step 9).- The connection is closed without a response: the request used a host name the instance does not serve (nginx drops unknown names). Use
MEILI_URL, the instance's IP address, or set your name withmeilisearch-set-hostname. - HTTP 401 / 403: 401 means no
Authorizationheader; 403 means the key is wrong, expired or lacks the action or index. Aftermeilisearch-rotate-master-key, every key value has changed. - A task stays
processingwith an embedder: Meilisearch retries an unreachable embedder for a few minutes. Checkjournalctl -u meilisearchand the task'serror; private and link-local URLs are refused by design. - First boot:
journalctl -u meilisearch-firstbootshows what it did. If it failed, it closed the public listener; fix the cause and runsudo systemctl restart meilisearch-firstboot.
Security recommendations
- Restrict the security group to your own networks. Use the search key (or keys you create per index) in front ends and the admin key only in backends.
- Rotate the master key if it was ever exposed, and create expiring, narrowly scoped keys for applications.
- The build was audited against the RustSec advisory database. Two dependency advisories that upstream has not yet shipped fixes for (rustls 0.23.43 and rustls-webpki 0.103.13) concern outbound HTTPS only, which Meilisearch uses solely for embedders, webhooks and remote instances you configure; nothing connects out by default. Point those settings only at providers you trust; cloudimg publishes a new version when Meilisearch ships the fixed libraries.
Support and licensing
cloudimg provides 24/7 technical support by email and live chat: support@cloudimg.co.uk.
This image contains Meilisearch Community Edition, licensed under the MIT licence (/usr/share/doc/cloudimg/meilisearch/LICENSE-MIT). The Meilisearch repository is licensed "MIT AND BUSL-1.1": the Enterprise Edition features (BUSL-1.1) are not included in this image and are not built into its binary. Meilisearch is a trademark of Meili SAS; cloudimg is not affiliated with or endorsed by Meili SAS.