How to Optimize MySQL my.cnf on a Small VPS (1-2 GB RAM) Without Crashing
Learn to tune MySQL my.cnf on a small VPS: size the InnoDB buffer pool, disable performance_schema, and cut RAM without losing throughput.
A 1 or 2 GB RAM VPS is where MySQL on defaults starts to hurt. The mysqld
process takes ~450 MB at boot, your PHP/Node app gets squeezed, the kernel
starts killing processes via OOM, and swap kicks in destroying I/O. The
problem is rarely MySQL itself — it is the default my.cnf, calibrated
for a generic 8 GB server.
This tutorial covers the my.cnf adjustments that actually matter on a
small VPS: sizing innodb_buffer_pool_size to the real machine, turning
off performance_schema when it makes sense, controlling connections
and the binary log, and validating everything with commands you run
before and after. The persona is a developer who spun up MySQL 8 or
MariaDB on a 1-2 GB droplet, saw mysqld consume 40% of RAM, and wants
to shrink it without losing performance.
Execution time: 20-30 minutes including a backup of the original
my.cnf, editing, restart, and measurement. The adjustments apply to
MySQL 8.0+ and MariaDB 10.6+ on Ubuntu 22.04/24.04 LTS and Debian 12.
Prerequisites
A Linux VPS (Ubuntu 22.04/24.04 LTS or Debian 12) with 1 or 2 GB of RAM,
MySQL 8.0+ or MariaDB 10.6+ installed and running, sudo access, and
the MySQL root credentials. Back up your database before anything —
mysqldump --single-transaction --all-databases > backup.sql — because
a restart with a broken my.cnf can prevent the service from starting.
Confirm what you have today before touching a single line:
free -m ps -o rss= -p $(pidof mysqld) mysql --version /etc/mysql/mysql.conf.d/mysqld.cnf On MariaDB, the main file is /etc/mysql/mariadb.conf.d/50-server.cnf.
In both, the rule is the same: do not edit /etc/mysql/my.cnf directly —
it only performs !includedir of the files in the mysql.conf.d/ or
mariadb.conf.d/ directories.
Diagnosing current consumption
Before cutting, measure. Without a baseline, you have no idea whether the tuning helped.
Check the current mysqld RSS and how much of total RAM that is:
ps -o pid,rss,cmd -p $(pidof mysqld)
free -mThe rss value is in KB. Divide by 1024 to get MB. On a 1 GB VPS with
default MySQL 8, expect something between 380 and 500 MB — well above
what is needed for the real load of a small site.
Look at real InnoDB buffer pool usage — whether the default 128 MB pool has headroom or is undersized:
mysql -u root -p -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages%';"How to read it:
pages_total= pool size in 16 KB pagespages_freenear zero = pool is full, MySQL is recycling pagespages_data/pages_total= how much is in productive use
If pages_free is high (>30% of total) and the server has days of
uptime, you can shrink the pool without harm. If it is near zero and
Innodb_buffer_pool_reads (not read_requests) grows fast, the pool
is undersized.
Check performance_schema — alone it eats 70-100 MB:
mysql -u root -p -e "SELECT SUM(MEMORY_USED)/1024/1024 AS mb FROM sys.x\$memory_global_by_current_bytes;"On a 1 GB VPS with no external profiling tool reading from it, this is the first candidate to switch off.
Editing my.cnf
Create your own override file instead of editing the default
mysqld.cnf — that way, package updates do not overwrite your tuning.
Create the override file:
sudo nano /etc/mysql/mysql.conf.d/99-hostini-tuning.cnfOn MariaDB: /etc/mysql/mariadb.conf.d/99-hostini-tuning.cnf. The 99
ensures this file is read last — overriding any default.
Paste this base configuration for a 1 GB RAM VPS:
[mysqld]
# InnoDB — the heart of MySQL
innodb_buffer_pool_size = 256M
innodb_buffer_pool_instances = 1
innodb_log_buffer_size = 8M
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit = 2
# Connections — a small VPS cannot handle 150
max_connections = 50
thread_cache_size = 8
table_open_cache = 200
table_definition_cache = 400
# Performance schema — off on 1 GB
performance_schema = OFF
# Binary log — off if you do NOT use replication
disable_log_bin
# On MariaDB use: skip-log-bin
# Slow query log for diagnosis
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
# Temp tables — in RAM up to this limit
tmp_table_size = 16M
max_heap_table_size = 16M
# Sort/join buffers — per connection, watch max_connections
sort_buffer_size = 256K
join_buffer_size = 256K
read_buffer_size = 128K
read_rnd_buffer_size = 256KFor a 2 GB VPS, tweak only these lines:
innodb_buffer_pool_size = 512M
max_connections = 100
performance_schema = ON
performance-schema-instrument='memory/%=COUNTED'sort_buffer_size and join_buffer_size are allocated PER ACTIVE
CONNECTION, not globally. Bumping each to 4 MB with 100 connections =
400 MB potentially in those buffers alone. The low values in the
snippet (256K) are intentional — a slow query on a small VPS is
solved with an index, not a bigger buffer.
Save (Ctrl+O, Enter, Ctrl+X) and validate the syntax before the
restart:
sudo mysqld --validate-configIf any error appears, the service will not come up after the restart. Fix it before continuing. Expected output: no lines (silence is success).
Restart MySQL and check the status:
sudo systemctl restart mysql
sudo systemctl status mysql --no-pagerOn MariaDB: sudo systemctl restart mariadb. If the status shows
active (running), move on. If it failed, read the reason in
sudo journalctl -u mysql -n 50 — it is almost always a typo in the
.cnf.
Verification
Confirm the tuning took effect and the RAM savings showed up.
Confirm the values that are now in effect:
mysql -u root -p -e "SHOW VARIABLES WHERE Variable_name IN ('innodb_buffer_pool_size', 'max_connections', 'performance_schema', 'log_bin', 'tmp_table_size');"The values should match what you put in 99-hostini-tuning.cnf. If any
still shows the old default, the file is not being read — check the
path and permissions (it must be 644 root:root).
Measure RAM consumption again:
ps -o pid,rss,cmd -p $(pidof mysqld)
free -mExpectation post-tuning on a 1 GB VPS: mysqld at ~280-340 MB (a ~30%
drop). On 2 GB with performance_schema on: ~500-580 MB. Wait a few
minutes with real traffic before the final measurement — the pool
warms up and stabilizes.
Run SHOW STATUS after 1-2 hours of real use and confirm the buffer pool is not choking:
mysql -u root -p -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_%';"Signs of health: pages_free > 5% of pages_total, reads growing
slowly compared to read_requests (hit ratio > 99%). If reads
spikes, bump the buffer pool by 64 MB and re-test.
Troubleshooting
MySQL fails to start after the restart
Check the journal log and find the exact line that broke:
sudo journalctl -u mysql -n 80 --no-pager
Common causes: innodb_buffer_pool_size larger than the total
available RAM, O_DIRECT on a filesystem that does not support it
(rare on ext4/xfs), a deprecated option in MySQL 8 (query_cache_size
was removed). Revert your 99-hostini-tuning.cnf to a minimum base
(only innodb_buffer_pool_size) and add lines back one at a time.
”Out of memory” in the application after tuning
You reduced MySQL’s RAM but the app grew to fill the void. Run
free -m during real load and watch whether available drops below
100 MB. If it does, consider lowering swappiness
(sudo sysctl vm.swappiness=10) and enable a 1 GB swap file as a
safety net — not a production solution, but it prevents OOM kill
while you size things better.
Queries that used to be fast are now slow
Classic symptom of a buffer pool that became too small — the table
used to fit in RAM, now it does not. Check
Innodb_buffer_pool_reads (not read_requests) on SHOW GLOBAL STATUS before and after. If it climbed sharply, raise
innodb_buffer_pool_size by 64 MB and measure again. Structural fix:
add an index to the query, do not inflate the pool.
Next steps
With my.cnf tuned, it is worth consolidating the operation:
- Configure rotation for the slow log (
slow.loggrows fast) vialogrotatein/etc/logrotate.d/mysql-server - Schedule a daily
mysqldump --single-transactionwithcronand ship it to external storage (rclone + S3 or Backblaze B2) - In production with replication or point-in-time recovery, keep
log_binon and setexpire_logs_days = 7to control disk - Consider moving to a VPS with more RAM if the 256 MB pool cannot keep up — generally, jumping to 4 GB eliminates fine tuning and frees you to focus on the product
A Hostini VPS ships with NVMe SSD and a kernel tuned for database
workloads — O_DIRECT works without silent fallback and predictable
I/O reduces the need for an oversized buffer pool to compensate for
slow disk latency.
Frequently asked questions
How much RAM does MySQL 8 use by default without tuning?
On a stock MySQL 8.0 install on Ubuntu/Debian, the mysqld process sits between 380 and 500 MB of RSS at boot, with no active connections. That comes mostly from innodb_buffer_pool_size (128 MB), performance_schema (~80 MB), and the mysql.* system tables. On a 1 GB VPS, that alone eats ~40% of RAM before a single query runs.
Can I turn off performance_schema without breaking anything?
Yes, with caveats. performance_schema powers the sys.* views and tools like percona-toolkit's pt-query-digest. Turning it off saves 70-100 MB of RAM but you lose fine-grained visibility into slow queries. Recommended on a 1 GB VPS; on 2 GB or more, keep it on and tune just the consumers via setup_consumers.
What is the ideal innodb_buffer_pool_size on a small VPS?
The 70-80% of physical RAM rule only applies to servers dedicated to MySQL. On a 1 GB VPS running MySQL plus a web app plus nginx, keep it between 256 and 384 MB. On a 2 GB VPS, 512 to 768 MB. The rest goes to the app, OS cache, and a margin against the OOM killer.
Is it worth disabling the binary log (log_bin) on a small VPS?
Only disable it if you do NOT use replication or point-in-time recovery. The binary log eats IOPS (sequential writes) and disk (~10-20% of write traffic). Without it, restore only rewinds to the last mysqldump. For a single instance with acceptable daily backups, disabling is fine; for any setup that may become a replica later, keep it on.
How do I know if my tuning actually worked?
Run SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_pages%' after 24h of real traffic. If Innodb_buffer_pool_pages_free stays near zero and Innodb_buffer_pool_reads grows fast, the pool is too small. Combine that with free -m to confirm the system is not swapping. The mysqld RSS in ps aux should stabilize after warm-up.
Can I use MariaDB instead of MySQL to save RAM?
MariaDB 10.6+ has a footprint similar to MySQL 8.0 — there is no magic saving. The real difference comes from my.cnf tuning, not from the fork. MariaDB ships some more conservative defaults (innodb_buffer_pool_size=128M as well), so the same adjustments in this tutorial apply with `[mariadb]` in place of `[mysqld]` where applicable.