Manticore Search is a fast, lightweight, open-source database designed for full-text search, faceted filtering, vector search, and log analytics. It is written in C++ and is significantly leaner on resources than Elasticsearch, which makes it a strong fit for VPS deployments where memory headroom matters. This guide walks through deploying Manticore Search on a RamNode VPS for production use, including TLS termination, authentication, firewall hardening, and a working backup strategy.
What you will build
By the end of this guide you will have:
- A current Manticore Search daemon (
searchd) installed from the official APT repository - Real-time tables created and queried over HTTP and the MySQL wire protocol
- nginx in front of the HTTP listener with TLS from Let's Encrypt
- HTTP basic auth restricting the search endpoint to your applications
- UFW configured so only nginx and SSH are publicly reachable
- A daily backup job using
manticore-backup
RamNode plan sizing
Manticore is light. Memory usage scales primarily with index size and concurrency, not with daemon overhead.
| Workload | Suggested RamNode plan | Notes |
|---|---|---|
| Dev / staging, up to ~1M docs | Standard VPS, 2 GB RAM, 2 vCPU | Comfortable for testing real-time indexes and small corpora |
| Production, 1M to ~20M docs | Premium NVMe VPS, 4 GB RAM, 2 vCPU | Sweet spot for blog search, product catalogs, log analytics under modest QPS |
| Vector search or 50M+ docs | Premium NVMe VPS, 8 to 16 GB RAM, 4 vCPU | Vector indexes are RAM-bound; size based on embedding count and dimensions |
RamNode's NVMe plans are worth the extra cost here because Manticore's plain indexes and binlogs are I/O sensitive during heavy indexing and merges. Provision Ubuntu 24.04 LTS during checkout.
Prerequisites
- A RamNode VPS running Ubuntu 24.04 LTS
- A domain or subdomain pointed to the VPS public IP (for example
search.example.com) - SSH key access as a non-root sudo user
- Ports 80 and 443 reachable from the public internet for the ACME challenge and HTTPS traffic
Step 1: Initial server hardening
Update the base system, set the timezone, and enable unattended security updates.
sudo apt update && sudo apt upgrade -y
sudo timedatectl set-timezone UTC
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgradesConfigure UFW so only SSH stays open during installation. We will open 80/443 after nginx is in place.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH
sudo ufw enable
sudo ufw status verboseStep 2: Install Manticore Search from the official repository
Add the official APT repository and install the server, MySQL client tools for the CLI, and the columnar extension.
wget https://repo.manticoresearch.com/manticore-repo.noarch.deb
sudo dpkg -i manticore-repo.noarch.deb
sudo apt update
sudo apt install -y manticore manticore-extramanticore-extra pulls in the columnar storage library and the embeddings library, both of which you will want even if you are not using them yet. Enable and start the daemon.
sudo systemctl enable --now manticore
sudo systemctl status manticore --no-pagerConfirm searchd is listening on the three default ports.
ss -tlnp | grep -E '9306|9308|9312'You should see binds on 127.0.0.1:9306 (MySQL wire protocol), 127.0.0.1:9308 (HTTP), and 127.0.0.1:9312 (binary). Keeping these on loopback by default is intentional. The reverse proxy will expose only the HTTP endpoint, and only to authenticated clients.
Step 3: Configure Manticore
The package ships a default /etc/manticoresearch/manticore.conf. For most deployments running with real-time tables, the defaults are sane. The two settings worth tuning early are query log location and worker concurrency.
sudo nano /etc/manticoresearch/manticore.confInside the searchd block, verify or set:
searchd
{
listen = 127.0.0.1:9306:mysql
listen = 127.0.0.1:9308:http
listen = 127.0.0.1:9312
log = /var/log/manticore/searchd.log
query_log = /var/log/manticore/query.log
query_log_format = sphinxql
pid_file = /var/run/manticore/searchd.pid
data_dir = /var/lib/manticore
binlog_path = /var/lib/manticore/binlog
threads = 0
}Setting threads = 0 tells Manticore to autodetect, which on a 2 vCPU plan resolves to a small worker pool that fits the box. On a 4+ vCPU plan you can leave it at autodetect or set it explicitly to match your vCPU count.
Apply changes:
sudo systemctl restart manticore
sudo journalctl -u manticore --since "5 minutes ago" --no-pagerStep 4: Create your first real-time table
Connect using the MySQL client over the wire protocol.
mysql -h 127.0.0.1 -P 9306Create a real-time table for documents:
CREATE TABLE documents (
title text,
body text,
author string,
published_at timestamp,
tags multi
) min_infix_len='2' html_strip='1';Insert a row and run a search:
INSERT INTO documents (title, body, author, published_at, tags)
VALUES ('Hello Manticore', 'A fast lightweight search engine', 'vanessa', UNIX_TIMESTAMP(), (1,2));
SELECT id, title, weight() FROM documents WHERE MATCH('manticore');Exit the client with \q. The same query over HTTP:
curl -s -X POST http://127.0.0.1:9308/search \
-H 'Content-Type: application/json' \
-d '{"index":"documents","query":{"match":{"*":"manticore"}}}' | jqStep 5: Reverse proxy with nginx and TLS
Install nginx and certbot.
sudo apt install -y nginx certbot python3-certbot-nginxOpen HTTP and HTTPS in the firewall.
sudo ufw allow 'Nginx Full'
sudo ufw delete allow 'Nginx HTTP' 2>/dev/null || true
sudo ufw statusCreate an HTTP basic auth file. Replace appuser with your client username.
sudo apt install -y apache2-utils
sudo htpasswd -c /etc/nginx/manticore.htpasswd appuserDrop in the site config at /etc/nginx/sites-available/manticore:
upstream manticore_http {
server 127.0.0.1:9308;
keepalive 32;
}
server {
listen 80;
server_name search.example.com;
# certbot will manage this block once TLS is issued
location /.well-known/acme-challenge/ {
root /var/www/html;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name search.example.com;
# certbot will populate these
# ssl_certificate /etc/letsencrypt/live/search.example.com/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/search.example.com/privkey.pem;
client_max_body_size 64M;
# Lock down the admin-style endpoints
location ~ ^/(cli|debug) {
return 404;
}
location / {
auth_basic "Manticore Search";
auth_basic_user_file /etc/nginx/manticore.htpasswd;
proxy_pass http://manticore_http;
proxy_http_version 1.1;
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;
proxy_set_header Connection "";
proxy_read_timeout 60s;
}
}Enable the site, test, and reload.
sudo ln -s /etc/nginx/sites-available/manticore /etc/nginx/sites-enabled/manticore
sudo nginx -t
sudo systemctl reload nginxIssue the certificate.
sudo certbot --nginx -d search.example.com \
--non-interactive --agree-tos -m you@example.com --redirectCertbot will rewrite the site file with the correct ssl_certificate directives and schedule renewal via the certbot.timer systemd unit. Verify renewal will work:
sudo certbot renew --dry-runTest the public endpoint:
curl -u appuser https://search.example.com/sql -d 'query=SHOW TABLES'The /cli and /debug endpoints, which expose interactive shells and internal state, are blocked at the nginx layer. Anything else proxies through to searchd over loopback.
Step 6: Optional MySQL wire protocol access
If your application needs SphinxQL over the MySQL wire protocol from outside the box, do not expose 9306 publicly. Instead, tunnel it over SSH from the client.
ssh -L 9306:127.0.0.1:9306 you@search.example.comInside applications running on the same VPS, point at 127.0.0.1:9306 directly with no additional network exposure.
Step 7: Backups with manticore-backup
The manticore-backup utility is included with the manticore package. Create a backup target directory and a wrapper script.
sudo mkdir -p /var/backups/manticore
sudo chown manticore:manticore /var/backups/manticoreCreate /usr/local/bin/backup-manticore.sh:
#!/usr/bin/env bash
set -euo pipefail
BACKUP_DIR="/var/backups/manticore"
RETENTION_DAYS=14
manticore-backup \
--backup-dir="$BACKUP_DIR" \
--config=/etc/manticoresearch/manticore.conf \
--compress
find "$BACKUP_DIR" -type d -mtime +"$RETENTION_DAYS" -exec rm -rf {} +Make it executable and schedule it via systemd.
sudo chmod +x /usr/local/bin/backup-manticore.shCreate /etc/systemd/system/manticore-backup.service:
[Unit]
Description=Manticore Search backup
After=manticore.service
[Service]
Type=oneshot
User=manticore
ExecStart=/usr/local/bin/backup-manticore.shCreate /etc/systemd/system/manticore-backup.timer:
[Unit]
Description=Daily Manticore Search backup
[Timer]
OnCalendar=daily
RandomizedDelaySec=30m
Persistent=true
[Install]
WantedBy=timers.targetEnable and start the timer.
sudo systemctl daemon-reload
sudo systemctl enable --now manticore-backup.timer
sudo systemctl list-timers --no-pager | grep manticoreFor offsite copies, push the backup directory to an S3-compatible object store (Backblaze B2, Wasabi, etc.) using rclone from the same script, after manticore-backup completes.
Step 8: Performance and observability tips
A few defaults worth revisiting once you have traffic.
- Disk I/O. Place
/var/lib/manticoreon the NVMe volume that came with the RamNode plan. Avoid mounting the binlog directory on a network-attached volume. - Memory cap. For vector or large columnar tables, set
max_threads_per_queryin the query layer and watchvm.swappiness. Lower it to 10 on dedicated search boxes:sudo sysctl -w vm.swappiness=10and persist in/etc/sysctl.conf. - Logs.
searchd.logis the operational log andquery.logis the query log. Rotate both with/etc/logrotate.d/manticore, which the package installs. - Metrics. Hit
SHOW STATUSover SphinxQL orGET /-/healthzover HTTP from your monitoring system. The HTTP/sqlendpoint also serves a JSON-shaped response ofSHOW STATUSif you prefer machine-readable output.
Troubleshooting
searchdwill not start after a config change. Checkjournalctl -u manticore --no-pager -n 100. A common cause is a malformed config block or an invaliddata_dirpermission. Confirm/var/lib/manticoreis owned by themanticoreuser.Connection refusedover HTTP. Confirmsearchdis listening withss -tlnp | grep 9308. If it is, look at the nginx upstream and make sure it points to127.0.0.1:9308, not the public interface.401 Unauthorizedfrom nginx. Regenerate the htpasswd entry withsudo htpasswd /etc/nginx/manticore.htpasswd appuser(note: no-cflag on the second run, or it will overwrite the file).- High memory after large bulk inserts. Force a merge with
OPTIMIZE INDEX <table>over SphinxQL during a low-traffic window. Real-time tables accumulate disk chunks until merged.
Next steps
- Switch from real-time tables to plain tables if your data is mostly static and you can afford periodic full rebuilds. Plain tables are faster to query for the same RAM footprint.
- Enable replication once you spin up a second VPS. Manticore replication is built on Galera and works well over a private RamNode network.
- Wire up the Manticore Buddy bridge if you want PostgreSQL or Elasticsearch-style query syntax.
Manticore is one of the easiest search engines to run on a single VPS. With nginx in front, basic auth on the wire, UFW restricting public ports, and a daily backup timer, you have a deployment that will hold up for the long haul.
