How to optimize MariaDB on a 2GB RAM VPS

A 2GB RAM VPS is where MariaDB goes to die if you leave it at defaults. The stock configuration assumes the server owns the whole machine, and on a 2GB box that assumption sends the kernel's OOM killer after mysqld the moment traffic spikes. I have run MariaDB on a 2GB Linux VPS for years, and the difference between a server that survives a traffic burst and one that gets killed at 3am is a handful of settings. This guide walks through the exact values I use, why they matter, and how to verify each change actually stuck.
Prerequisites
- A VPS running Ubuntu 24.04 or Debian 12 with 2GB RAM (the commands work on any modern distro, paths stay the same).
- Root or sudo access on the server.
- MariaDB installed and running. If it is not installed yet,
sudo apt install mariadb-serveron Debian/Ubuntu gets you a current LTS release. - Knowledge of the workloads: is this a WordPress site, an n8n workflow database, or a custom app? The answer changes the right thread count.
Why MariaDB eats all your RAM
The default my.cnf ships with innodb_buffer_pool_size set to around 128MB, which sounds small, but the real memory hog is everything else. The InnoDB buffer pool only grows to its configured limit, yet the adaptive hash index, the dictionary cache, per-connection thread buffers, and the query cache in older versions all add up fast. On a 2GB box, the total footprint of an untouched MariaDB under load routinely crosses 1.5GB, and then the OOM killer starts picking targets.
The second problem: swap. A fresh VPS often has no swap at all, or a token amount. When MariaDB hits its memory ceiling, the kernel starts swapping aggressively, and InnoDB hates that more than almost anything. A swapped-out buffer pool means every query touches disk twice, once for the page eviction, once for the reload. The fix is a proper swap file plus a buffer pool sized to keep the active working set in RAM.
Step 1: Set up swap before touching MariaDB
Before tuning the database, give the OS room to breathe. A 2GB swap file on a 2GB RAM VPS is the sweet spot, enough to catch memory spikes without making the disk the bottleneck. On an NVMe-backed VPS this is painless, and modern NVMe SSDs handle swap better than the spinning disks of a decade ago.
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
Verify it is active:
swapon --show
# Output: NAME TYPE SIZE USED PRIO
# /swapfile file 2G 0B -2
Two tweaks make swap behave under memory pressure. Lower vm.swappiness so the kernel prefers RAM until it genuinely needs to spill, and lower vm.vfs_cache_pressure so inode and dentry caches survive longer. Both go into /etc/sysctl.conf:
vm.swappiness=10
vm.vfs_cache_pressure=50
sudo sysctl -p
Swappiness of 10 means the kernel will only swap when memory is really tight, not at the first hint of pressure. I learned this the hard way: with the default swappiness of 60, a single MariaDB spike turned into a thrashing session that took minutes to recover.
Step 2: Size the InnoDB buffer pool
This is the single most important setting in MariaDB, and the one people get wrong most often. On a 2GB VPS, the buffer pool should be around 60% of available RAM, so roughly 1.2GB if the DB is the only major service, or 700MB if nginx, PHP-FPM, or an app server shares the box. The buffer pool is the cache that holds your most-used indexes and rows; too big and the OS starts swapping, too small and every query hits disk.
Here is the pragmatic middle ground for a shared 2GB VPS:
sudo mariadb -e "SET GLOBAL innodb_buffer_pool_size = 700 * 1024 * 1024;"
That command only changes the runtime value. To make it permanent, add it to the config file. Locate the config first:
mariadb --help --verbose | grep -A1 'my.cnf'
# Output shows the read order, typically /etc/mysql/my.cnf
Then create a dedicated override file so package updates do not stomp your changes:
sudo nano /etc/mysql/mariadb.conf.d/99-lowmem.cnf
Fill it with:
[mysqld]
innodb_buffer_pool_size = 700M
innodb_log_file_size = 96M
innodb_flush_log_at_trx_commit = 2
The innodb_flush_log_at_trx_commit = 2 line is a deliberate trade-off: with value 1 (the default) every commit fsyncs to disk, which is safe but slow on budget hardware. Value 2 flushes to the OS cache instead, losing at most one second of transactions if the OS crashes. For a web app, not a financial ledger, that trade is worth the speed. If you run a payment system or anything where losing a second of commits is unacceptable, keep the default of 1.
Restart and verify the value took effect:
sudo systemctl restart mariadb
sudo mariadb -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';"
# Output: +-------------------------+-----------+
# | Variable_name | Value |
# +-------------------------+-----------+
# | innodb_buffer_pool_size | 734003200 |
734003200 bytes is exactly 700MB, so the setting is live.
Step 3: Cap the threads that eat per-connection RAM
Every open connection to MariaDB reserves thread buffers, and on a 2GB box, dozens of connections means hundreds of MB of overhead. The default max_connections of 151 is far too generous for 2GB of RAM. Worse, the per-thread settings like sort_buffer_size default to 2MB each, and join_buffer_size adds another 256KB per connection, all allocated on demand and all summing up fast.
sudo nano /etc/mysql/mariadb.conf.d/99-lowmem.cnf
Add under the [mysqld] section:
max_connections = 50
thread_cache_size = 8
sort_buffer_size = 512K
join_buffer_size = 256K
tmp_table_size = 32M
max_heap_table_size = 32M
These values matter because each connection can allocate up to sort_buffer_size plus join_buffer_size at peak, so 50 connections at 768KB each is about 38MB worst case. That is manageable. The default 151 connections at default buffer sizes could hit 350MB under load, on a box that has 2GB total.
The table size settings are likewise deliberate: internal temporary tables spill to disk on a 2GB box if they get large, so allowing 32MB in RAM before the spill beats the old default of 16MB, but it stays bounded.
Restart and confirm:
sudo systemctl restart mariadb
sudo mariadb -e "SHOW VARIABLES WHERE Variable_name IN ('max_connections','sort_buffer_size','tmp_table_size');"
Step 4: Disable query cache on MariaDB 10.6 and later
MariaDB removed the query cache in version 10.6, so if you run a current LTS like 11.4 or 12.3, the setting is gone and nothing to tune. If you are still on 10.5 or earlier, the query cache did more harm than good in most workloads, it is a global mutex that serializes cache lookups, and on a busy server it becomes a bottleneck. Leave it off:
query_cache_type = 0
query_cache_size = 0
If that looks like a non-issue because you run a modern version, good, the point is to stop copying outdated tuning guides that still tell you to size the query cache. Those guides are wrong for anything MariaDB 10.6 and newer, which covers every LTS release you should run in 2026.
Step 5: Measure the actual memory footprint
After all the config changes, verify the process fits in memory. The quickest check is the process list from the OS side:
ps -o pid,rss,vsz,cmd -p $(pgrep -x mariadbd)
# Output: PID RSS VSZ CMD
# 123456 512340 2045678 /usr/sbin/mariadbd
RSS is the resident set size in KB. On a 2GB VPS with 700MB of buffer pool, a realistic RSS is 500-700MB, which leaves room for nginx, PHP-FPM, or your app. If RSS sits above 1.2GB with the buffer pool at 700MB, something else is allocating memory, check for a runaway tmp_table_size or too many open connections:
sudo mariadb -e "SHOW STATUS LIKE 'Threads_connected';"
# Keep this under 20-30 for a 2GB box
sudo mariadb -e "SHOW GLOBAL STATUS LIKE 'Created_tmp_disk_tables';"
# Watch this counter grow: each increment is a query that spilled to disk
If disk temp tables grow fast, reduce tmp_table_size expectations, or add an index to the queries causing the sorts. Memory tuning never fixes a missing index.
Step 6: If MariaDB runs in Docker, cap the container
Running MariaDB in Docker on a 2GB VPS changes the math, because Docker does not respect host memory limits unless you set them. The container can grow until the host OOMs, and the OOM killer then targets the largest process, which is usually your database container at the worst moment.
With Docker Compose v2, set explicit memory limits that leave headroom for the host OS and other services:
services:
mariadb:
image: mariadb:11
deploy:
resources:
limits:
memory: 1.5G
reservations:
memory: 1G
command: --innodb-buffer-pool-size=700M --max-connections=50
Setting both limits and reservations is the part most guides skip. Reservations tell Docker the container needs 1GB guaranteed, limits cap it at 1.5GB so it cannot steal the whole host. Without the reservation, Docker may place the container on a host that cannot actually provide the memory, and on a single 2GB VPS that means the kernel starts swapping instantly.
Verify the limit is respected inside the container:
docker exec mariadb cat /sys/fs/cgroup/memory.max
# Output: 1610612736 (1.5GB in bytes)
Troubleshooting
Symptom: MariaDB will not start after editing config. A typo in a config file aborts the service. Check the error log first:
sudo journalctl -u mariadb -n 50
# Look for: "unknown variable 'innodb_buffer_pool_sizee'" or similar
Then validate the config without starting the service:
sudo mariadbd --validate-config
Symptom: OOM killer still fires during peak traffic. The buffer pool is too large for the total workload. Drop it from 700M to 512M, then check pressure again. Also confirm no other process is leaking: free -h shows real usage, and a process eating RAM in a loop shows up in top sorted by memory.
Symptom: queries get slower after the change. You likely set innodb_buffer_pool_size too low for the working set. Watch Innodb_buffer_pool_reads versus Innodb_buffer_pool_read_requests; if reads are a high percentage of requests, the pool is too small and queries hit disk.
sudo mariadb -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read%';"
# read_requests is total logical reads, reads is physical disk reads
FAQ
What is the ideal innodb_buffer_pool_size for 2GB RAM?
Set it to 50-70% of total RAM depending on what else runs on the box. For a dedicated database VPS, 1.2GB works. For a shared server with nginx and PHP-FPM, 700MB is a safer starting point, then watch RSS and disk reads to adjust.
Why does MariaDB use more RAM than my buffer pool setting?
The buffer pool is only one consumer. Per-connection sort and join buffers, the dictionary cache, the adaptive hash index, and the binary log cache all live outside the pool. On a 2GB box, budget for the pool plus roughly 200-400MB of overhead.
Does MariaDB need swap?
Yes, and specifically on a 2GB VPS. A 2GB swap file catches memory spikes and prevents the OOM killer from terminating mysqld. Keep swappiness low (10-20) so the kernel only swaps when memory is genuinely exhausted.
Is the query cache worth enabling on a 2GB VPS?
No, and on MariaDB 10.6 and later the option is removed entirely. The query cache was a global mutex that serialized lookups, and it typically hurt performance under load. Skip it and tune the buffer pool instead.
Should I use Docker memory limits for MariaDB?
Yes, if you run it in a container. Set both limits and reservations, with limits around 1.5G on a 2GB host. Without limits, a container can grow until the host OOMs and the kernel kills the database mid-write.
Related articles
- Configure swap and optimize memory on a low RAM VPS
- How to tune MySQL for a low RAM VPS
- Set up a LEMP stack with Nginx, MariaDB, PHP 8.3 on Ubuntu 24.04
- Self-host VPS monitoring with Prometheus and Grafana
2GB 内存 VPS 优化 MariaDB 要点
在 2GB 内存的 VPS 上,MariaDB 默认配置会导致内存溢出并被内核杀死。核心操作是把 InnoDB 缓冲池设为内存的 50-70%,例如共享服务器用 700M,独立数据库用 1.2G。降低最大连接数到 50,缩小排序和连接缓冲区,并在修改配置前先创建 2GB 交换分区。每次改动后用 SHOW VARIABLES 和 ps 验证实际生效值。如果使用 Docker 部署,必须同时设置容器内存上限和预留值,防止数据库容器耗尽整台主机内存。


