Meilisearch es un motor de búsqueda de código abierto escrito en Rust que ofrece resultados mientras el usuario escribe, tolerancia a errores tipográficos y una API REST muy sencilla. Funciona sin apenas configuración, pero en producción necesita una clave maestra, un usuario propio y un proxy con HTTPS delante. En este tutorial instalarás Meilisearch en Ubuntu 24.04 como servicio systemd, indexarás documentos, configurarás filtros, ordenación y sinónimos, crearás una clave de solo búsqueda y publicarás la API con Nginx y Let's Encrypt.
Requisitos previos
Para seguir esta guía necesitas:
- Un servidor con Ubuntu 24.04 LTS de 64 bits (x86_64 o arm64), por ejemplo un VPS de CubePath.
- Un usuario no root con privilegios
sudo. - Al menos 1 GB de RAM. El espacio en disco que ocupa el índice suele ser varias veces el tamaño de los datos originales.
- Para el paso 8: un dominio o subdominio (en esta guía,
search.your_domain) con un registro DNS A apuntando a la IP del servidor.
Paso 1: Instalar el binario de Meilisearch
Meilisearch se distribuye como un único binario en las versiones publicadas en GitHub. Consulta la última versión estable en la página de releases y descárgala; esta guía usa la 1.54.0:
MEILI_VERSION=1.54.0
cd /tmp
curl -fL -o meilisearch "https://github.com/meilisearch/meilisearch/releases/download/v${MEILI_VERSION}/meilisearch-linux-amd64"
sudo install -m 0755 meilisearch /usr/local/bin/meilisearch
En servidores arm64, descarga meilisearch-linux-aarch64 en lugar de meilisearch-linux-amd64.
Comprueba la instalación:
meilisearch --version
meilisearch 1.54.0
Paso 2: Crear el usuario, los directorios y la configuración
Ejecuta Meilisearch con un usuario de sistema sin shell, propietario solo de sus datos:
sudo useradd --system --home-dir /var/lib/meilisearch --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
sudo chmod 750 /var/lib/meilisearch
En modo producción, Meilisearch exige una clave maestra de al menos 16 bytes. Genera una aleatoria y guárdala en un lugar seguro:
openssl rand -base64 32
Crea el fichero de configuración:
sudo nano /etc/meilisearch.toml
Pega el siguiente contenido y sustituye your_master_key por la clave que acabas de generar:
env = "production"
master_key = "your_master_key"
http_addr = "127.0.0.1:7700"
db_path = "/var/lib/meilisearch/data"
dump_dir = "/var/lib/meilisearch/dumps"
snapshot_dir = "/var/lib/meilisearch/snapshots"
no_analytics = true
http_addr en 127.0.0.1 hace que Meilisearch solo acepte conexiones locales; Nginx lo publicará en el paso 8. no_analytics desactiva la telemetría. Como el fichero contiene la clave maestra, restringe sus permisos:
sudo chown root:meilisearch /etc/meilisearch.toml
sudo chmod 640 /etc/meilisearch.toml
Paso 3: Crear el servicio systemd
Crea la unidad del servicio:
sudo nano /etc/systemd/system/meilisearch.service
[Unit]
Description=Meilisearch
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=meilisearch
Group=meilisearch
WorkingDirectory=/var/lib/meilisearch
ExecStart=/usr/local/bin/meilisearch --config-file-path /etc/meilisearch.toml
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Recarga systemd, habilita el servicio y arráncalo:
sudo systemctl daemon-reload
sudo systemctl enable --now meilisearch
Comprueba que está activo y que responde. El endpoint /health es el único que no necesita clave:
systemctl status meilisearch --no-pager
curl http://127.0.0.1:7700/health
{"status":"available"}
Los logs del servicio se consultan con journalctl:
sudo journalctl -u meilisearch -n 20 --no-pager
Paso 4: Obtener las claves de API
La clave maestra solo debe usarse para gestionar claves. Al arrancar con clave maestra, Meilisearch crea dos claves por defecto: una de administración para el backend y otra de solo búsqueda. Guarda la clave maestra en una variable e instala jq para leer las respuestas:
sudo apt install -y jq
export MEILI_MASTER_KEY='your_master_key'
Lista las claves:
curl -s http://127.0.0.1:7700/keys \
-H "Authorization: Bearer ${MEILI_MASTER_KEY}" \
| jq '.results[] | {name, key}'
{
"name": "Default Search API Key",
"key": "d0552b41536279a0ad88bd595327b96f01176a60c2243e906c52ac02375f9bc4"
}
{
"name": "Default Admin API Key",
"key": "380689dd379232519a54d15935750cc7625620a2ea2fc06907cb40ba5b421b6f"
}
Guarda la clave de administración en otra variable. La usarás para todas las operaciones siguientes:
export MEILI_ADMIN_KEY=$(curl -s http://127.0.0.1:7700/keys \
-H "Authorization: Bearer ${MEILI_MASTER_KEY}" \
| jq -r '.results[] | select(.name == "Default Admin API Key") | .key')
Paso 5: Indexar documentos
Meilisearch crea el índice automáticamente al recibir los primeros documentos. Cada documento necesita un identificador único; aquí se indica de forma explícita con primaryKey=id. Crea un fichero de ejemplo:
nano ~/productos.json
[
{"id": 1, "nombre": "Camiseta básica de algodón", "descripcion": "Camiseta unisex de algodón 100 %", "categoria": "Ropa", "precio": 19.99, "stock": 150},
{"id": 2, "nombre": "Zapatillas de running", "descripcion": "Zapatillas ligeras con amortiguación", "categoria": "Calzado", "precio": 89.99, "stock": 45},
{"id": 3, "nombre": "Pantalón vaquero slim", "descripcion": "Vaquero ajustado de corte moderno", "categoria": "Ropa", "precio": 49.99, "stock": 80},
{"id": 4, "nombre": "Zapatillas de montaña", "descripcion": "Calzado impermeable para trail", "categoria": "Calzado", "precio": 119.00, "stock": 20}
]
Envía los documentos al índice productos:
curl -s -X POST "http://127.0.0.1:7700/indexes/productos/documents?primaryKey=id" \
-H "Authorization: Bearer ${MEILI_ADMIN_KEY}" \
-H "Content-Type: application/json" \
--data-binary @"$HOME/productos.json" | jq
{
"taskUid": 0,
"indexUid": "productos",
"status": "enqueued",
"type": "documentAdditionOrUpdate",
"enqueuedAt": "2026-09-25T10:12:31.123456Z"
}
Las escrituras en Meilisearch son asíncronas: la respuesta solo confirma que la tarea está en cola. Comprueba su resultado con el taskUid devuelto:
curl -s http://127.0.0.1:7700/tasks/0 \
-H "Authorization: Bearer ${MEILI_ADMIN_KEY}" | jq '.status, .details'
"succeeded"
{
"receivedDocuments": 4,
"indexedDocuments": 4
}
Si el estado es failed, el campo error de la tarea explica el motivo. Para modificar solo algunos campos de documentos existentes, envíalos con PUT al mismo endpoint en lugar de POST; los campos que no incluyas se conservan.
Paso 6: Configurar filtros, ordenación y sinónimos
Por defecto, todos los campos son buscables, pero ninguno se puede usar para filtrar u ordenar. Declara qué campos cumplen cada función y añade sinónimos propios de tu catálogo. El orden de searchableAttributes también define la relevancia: una coincidencia en nombre pesa más que en descripcion:
curl -s -X PATCH http://127.0.0.1:7700/indexes/productos/settings \
-H "Authorization: Bearer ${MEILI_ADMIN_KEY}" \
-H "Content-Type: application/json" \
-d '{
"searchableAttributes": ["nombre", "descripcion", "categoria"],
"filterableAttributes": ["categoria", "precio"],
"sortableAttributes": ["precio", "stock"],
"synonyms": {
"zapatillas": ["deportivas", "tenis"],
"deportivas": ["zapatillas"],
"tenis": ["zapatillas"]
}
}' | jq '.taskUid'
Cambiar los ajustes reindexa los documentos, así que también es una tarea asíncrona. Con pocos documentos termina en un instante; puedes comprobarlo con /tasks/<taskUid> como antes.
Ahora prueba una búsqueda por un sinónimo, combinada con un filtro, un orden y facetas:
curl -s -X POST http://127.0.0.1:7700/indexes/productos/search \
-H "Authorization: Bearer ${MEILI_ADMIN_KEY}" \
-H "Content-Type: application/json" \
-d '{
"q": "deportivas",
"filter": "precio < 100",
"sort": ["precio:asc"],
"facets": ["categoria"]
}' | jq '[.hits[] | {nombre, precio}], .facetDistribution'
[
{
"nombre": "Zapatillas de running",
"precio": 89.99
}
]
{
"categoria": {
"Calzado": 1
}
}
Ningún documento contiene la palabra deportivas, pero el sinónimo la relaciona con zapatillas, y el filtro descarta las zapatillas de montaña por precio. Prueba también a buscar zapatilas: la tolerancia a errores tipográficos devuelve las mismas zapatillas sin configurar nada. facetDistribution devuelve los conteos por categoría, que puedes usar para construir filtros en la interfaz.
Paso 7: Crear una clave de solo búsqueda
La clave de búsqueda por defecto sirve para todos los índices. Para un frontend público es mejor una clave limitada a un índice concreto:
curl -s -X POST http://127.0.0.1:7700/keys \
-H "Authorization: Bearer ${MEILI_MASTER_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "Frontend productos",
"description": "Búsqueda pública en el índice productos",
"actions": ["search"],
"indexes": ["productos"],
"expiresAt": null
}' | jq -r '.key'
El comando imprime la clave nueva. Comprueba que puede buscar pero no leer los ajustes del índice, sustituyendo your_search_key por su valor:
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:7700/indexes/productos/settings \
-H "Authorization: Bearer your_search_key"
403
Paso 8: Publicar la API con Nginx y HTTPS
Instala Nginx y Certbot:
sudo apt install -y nginx certbot python3-certbot-nginx
Crea un sitio para el subdominio de búsqueda. client_max_body_size permite enviar lotes de documentos grandes a través del proxy; el límite por defecto de Nginx es de solo 1 MB:
sudo nano /etc/nginx/sites-available/meilisearch
server {
listen 80;
server_name search.your_domain;
client_max_body_size 100M;
location / {
proxy_pass http://127.0.0.1:7700;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Actívalo, comprueba la sintaxis y recarga Nginx:
sudo ln -s /etc/nginx/sites-available/meilisearch /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Si usas UFW, permite HTTP y HTTPS (y SSH, antes de activarlo):
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
Obtén el certificado de Let's Encrypt:
sudo certbot --nginx -d search.your_domain
Comprueba el acceso desde fuera del servidor:
curl https://search.your_domain/health
{"status":"available"}
No hace falta bloquear rutas de administración en Nginx: sin una clave válida, Meilisearch rechaza cualquier petición distinta de /health.
Paso 9: Hacer copias de seguridad con dumps
Un dump exporta documentos, ajustes y claves en un formato que se puede importar en la misma versión o en una posterior de Meilisearch, lo que lo hace útil tanto para copias de seguridad como para actualizar de versión. Lanza uno:
curl -s -X POST http://127.0.0.1:7700/dumps \
-H "Authorization: Bearer ${MEILI_ADMIN_KEY}" | jq '.taskUid'
Cuando la tarea termine, el fichero aparecerá en el directorio configurado:
sudo ls /var/lib/meilisearch/dumps/
20260925-101845123.dump
Copia esos ficheros fuera del servidor con tu sistema de backups habitual. Para restaurar un dump, arranca Meilisearch con una base de datos vacía y la opción --import-dump /ruta/al/fichero.dump.
Solución de problemas
El servicio no arranca. Revisa el journal:
sudo journalctl -u meilisearch -n 50 --no-pager
Si el error indica que la clave maestra es demasiado corta, genera otra de al menos 16 bytes. Si indica problemas de permisos en /var/lib/meilisearch, vuelve a ejecutar el chown del paso 2.
The provided API key is invalid o missing_authorization_header. La cabecera debe ser Authorization: Bearer <clave>. Comprueba que la variable no está vacía en la sesión actual con echo "$MEILI_ADMIN_KEY".
Attribute precio is not filterable. El campo no está en filterableAttributes, o la tarea de ajustes del paso 6 aún no ha terminado o ha fallado. Consulta sus ajustes con GET /indexes/productos/settings.
Una importación grande devuelve 413 Request Entity Too Large. Si pasa por Nginx, sube client_max_body_size. Si viene de Meilisearch, el límite propio es de 100 MB por petición: divide el fichero en lotes más pequeños.
Conclusión
Tienes Meilisearch funcionando en Ubuntu 24.04 como servicio systemd protegido con clave maestra, con un índice configurado para filtrar, ordenar y entender sinónimos, una clave pública limitada y la API publicada con HTTPS. Como siguientes pasos, conecta tu frontend con el cliente oficial meilisearch de JavaScript o con InstantSearch, programa dumps periódicos con un temporizador de systemd y ajusta las reglas de relevancia (rankingRules) cuando tengas datos reales.
