Typesense 30.2 on AWS User Guide
Overview
This guide covers launching and using the Typesense 30.2 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.
Typesense is an open source search engine with a REST API. It returns typo tolerant results in milliseconds and supports filters, facets, sorting, grouping, synonyms, geo search, and vector search over embeddings you store with your documents, alone or combined with keyword relevance (hybrid search). Applications send documents to a collection 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 admin (bootstrap) API key on the instance and creates a search-only key through Typesense's keys API. Both are written to
/root/typesense-credentials.txt(root only). No key is baked into the image. - 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. Typesense itself listens on
127.0.0.1:8108only, and its cluster (peering) port on127.0.0.1:8107only. - Closed until first boot, and fail closed. Typesense cannot start before first boot has written its key, and nginx ships switched off. First boot opens the public listener only after Typesense works on loopback and an authenticated check passes; if anything fails, the listener is closed again. The service also refuses to start if its configuration ever loses the key, listens on a public address, or finds its data volume missing.
- Your data on its own disk. The Typesense data directory
/var/lib/typesenseis a separate 30 GiB EBS volume that you can resize independently of the operating system. - Hardened defaults. Typesense runs as its own unprivileged account in a systemd sandbox that can write only to its data and log directories. It can reach loopback only: no outbound connection and no instance metadata access unless you choose to open outbound access (see Step 11). nginx is blocked from the instance metadata service too, and it closes Typesense's
/proxyand/personalizationendpoints. Only the Typesense service account can connect to the cluster port. 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: Typesense 30.2 (the official release binary from dl.typesense.org, checksum pinned) at /opt/typesense/typesense-server, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. The data volume ships empty: no collection, document or key.
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 Typesense API) and TCP 80 (redirects to HTTPS) from your own networks only. Never open 8108 or 8107: Typesense 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 Typesense 30.2 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (Typesense keeps its indexes in memory, so size the instance's memory for your dataset), your key pair and a security group as above. The AMI brings two EBS volumes: the root volume and a 30 GiB data volume for /var/lib/typesense; enlarge the data volume at launch if you expect a large dataset.
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/sdf,Ebs={VolumeSize=100,VolumeType=gp3}' \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=typesense}]'
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| Typesense 30.2 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 keys, starts Typesense, opens nginx and runs an authenticated check before it finishes (well under a minute):
systemctl is-active typesense nginx typesense-firstboot typesense-peering-guard
sudo test -f /var/lib/cloudimg/typesense-firstboot.done && echo FIRSTBOOT_DONE
active
active
active
active
FIRSTBOOT_DONE
Only SSH and nginx listen on public addresses; Typesense 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:8107 users:(("typesense-serve",...))
127.0.0.1:8108 users:(("typesense-serve",...))
[::]:22 users:(("sshd",...))
[::]:443 users:(("nginx",...))
[::]:80 users:(("nginx",...))
Step 4: Get your URL and keys
sudo grep -E '^TYPESENSE_' /root/typesense-credentials.txt
The file is readable by root only:
TYPESENSE_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
TYPESENSE_API_KEY=<TYPESENSE_API_KEY>
TYPESENSE_SEARCH_API_KEY=<TYPESENSE_SEARCH_API_KEY>
TYPESENSE_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 Typesense under your own DNS name, see Step 9.TYPESENSE_API_KEYis the admin (bootstrap) key: it can do everything, including creating keys. Use it from your backend only.TYPESENSE_SEARCH_API_KEYcan only search, in every collection. It is the one to use from a web or mobile front end.
Send a key in the X-TYPESENSE-API-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. Avoid passing a key as a URL parameter: URLs end up in logs and browser history (nginx on this image does not log the query string, but other systems might).
Step 5: Confirm the API requires a key
C=/etc/ssl/typesense/typesense.crt; U=https://127.0.0.1
curl -s --cacert $C -o /dev/null -w 'no key -> HTTP %{http_code}\n' $U/collections
printf 'X-TYPESENSE-API-KEY: %s\n' wrong-key | curl -s --cacert $C -o /dev/null -w 'wrong key -> HTTP %{http_code}\n' -H @- $U/collections
sudo sed -n 's/^TYPESENSE_API_KEY=/X-TYPESENSE-API-KEY: /p' /root/typesense-credentials.txt | curl -s --cacert $C -H @- $U/debug; echo
curl -s --cacert $C $U/health; echo
no key -> HTTP 401
wrong key -> HTTP 401
{"state":1,"version":"30.2"}
{"ok":true}
/health stays open for load balancers; it reveals nothing. From another machine, use TYPESENSE_URL instead of https://127.0.0.1 (and -k or the instance's certificate, since it is self signed).
Step 6: Create a collection, import documents and search
Create a movies collection with a text field, a facet, a sort field and a 3 dimension vector field, then import five documents (JSON Lines) with the admin key:
C=/etc/ssl/typesense/typesense.crt; U=https://127.0.0.1
admin() { sudo sed -n 's/^TYPESENSE_API_KEY=/X-TYPESENSE-API-KEY: /p' /root/typesense-credentials.txt | curl -s --cacert "$C" -H @- "$@"; }
admin -H 'Content-Type: application/json' -X POST --data '{"name":"movies","fields":[{"name":"title","type":"string"},{"name":"genres","type":"string[]","facet":true},{"name":"year","type":"int32"},{"name":"embedding","type":"float[]","num_dim":3}],"default_sorting_field":"year"}' "$U/collections" | jq -c '{name, num_documents}'
admin -X POST --data-binary @<(printf '%s\n' \
'{"id":"1","title":"Carol","genres":["Romance","Drama"],"year":2015,"embedding":[0.9,0.1,0.1]}' \
'{"id":"2","title":"Wonder Woman","genres":["Action","Adventure"],"year":2017,"embedding":[0.1,0.2,0.3]}' \
'{"id":"3","title":"Life of Pi","genres":["Adventure","Drama"],"year":2012,"embedding":[0.1,0.8,0.2]}' \
'{"id":"4","title":"Mad Max: Fury Road","genres":["Adventure","Science Fiction"],"year":2015,"embedding":[0.2,0.3,0.3]}' \
'{"id":"5","title":"Moana","genres":["Fantasy","Action"],"year":2016,"embedding":[0.7,0.6,0.1]}') \
"$U/collections/movies/documents/import?action=create"; echo
{"name":"movies","num_documents":0}
{"success":true}
{"success":true}
{"success":true}
{"success":true}
{"success":true}
Search with the search key: a query with two typos still finds the film, filters and sorting work, the nearest neighbours of a vector come back with their distance, and the search key cannot write:
C=/etc/ssl/typesense/typesense.crt; U=https://127.0.0.1
search() { sudo sed -n 's/^TYPESENSE_SEARCH_API_KEY=/X-TYPESENSE-API-KEY: /p' /root/typesense-credentials.txt | curl -s --cacert "$C" -H @- "$@"; }
search "$U/collections/movies/documents/search?q=wondr+womn&query_by=title" | jq -c '{found, titles: [.hits[].document.title]}'
search "$U/collections/movies/documents/search?q=*&filter_by=genres:Adventure+%26%26+year:%3E%3D2015&sort_by=year:desc" | jq -c '[.hits[].document | {title, year}]'
search "$U/collections/movies/documents/search?q=*&vector_query=embedding:(%5B0.1,0.2,0.3%5D,k:2)" | jq -c '[.hits[] | .document.title]'
search -o /dev/null -w 'search key writing a document -> HTTP %{http_code}\n' -H 'Content-Type: application/json' -X POST --data '{"id":"9","title":"x","genres":[],"year":1,"embedding":[0,0,1]}' "$U/collections/movies/documents"
{"found":1,"titles":["Wonder Woman"]}
[{"title":"Wonder Woman","year":2017},{"title":"Mad Max: Fury Road","year":2015}]
["Wonder Woman","Mad Max: Fury Road"]
search key writing a document -> HTTP 401
In a real application you store the embeddings your own model produces (any dimension) in a float[] field with num_dim, and combine keyword and vector relevance by adding query_by together with vector_query (hybrid search). Typesense can also compute embeddings itself with a built-in model or a remote provider; both need outbound connections, which this image keeps off until you open them (Step 11).
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 and a wrong key are refused while the admin key works on the same request, the search key cannot create collections or read keys, the closed endpoints answer 403, and the admin key creates a temporary collection whose documents the search key then finds by typo tolerant text and by vector, before the collection is dropped:
sudo typesense-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
auth: no key 401, wrong key 401, admin key 200 (/collections); /debug without a key 401, with the admin key 200: version 30.2
search key: create collection 401, /keys 401, /debug 401
closed at nginx: /proxy, /proxy_sse and /personalization answer 403 even with the admin key
round trip: admin key created cloudimg_verify_<n> and imported 3 documents; the search key found 'Wonder Woman' for 'wondr womn' and by vector, could not write or drop; collection dropped
TYPESENSE_ROUNDTRIP_OK
Step 8: Security checks
The files that matter are locked down; the configuration file holds the admin key:
sudo stat -c '%a %U:%G %n' /etc/typesense/typesense-server.ini /root/typesense-credentials.txt /var/lib/typesense /etc/ssl/typesense/typesense.key
640 root:typesense /etc/typesense/typesense-server.ini
600 root:root /root/typesense-credentials.txt
750 typesense:typesense /var/lib/typesense
600 root:root /etc/ssl/typesense/typesense.key
The binary is the official 30.2 release (its SHA-256 equals the typesense-server binary in the official typesense/typesense:30.2 container image), configured for loopback only:
sha256sum /opt/typesense/typesense-server; /opt/typesense/typesense-server --version 2>&1 | head -1
sudo grep -E '^(api-address|api-port|peering-address|peering-port|data-dir|log-dir|enable-cors) ' /etc/typesense/typesense-server.ini
1178111344c96bf91567f579a30e0153e346f716b6f3e10f484ba099f21450b5 /opt/typesense/typesense-server
Typesense 30.2
api-address = 127.0.0.1
api-port = 8108
peering-address = 127.0.0.1
peering-port = 8107
data-dir = /var/lib/typesense
log-dir = /var/log/typesense
enable-cors = true
Typesense runs unprivileged in a sandbox that can write only to its data and log directories, may connect to loopback addresses only (so neither an embedder, a conversation model nor the proxy endpoint can reach the instance metadata service or anything else), and nginx cannot reach the instance metadata service either:
systemctl show typesense -p User -p ProtectSystem -p ReadWritePaths -p NoNewPrivileges -p IPAddressDeny -p IPAddressAllow
systemctl show nginx -p IPAddressDeny
IPAddressAllow=::1/128 127.0.0.0/8
IPAddressDeny=::/0 0.0.0.0/0
User=typesense
ReadWritePaths=/var/lib/typesense /var/log/typesense
ProtectSystem=strict
NoNewPrivileges=yes
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
nginx passes Typesense 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/typesense-proxy.conf
proxy_set_header Host $ts_host;
proxy_set_header Forwarded "";
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
Typesense's /proxy endpoint (which fetches any URL a key holder supplies) and its /personalization model upload are closed at nginx, and the cluster port refuses every account except Typesense's own (it serves built-in status and file services without authentication):
C=/etc/ssl/typesense/typesense.crt
for p in /proxy /personalization/models; do curl -s --cacert $C -o /dev/null -w "$p -> HTTP %{http_code}\n" -X POST https://127.0.0.1$p; done
curl -s -m 5 -o /dev/null -w 'peering port 8107 as ubuntu -> %{http_code}\n' http://127.0.0.1:8107/status || true
/proxy -> HTTP 403
/personalization/models -> HTTP 403
peering port 8107 as ubuntu -> 000
The certificate was generated on this instance and names its own addresses:
H=$(sudo sed -n 's#^TYPESENSE_URL=https://\([^/]*\)/.*#\1#p' /root/typesense-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 typesense-secret-leak-check.sh
LEAKCHECK_OK no admin or search key in the journal, auth.log, syslog, cloud-init, nginx logs, the Typesense log, shell history, MOTD, /var/backups, /etc (admin key only in typesense-server.ini), /usr/local, /opt or /tmp
Step 9: Your own DNS name and certificate
Point a DNS record at the instance, then set the name Typesense is served as. This mints a new self signed certificate for the name, updates nginx's host rule and the URL in /root/typesense-credentials.txt, reloads nginx and keeps the name across reboots:
sudo typesense-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/typesense/typesense.crt and typesense.key (key mode 0600) and run sudo systemctl reload nginx; running typesense-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 key rotation
Create a key for each application with the admin key; a key can be limited to some collections, some actions and an expiry time. For example, a key that can only search the movies collection:
sudo sed -n 's/^TYPESENSE_API_KEY=/X-TYPESENSE-API-KEY: /p' /root/typesense-credentials.txt \
| curl -s --cacert /etc/ssl/typesense/typesense.crt -H @- -H 'Content-Type: application/json' \
-X POST --data '{"description":"movies-search","actions":["documents:search"],"collections":["movies"]}' \
https://127.0.0.1/keys
Typesense shows a new key's value only in that response; store it then. From a search-only key you can also derive scoped search keys (with embedded filters) in your backend without calling the server; see the Typesense documentation on scoped API keys.
To generate a new admin key and a new search-only key (if either was ever exposed), run:
sudo typesense-rotate-api-key
It writes the new admin key to /etc/typesense/typesense-server.ini, restarts Typesense, deletes the old cloudimg search-only key, creates a new one and rewrites /root/typesense-credentials.txt. Keys you created yourself are kept. Update every client that used the old keys.
Step 11: Outbound connections (off by default)
Typesense can call out to the internet for some optional features: downloading a built-in embedding model (from models.typesense.org), remote embedding providers (OpenAI, Google, Azure or any OpenAI-compatible URL), conversation (RAG) and natural language search models, and the /proxy endpoint. This image blocks every outbound connection from the Typesense service, because Typesense's HTTP client does not verify the TLS certificate of the server it calls and its bundled OpenSSL and curl libraries carry advisories that upstream has not yet updated (see Security recommendations). Keyword, faceted, geo and vector search with embeddings you supply work without it.
If you need one of those features, allow outbound access by removing the drop-in and restarting Typesense. Point those features only at providers you trust, ideally over a private network path (for example a VPC endpoint or a proxy you control):
sudo rm /etc/systemd/system/typesense.service.d/60-cloudimg-egress.conf
sudo systemctl daemon-reload && sudo systemctl restart typesense
Typesense stays blocked from the instance metadata service (the other drop-in, 50-cloudimg-imds-deny.conf), and its proxy-disallowed-dest-cidrs setting keeps refusing private, loopback and link-local destinations. To close outbound access again, recreate the file with IPAddressAllow=localhost and IPAddressDeny=any under [Service]. The /proxy and /personalization endpoints stay closed at nginx unless you remove their location blocks from /etc/nginx/sites-available/cloudimg-typesense.
Step 12: Data volume, snapshots and backups
The data directory is its own EBS volume, mounted by filesystem UUID:
findmnt -n -o SOURCE,FSTYPE,TARGET /var/lib/typesense; df -h /var/lib/typesense | tail -1
/dev/nvme1n1 ext4 /var/lib/typesense
/dev/nvme1n1 30G 13M 28G 1% /var/lib/typesense
To grow it, enlarge the volume in the EC2 console (or with aws ec2 modify-volume), then run sudo resize2fs /dev/nvme1n1 on the instance; no restart is needed. If the volume is ever missing, Typesense refuses to start rather than write your data to the root disk.
Typesense can write a consistent snapshot of its data directory while it runs. Write it onto the data volume, then copy it off the instance (it holds your documents and API keys, so treat it as secret):
sudo install -d -o typesense -g typesense -m 0750 /var/lib/typesense/backups
sudo sed -n 's/^TYPESENSE_API_KEY=/X-TYPESENSE-API-KEY: /p' /root/typesense-credentials.txt \
| curl -s --cacert /etc/ssl/typesense/typesense.crt -H @- -X POST \
'https://127.0.0.1/operations/snapshot?snapshot_path=/var/lib/typesense/backups/snapshot-1'
EBS snapshots of the data volume are the simplest whole-dataset backup.
Step 13: Configuration
Settings live in /etc/typesense/typesense-server.base.ini (no key in it); /etc/typesense/typesense-server.ini is rendered from it plus the admin key. Edit the base file (for example cors-domains, cache-num-entries or log-slow-requests-time-ms; see the Typesense server configuration reference), then re-render with the current keys and restart:
sudo nano /etc/typesense/typesense-server.base.ini
sudo typesense-set-api-key --keep
Keep api-address and peering-address on 127.0.0.1 so nginx stays the only public listener, and leave TLS to nginx: the service refuses to start otherwise. enable-cors = true lets a browser front end call the API with the search key; set cors-domains to your own origins to restrict it. This image runs Typesense as a single node.
Step 14: Server components
/opt/typesense/typesense-server --version 2>&1 | head -1; nginx -v 2>&1; lsb_release -ds
ls /usr/share/doc/cloudimg/typesense/
Typesense 30.2
nginx version: nginx/1.24.0 (Ubuntu)
Ubuntu 24.04.5 LTS
LICENSE.txt
SOURCE
VERSION
| Component | Version | Notes |
|---|---|---|
| Typesense | 30.2 | /opt/typesense/typesense-server, service typesense, data /var/lib/typesense (own EBS volume), log /var/log/typesense |
| nginx | 1.24.0 | TLS on 443, redirect on 80, site /etc/nginx/sites-available/cloudimg-typesense |
| Ubuntu | 24.04 LTS | unattended security upgrades on |
Upgrading Typesense: take a snapshot (Step 12), stop the service, replace the binary with the newer official typesense-server-<version>-linux-amd64.tar.gz release from dl.typesense.org (check the Typesense release notes for upgrade steps), and start the service again.
Troubleshooting
curl: (60) SSL certificate problem: the certificate is self signed. Pass--cacert /etc/ssl/typesense/typesense.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
TYPESENSE_URL, the instance's IP address, or set your name withtypesense-set-hostname. - HTTP 401
Forbidden - a valid x-typesense-api-key header must be sent.: the key is missing, wrong, expired, or not allowed that action or collection (a search-only key gets 401 for any write). Aftertypesense-rotate-api-key, both cloudimg keys have changed. - HTTP 403
closed by cloudimg: the request went to/proxyor/personalization, which this image closes at nginx (Step 11). - An embedding model or remote embedder fails with a connection error or "Model not found": outbound connections are off by default (Step 11).
- Typesense does not start after a configuration change:
journalctl -u typesenseshows the reason;typesense-check-configrefuses a missing key, a public address, server TLS or a missing data volume. - First boot:
journalctl -u typesense-firstbootshows what it did. If it failed, it closed the public listener; fix the cause and runsudo systemctl restart typesense-firstboot.
Security recommendations
- Restrict the security group to your own networks. Use the search key (or keys you create per collection) in front ends and the admin key only in backends.
- Rotate the keys if either was ever exposed, and create expiring, narrowly scoped keys for applications.
- Typesense is a single C++ binary that includes its own copies of third-party libraries. The 30.2 release bundles OpenSSL 3.0.5, curl 7.87.0, zlib 1.2.12 and libarchive 3.7.7, for which fixed versions with security advisories exist upstream. On this image they are not reachable: Typesense does not terminate TLS (nginx does), it cannot open outbound connections (OpenSSL and curl are used only for outbound HTTPS), and the only feature that unpacks archives (
/personalizationmodel uploads) is closed. Opening outbound access (Step 11) makes the outbound path reachable again, so use it only towards providers you trust. cloudimg publishes a new version when Typesense ships updated libraries.
Support and licensing
cloudimg provides 24/7 technical support by email and live chat: support@cloudimg.co.uk.
Typesense is licensed under the GNU General Public License version 3 (/usr/share/doc/cloudimg/typesense/LICENSE.txt). This image contains the unmodified official Typesense 30.2 release binary; its complete source code is the upstream tag v30.2 (named, with its commit, in /usr/share/doc/cloudimg/typesense/SOURCE), and cloudimg provides it on request. Typesense is a trademark of Typesense, Inc.; cloudimg is not affiliated with or endorsed by Typesense, Inc.