Use case · Search engine

Typesense self hosted on a dedicated server

A self-hosted Typesense node keeps its entire search index in RAM and the raw documents on disk, so size the machine by memory first, at 2 to 3 times the size of the fields you search, per the Typesense system requirements. Install the official DEB package behind a firewall, rebind it to 127.0.0.1, give browsers a search-only key, and back it up through the snapshot API.

See the machines

What Typesense keeps in memory

Typesense builds its whole search index in memory and reads from disk only the documents it returns. An idle process uses about 20 MB of RAM; the rest is your data. Only field values count toward the index, not field names, and fields left out of the schema (or with index set to false) stay on disk.

What is indexedRAM per the docsWorked example from the docs
Keyword-searchable fields2X to 3X the size of those fields1M records x 204 bytes = 204 MB, so 408 to 612 MB
Vectors7 bytes x dimensions x records384 dimensions x 100,000 records = 268.8 MB
Built-in embedding model (S-BERT, E-5)2 to 6 GB on top of the vectorsA remote embedding service adds nothing beyond the vectors

The system requirements page sets a floor of 2 vCPUs, says Typesense uses every core it is given, and cites a 4 vCPU node serving 104 concurrent searches per second over 2.2 million records. Disk must hold at least the raw dataset, and SSDs are recommended.

Sizing the machine

The production guide asks you to keep RAM use under 85% of the total and CPU under 90% on average, with a small swap file that should never actually be used. Our own rule of thumb from those two figures is to take the 3X end of the index estimate, add vectors and any built-in model, divide by 0.85 and round up to the next memory size our lines offer (8 GB is the smallest). Leave disk for the raw data plus one snapshot copy and the archives you keep.

Searchable fieldsIndex at 3XDivided by 0.85 (our arithmetic)Memory to order
1 GB3 GB3.5 GB8 GB
10 GB30 GB35.3 GB64 GB
60 GB180 GB212 GB256 GB
200 GB600 GB706 GB1 TB

Small indexes fit a Moonshot cartridge, several with NVMe (NVMe cartridge servers); only the m510 and m710x cartridges carry a console, the rest have power control. Larger ones belong on a single- or dual-socket rack server with up to 1 TB, or a four-socket machine with up to 2 TB; every racked single-, dual- and quad-socket server has an out-of-band console (HPE iLO, Dell iDRAC or a Supermicro BMC). The X-Gene cartridge is out, because its newest image is Ubuntu 18.04 and Typesense 26.0 and later want Ubuntu 20 or newer. Every line in the dedicated server range is the whole machine, with no hypervisor and an unmetered port. Delivery of a racked machine runs about 30 minutes from order approval; a build to order takes 4 to 24 hours.

What the DEB package does on install

Read from the files inside the 30.2 amd64 package.

  • postinst generates a random 48-character admin key, writes it into /etc/typesense/typesense-server.ini, then enables and starts the service, so the server is listening before apt returns.
  • typesense-server.ini sets api-address = 0.0.0.0 and api-port = 8108, with data in /var/lib/typesense and logs in /var/log/typesense. It ships at mode 644, readable by every local account, for the few seconds until the install block below sets it to 600 (on a fresh server with no other users, that is nobody).
  • typesense-server.service has no User= line, so the process runs as root and restarts on failure. It reads the key through --config, so the key never appears in ps.

So the firewall goes up first and the API moves to loopback straight after. As a host process, Typesense is filtered by ufw on 8108 and on its peering port 8107 like anything else.

Installing on Ubuntu 24.04

Run this as root (sudo -i) on a fresh server. It reads the newest release tag from GitHub and fetches the matching amd64 or arm64 package. Our complete list of changes to the install guide is an apt update that also installs ufw and curl, ufw switched on before the package, the version read instead of typed, the package kept in /root/typesense-pkg, --force-confold so a later upgrade keeps your edited ini, the ini set to mode 600, api-address moved to 127.0.0.1, a root-only curl config file (/root/.typesense-admin.curl, mode 600) carrying the admin key for later commands, and a health wait. Moved SSH off port 22? Run ufw allow 2222/tcp with your port in place of 2222 first, because the OpenSSH profile opens 22 only.

