Search Engine
    Open Source

    Deploy Manticore Search on a VPS

    Production-ready Manticore Search on a RamNode VPS — TLS via nginx, HTTP basic auth, UFW hardening, real-time tables, and daily backups.

    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.

    WorkloadSuggested RamNode planNotes
    Dev / staging, up to ~1M docsStandard VPS, 2 GB RAM, 2 vCPUComfortable for testing real-time indexes and small corpora
    Production, 1M to ~20M docsPremium NVMe VPS, 4 GB RAM, 2 vCPUSweet spot for blog search, product catalogs, log analytics under modest QPS
    Vector search or 50M+ docsPremium NVMe VPS, 8 to 16 GB RAM, 4 vCPUVector 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.

    shell
    sudo apt update && sudo apt upgrade -y
    sudo timedatectl set-timezone UTC
    sudo apt install -y unattended-upgrades
    sudo dpkg-reconfigure -plow unattended-upgrades

    Configure UFW so only SSH stays open during installation. We will open 80/443 after nginx is in place.

    shell
    sudo ufw default deny incoming
    sudo ufw default allow outgoing
    sudo ufw allow OpenSSH
    sudo ufw enable
    sudo ufw status verbose

    Step 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.

    shell
    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-extra

    manticore-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.

    shell
    sudo systemctl enable --now manticore
    sudo systemctl status manticore --no-pager

    Confirm searchd is listening on the three default ports.

    shell
    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.

    shell
    sudo nano /etc/manticoresearch/manticore.conf

    Inside the searchd block, verify or set:

    shell
    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:

    shell
    sudo systemctl restart manticore
    sudo journalctl -u manticore --since "5 minutes ago" --no-pager

    Step 4: Create your first real-time table

    Connect using the MySQL client over the wire protocol.

    shell
    mysql -h 127.0.0.1 -P 9306

    Create a real-time table for documents:

    shell
    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:

    shell
    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:

    shell
    curl -s -X POST http://127.0.0.1:9308/search \
      -H 'Content-Type: application/json' \
      -d '{"index":"documents","query":{"match":{"*":"manticore"}}}' | jq

    Step 5: Reverse proxy with nginx and TLS

    Install nginx and certbot.

    shell
    sudo apt install -y nginx certbot python3-certbot-nginx

    Open HTTP and HTTPS in the firewall.

    shell
    sudo ufw allow 'Nginx Full'
    sudo ufw delete allow 'Nginx HTTP' 2>/dev/null || true
    sudo ufw status

    Create an HTTP basic auth file. Replace appuser with your client username.

    shell
    sudo apt install -y apache2-utils
    sudo htpasswd -c /etc/nginx/manticore.htpasswd appuser

    Drop in the site config at /etc/nginx/sites-available/manticore:

    shell
    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.

    shell
    sudo ln -s /etc/nginx/sites-available/manticore /etc/nginx/sites-enabled/manticore
    sudo nginx -t
    sudo systemctl reload nginx

    Issue the certificate.

    shell
    sudo certbot --nginx -d search.example.com \
      --non-interactive --agree-tos -m you@example.com --redirect

    Certbot will rewrite the site file with the correct ssl_certificate directives and schedule renewal via the certbot.timer systemd unit. Verify renewal will work:

    shell
    sudo certbot renew --dry-run

    Test the public endpoint:

    shell
    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.

    shell
    ssh -L 9306:127.0.0.1:9306 you@search.example.com

    Inside 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.

    shell
    sudo mkdir -p /var/backups/manticore
    sudo chown manticore:manticore /var/backups/manticore

    Create /usr/local/bin/backup-manticore.sh:

    shell
    #!/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.

    shell
    sudo chmod +x /usr/local/bin/backup-manticore.sh

    Create /etc/systemd/system/manticore-backup.service:

    shell
    [Unit]
    Description=Manticore Search backup
    After=manticore.service
    
    [Service]
    Type=oneshot
    User=manticore
    ExecStart=/usr/local/bin/backup-manticore.sh

    Create /etc/systemd/system/manticore-backup.timer:

    shell
    [Unit]
    Description=Daily Manticore Search backup
    
    [Timer]
    OnCalendar=daily
    RandomizedDelaySec=30m
    Persistent=true
    
    [Install]
    WantedBy=timers.target

    Enable and start the timer.

    shell
    sudo systemctl daemon-reload
    sudo systemctl enable --now manticore-backup.timer
    sudo systemctl list-timers --no-pager | grep manticore

    For 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/manticore on 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_query in the query layer and watch vm.swappiness. Lower it to 10 on dedicated search boxes: sudo sysctl -w vm.swappiness=10 and persist in /etc/sysctl.conf.
    • Logs. searchd.log is the operational log and query.log is the query log. Rotate both with /etc/logrotate.d/manticore, which the package installs.
    • Metrics. Hit SHOW STATUS over SphinxQL or GET /-/healthz over HTTP from your monitoring system. The HTTP /sql endpoint also serves a JSON-shaped response of SHOW STATUS if you prefer machine-readable output.

    Troubleshooting

    • searchd will not start after a config change. Check journalctl -u manticore --no-pager -n 100. A common cause is a malformed config block or an invalid data_dir permission. Confirm /var/lib/manticore is owned by the manticore user.
    • Connection refused over HTTP. Confirm searchd is listening with ss -tlnp | grep 9308. If it is, look at the nginx upstream and make sure it points to 127.0.0.1:9308, not the public interface.
    • 401 Unauthorized from nginx. Regenerate the htpasswd entry with sudo htpasswd /etc/nginx/manticore.htpasswd appuser (note: no -c flag 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.