Install Elasticsearch on a VPS: Setup, Security & Fixes
If Elasticsearch refuses to start on a fresh VPS and the log just says max virtual memory areas vm.max_map_count [65530] is too low, you're not alone — it's the single most common reason a first-time install never gets off the ground. This guide walks through a clean install on Ubuntu, the handful of bootstrap checks that trip people up, and the security and memory settings you actually need before pointing a storefront or app at it.
Symptom: Elasticsearch installs but won't start (or crashes under load)
A few patterns show up depending on where you are in the process:
systemctl status elasticsearchshowsfailedright after install, with no obvious reason in the short status output- The journal shows
bootstrap checks failedfollowed by a list of memory, file descriptor, or thread errors - The service starts, but
curl -X GET "localhost:9200"hangs or refuses the connection - It runs fine for a day, then dies under any real indexing load, and
dmesgshows the OOM killer took it out - Product search on a Magento or WooCommerce (with an Elasticsearch plugin) storefront returns nothing, even though the index looks populated
Almost all of these trace back to three things: the OS memory-map limit, JVM heap sizing, and Elasticsearch's own startup safety checks refusing to run in an unsafe configuration.
Cause: Elasticsearch is deliberately strict about the environment it starts in
Elasticsearch uses memory-mapped files heavily (for its Lucene indexes), and it runs a set of "bootstrap checks" on startup that refuse to let it run in a configuration likely to fail later under load. That's a good thing in production — it just means a bare Ubuntu VPS needs a few OS-level tweaks before the service will start cleanly.
The usual culprits, in order of how often we see them:
- vm.max_map_count too low. The kernel default (65530) is well below what Elasticsearch needs (262144) for its memory-mapped index segments.
- No swap limit / memory not locked. Without
bootstrap.memory_lock, the JVM heap can get swapped out under memory pressure, which tanks performance and can crash the node. - JVM heap sized wrong. Default heap sizing on a small VPS (1–2 GB RAM) is either too small to do useful work or, worse, configured to use more RAM than the box actually has once the OS and other services are accounted for.
- File descriptor / thread limits too low for the default systemd service on some distros, especially if Elasticsearch was installed manually instead of via the official APT repo.
Fix: install, then satisfy the bootstrap checks one by one
1. Install from the official APT repository
Don't grab a random tarball. Add Elastic's repo so you get proper systemd integration and security defaults out of the box:
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo gpg --dearmor -o /usr/share/keyrings/elastic.gpg
echo "deb [signed-by=/usr/share/keyrings/elastic.gpg] https://artifacts.elastic.co/packages/8.x/apt stable main" | sudo tee /etc/apt/sources.list.d/elastic-8.x.list
sudo apt update
sudo apt install elasticsearch
The installer prints a randomly generated password for the built-in elastic superuser and an enrollment token — copy both somewhere safe before you lose the terminal scrollback. Security (TLS + auth) is on by default from 8.x onward, which is a change from older guides you may find online.
2. Fix vm.max_map_count permanently
Set it live and persist it across reboots — a temporary sysctl -w alone will silently reset on the next reboot and you'll be back here in a month:
sudo sysctl -w vm.max_map_count=262144
echo "vm.max_map_count=262144" | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
3. Lock memory and disable swap
In /etc/elasticsearch/elasticsearch.yml, set:
bootstrap.memory_lock: true
Then raise the systemd service's memlock limit, since the default is too low for the JVM to lock its full heap:
sudo systemctl edit elasticsearch
[Service]
LimitMEMLOCK=infinity
If you'd rather not deal with memory locking on a small box, disabling swap entirely (sudo swapoff -a plus removing the swap entry from /etc/fstab) is a simpler alternative that satisfies the same bootstrap check.
4. Size the JVM heap to the VPS, not to the defaults
The rule of thumb is half your available RAM, capped at 31 GB (to stay under the JVM's compressed-pointer limit), and never more than half so the OS page cache still has room to do its job. On a 4 GB VPS, that's a 2 GB heap:
# /etc/elasticsearch/jvm.options.d/heap.options
-Xms2g
-Xmx2g
Keep Xms and Xmx identical — a heap that resizes at runtime is a heap that pauses at runtime. On anything under 2 GB of total RAM, skip Elasticsearch entirely; it will not run stably and you'll spend more time firefighting the OOM killer than it's worth. Consider OpenSearch's lighter footprint or a hosted search service instead.
5. Bind it where it needs to be reachable, and nowhere else
By default Elasticsearch binds to localhost, which is correct for a single-server setup where your app talks to it over 127.0.0.1. If your app server and Elasticsearch node are separate VPS instances, bind to the private network interface only — never 0.0.0.0 on a box with a public IP:
network.host: 10.0.0.5
discovery.type: single-node
Then restrict port 9200 (and 9300 for cluster transport, if you ever add nodes) at the firewall to just the app server's IP:
sudo ufw allow from 10.0.0.10 to any port 9200 proto tcp
sudo ufw deny 9200
6. Start it and confirm the bootstrap checks pass
sudo systemctl enable --now elasticsearch
curl --cacert /etc/elasticsearch/certs/http_ca.crt -u elastic:'<your-password>' https://localhost:9200
A JSON response with a cluster name and version number means it's up. If it still fails, journalctl -u elasticsearch -n 100 --no-pager will list every bootstrap check that didn't pass — fix them one at a time; they're additive, not mutually exclusive.
Common bootstrap errors and what actually fixes each one
| Error in the log | Root cause | Fix |
|---|---|---|
| max virtual memory areas vm.max_map_count [65530] is too low | Kernel default too low for Lucene's memory-mapped segments | Set vm.max_map_count=262144 in /etc/sysctl.conf |
| memory locking requested for elasticsearch process but memory is not locked | bootstrap.memory_lock: true set, but LimitMEMLOCK not raised | Set LimitMEMLOCK=infinity via systemctl edit |
| max file descriptors [4096] for elasticsearch process is too low | Manual/tarball install without the official systemd unit | Reinstall via the APT/YUM repo, which ships LimitNOFILE=65535 |
| initial heap size not equal to maximum heap size | Xms and Xmx set to different values | Set both to the same value in jvm.options.d |
| Process killed (signal 9) shortly after start, dmesg shows oom-kill | Heap + OS + other services exceed physical RAM | Lower Xmx, or upgrade the VPS – don't run ES under 2GB RAM |
Prevention: bake the config in before you forget it
- Set
vm.max_map_countin your server provisioning script (Ansible, cloud-init, or a plain post-install bash script) so it's never a one-off manual step you forget on the next server. - Keep heap at 50% of RAM as a hard rule in your notes, and revisit it any time you resize the VPS — a resized box doesn't resize its JVM options automatically.
- Snapshot the index regularly (Elasticsearch's own snapshot API to S3-compatible storage, or Getwebup VPS snapshots for the whole disk) — a corrupted index after an unclean shutdown is much less stressful to fix from a snapshot than from scratch.
- Monitor JVM heap usage and old-generation GC pauses, not just disk and CPU — a heap creeping toward 85% is your early warning before the node stops responding to queries.
- If you're running this for a Magento or WooCommerce storefront, re-run a full reindex after any Elasticsearch version upgrade — mapping changes between major versions aren't always backward compatible with an existing index.
FAQ
Frequently asked questions
Can I run Elasticsearch on a 1GB or 2GB VPS?
Not reliably. Elasticsearch needs headroom for the JVM heap, the OS page cache, and Lucene's off-heap memory usage. Under 2GB total RAM you'll fight the OOM killer constantly. For small stores, either size up to 4GB or use a lighter alternative like OpenSearch with a small heap, or a hosted search service.
Do I need Elasticsearch on the same VPS as my website?
No, and for anything beyond a small single-server setup it's better on its own box or a private-network instance. That isolates its memory usage from your web server and PHP-FPM, and lets you restart or upgrade one without touching the other. Just make sure network.host is bound to a private interface, not the public one.
Why does my Magento/WooCommerce search still show old results after fixing the bootstrap errors?
Getting Elasticsearch running doesn't automatically reindex your catalog. Run the platform's own reindex command (php bin/magento indexer:reindex catalogsearch_fulltext for Magento, or your search plugin's reindex tool for WooCommerce) once the service is confirmed healthy.
Is it safe to disable the bootstrap checks instead of fixing them?
Technically yes, with discovery.type: single-node and by setting individual checks off, but that just delays the failure to production under load instead of at startup. The checks exist because those exact conditions cause node crashes and data loss later - fix the underlying setting instead.
What's the difference between Elasticsearch and OpenSearch for this kind of setup?
OpenSearch is the open-source fork Amazon maintains after Elastic's license change; the install steps, bootstrap checks, and vm.max_map_count fix in this guide apply to both almost identically. Pick OpenSearch if you want to stay fully Apache 2.0 licensed, or if the platform you're running (recent Magento versions, for example) explicitly recommends it.