apt-get update && apt-get install -y ufw curl \
&& sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw && ufw allow OpenSSH && ufw --force enable \
&& V=$(curl -fsS https://api.github.com/repos/typesense/typesense/releases/latest | sed -n 's/.*"tag_name": *"v\([0-9.]*\)".*/\1/p') \
&& [ -n "$V" ] && A=$(dpkg --print-architecture) && P=/root/typesense-pkg/typesense-server-$V-$A.deb \
&& install -d -m 700 /root/typesense-pkg \
&& curl -fL -o "$P" "https://dl.typesense.org/releases/$V/typesense-server-$V-$A.deb" \
&& apt-get install -y -o Dpkg::Options::=--force-confold "$P" \
&& I=/etc/typesense/typesense-server.ini && chmod 600 "$I" \
&& sed -i 's/^api-address *=.*/api-address = 127.0.0.1/' "$I" && grep -qx 'api-address = 127.0.0.1' "$I" \
&& K=$(sed -n 's/^api-key *= *//p' "$I") && [ -n "$K" ] && C=/root/.typesense-admin.curl \
&& install -m 600 /dev/null "$C" \
&& printf 'header = "X-TYPESENSE-API-KEY: %s"\nheader = "Content-Type: application/json"\n' "$K" > "$C" \
&& systemctl enable --now typesense-server && systemctl restart typesense-server \
&& { ok=; for i in $(seq 1 30); do curl -fs http://127.0.0.1:8108/health >/dev/null && ok=1 && break; sleep 2; done; [ -n "$ok" ]; } \
&& ss -ltn | grep -q ' 127.0.0.1:8108 ' && ! ss -ltn | grep -q ' 0.0.0.0:8108 ' \
&& echo "Typesense $V answers on 127.0.0.1:8108 only" \
|| echo "STOP - read the message above; nothing after the failed step ran; check ufw status before going on"

If you see STOP, the steps after the failure did not run; fix what it says, check ufw status, and paste again. Repeating it keeps your config and key, but if a newer release has come out since, it also upgrades you, so read that release's notes first. That makes it the upgrade path too.

With the official Docker image instead, publish 127.0.0.1:8108:8108 rather than the install guide's 8108:8108: on IPv4 a published port skips ufw's rules entirely (Docker's packet-filtering page explains why), and Docker Engine 28+ keeps a 127.0.0.1 mapping local. Replace its sample key xyz too; a key passed as an argument shows in ps.

Keys, HTTPS and browser search

The bootstrap key allows every operation, and the server configuration page says not to expose it to browser JavaScript. Keep it on the server and mint narrower keys from it.

  1. As root, create a search-only key for one collection (replace products) with curl -fsS -K /root/.typesense-admin.curl -d '{"description":"site search","actions":["documents:search"],"collections":["products"]}' http://127.0.0.1:8108/keys, which takes its admin header from the root-only file the install block wrote. The value in the reply is shown only at creation, so store it then.
  2. If your backend runs on this machine, let it query 127.0.0.1:8108 and stop here. If browsers call Typesense directly, aim a DNS name at the machine, then ufw allow 80,443/tcp before any certificate request.
  3. Install Caddy from Ubuntu 24.04's universe repository (apt-get update && apt-get install -y caddy), write printf 'search.example.com {\n\treverse_proxy 127.0.0.1:8108\n}\n' > /etc/caddy/Caddyfile with your hostname, then caddy validate --config /etc/caddy/Caddyfile && systemctl reload caddy.
  4. Limit browser origins with enable-cors = true and cors-domains = https://www.example.com in the ini (no trailing slash), then systemctl restart typesense-server.

Root can still read the admin key in the ini and in /root/.typesense-admin.curl, and so can anyone who can read the backup archives. Give scoped keys an expires_at and rotate them; for multi-tenant collections, the production guide has your backend generate scoped search keys with the user's ID in the embedded filter.

Backups through the snapshot API

The backup guide warns that archiving the data directory directly is unsafe, because Typesense may be writing to open files. Its route is the snapshot endpoint, which writes a point-in-time copy to a directory you name. The script below snapshots into a fresh folder, archives it with the ini, checks the listing, and only then prunes archives older than 14 days. The nightly job is written only after a first run succeeds, so run this once your first collection holds documents.

cat > /usr/local/sbin/typesense-backup.sh <<'EOF'
set -eu -o pipefail
umask 077
B=/var/backups/typesense; T=$(date +%F-%H%M%S); W="$B/work-$T"; F="$B/.part-$T"
trap 'rm -rf "$W" "$F"' EXIT
curl -fsS -K /root/.typesense-admin.curl -X POST \
  "http://127.0.0.1:8108/operations/snapshot?snapshot_path=$W/data" | grep -q '"success":true'
tar -czf "$F" -C "$W" data -C /etc/typesense typesense-server.ini
tar -tzf "$F" > "$W/list"
grep -qx typesense-server.ini "$W/list"; grep -q '^data/state/snapshot/' "$W/list"
mv "$F" "$B/typesense-$T.tar.gz"
find "$B" -maxdepth 1 -name 'typesense-*.tar.gz' -mtime +14 -delete
echo "backup OK $B/typesense-$T.tar.gz"
EOF
install -d -m 700 /var/backups/typesense && bash /usr/local/sbin/typesense-backup.sh \
&& printf 'SHELL=/bin/bash\n30 3 * * * root bash /usr/local/sbin/typesense-backup.sh > /var/backups/typesense/last-run.log 2>&1\n' > /etc/cron.d/typesense-backup \
&& echo "nightly backup scheduled" || echo "STOP - no archive was kept and no job was installed; read the message above"

Each snapshot duplicates the dataset, so leave disk room for it. Archives are mode 600 in a mode 700 folder because they hold the admin key. Copy them off the machine yourself, with plain rsync -a and no --delete, so an emptied source can never empty the copies.

Restoring, and rolling back

The guide's restore is stop, empty the data directory, extract, start. Ours checks the archive and the set-aside path before stopping anything, moves current data aside instead of deleting it, and first writes a rollback script. Run as root, with A set to an archive from ls /var/backups/typesense.

cat > /root/ts-rollback.sh <<'EOF'
D=/var/lib/typesense
test -d "$D.previous" && test ! -e "$D.failed" && systemctl stop typesense-server \
&& { test ! -e "$D" || mv "$D" "$D.failed"; } && mv "$D.previous" "$D" && systemctl start typesense-server \
&& echo "rolled back" || echo "STOP - check which of $D, $D.previous and $D.failed exist before going on"
EOF
A=/var/backups/typesense/typesense-2026-11-01-033000.tar.gz; D=/var/lib/typesense; G=
test -f "$A" && gzip -t "$A" && tar -tzf "$A" > /root/ts-restore.list \
&& grep -q '^data/state/snapshot/' /root/ts-restore.list && test -d "$D" && test ! -e "$D.previous" \
&& G=1 || echo "STOP - the archive check failed, $D is missing or $D.previous already exists; nothing was touched"
[ -n "$G" ] && { systemctl stop typesense-server \
&& mv "$D" "$D.previous" && install -d -m 755 "$D" && tar -xzf "$A" -C "$D" --strip-components=1 data \
&& systemctl start typesense-server \
&& { ok=; for i in $(seq 1 120); do curl -fs http://127.0.0.1:8108/health >/dev/null && ok=1 && break; sleep 5; done; [ -n "$ok" ]; } \
&& echo "restored from $A - check your collections" \
|| echo "STOP - if $D.previous does not exist, your data was not moved and nothing is lost (run systemctl start typesense-server if it is stopped); if it does, your old data is there, so read journalctl -u typesense-server and /var/log/typesense/typesense.log; if it is still loading, wait and check /health again, otherwise run bash /root/ts-rollback.sh"; }

A large index can take longer than the ten minutes the loop waits (for a single-node Typesense Cloud upgrade, the updating guide puts downtime at 5 to 60 minutes, depending on dataset size); if the log shows it still loading, wait and query /health again before rolling back with bash /root/ts-rollback.sh. Once satisfied, remove /var/lib/typesense.previous, or a later restore stops before touching anything; remove /var/lib/typesense.failed too if you rolled back, or a later rollback refuses. /root/ts-restore.list is only a listing and can go.

Keys created through /keys live in the on-disk store (auth_manager.cpp), so they return with the data. On a new machine, run the install block first, and copy the old api-key line from the archive's ini if your apps still use the bootstrap key. After that, or any other api-key change, paste the install block again so it restarts Typesense and rewrites /root/.typesense-admin.curl; otherwise every nightly backup fails with a 401 that shows only in last-run.log.

Upgrades, clusters and the licence

The updating guide says to read every release note published since your version and to test in staging first. An upgrade is a new package and a restart, with no re-index. Take a fresh archive with the backup script, then rerun the install block; a single node is down while the index rebuilds.

Clusters run Raft, where 3 nodes tolerate one failure and 5 tolerate two at the cost of higher write latency, and all nodes share the bootstrap key. The high availability guide wants peering-address on private IPs because Raft traffic is unencrypted; a mesh like the one on our NetBird page can supply them. Update every follower first and the leader last, waiting for /health to answer on each node before the next; the node whose /debug (admin key) shows state 1 is the leader, 4 a follower.

Typesense is GPL-3.0. Our general reading, not legal advice, is that running it, modified or not, carries no source duty, while distributing copies, modified or not, means passing on the source under the same licence. There is no network clause in GPL-3.0.

When one machine is the wrong answer

  • An index bigger than one machine's memory. A cluster replicates the entire dataset to every node, so it adds copies, not capacity.
  • Built-in embedding models at volume. The docs report a marked speed-up on a GPU, and Typesense supports Nvidia GPUs only.

Questions

How much memory does a million-product catalogue need?

Measure the values of only the fields you search, filter or sort on, then multiply by 2 to 3. At 200 bytes of such fields per product, that is about 200 MB of data and 400 to 600 MB of index. Add vectors at 7 bytes per dimension per product if you use them, then keep the total under 85% of RAM.

Can Typesense face the internet without a proxy?

The production guide says it can, since it embeds the h2o web server and can take api-port = 443 with ssl-certificate and ssl-certificate-key set. We put Caddy in front because it obtains and renews its certificates itself. Either way, only search-only or scoped keys should ever reach a browser.

Does upgrading Typesense mean re-indexing everything?

No. The updating guide says the new version reads the raw data from disk and rebuilds its in-memory indexes. Plan for the rebuild time on a single node, and take a snapshot archive first.

Size the RAM, then choose the line

Work out the index from your searchable fields and pick a machine with that memory, SSD storage and root.

See the machines

Related on Server Room

Other use cases