{"id":3289,"date":"2026-09-29T11:09:38","date_gmt":"2026-09-29T11:09:38","guid":{"rendered":"https:\/\/siteharbour.com\/blog\/?p=3289"},"modified":"2026-09-30T10:05:38","modified_gmt":"2026-09-30T10:05:38","slug":"optimize-mysql-mariadb-low-ram-wordpress","status":"publish","type":"post","link":"https:\/\/siteharbour.com\/blog\/optimize-mysql-mariadb-low-ram-wordpress\/","title":{"rendered":"How to Optimize MySQL and MariaDB on a Low-RAM WordPress Server"},"content":{"rendered":"<p>Give the InnoDB buffer pool enough memory to hold the data WordPress actually reads, which on a small site is often 128 to 256 MB, cap connections and in-memory temporary tables, leave the durability settings alone, and use the slow query log to find the queries that really cost you time. Measure first: a database that already fits in memory gains nothing from a bigger buffer pool. This guide covers MySQL 8.0 and MariaDB, calls out the differences, and works together with the guides on <a href=\"\/blog\/optimize-php-fpm-workers-limited-ram\/\">PHP-FPM workers for limited RAM<\/a> and <a href=\"\/blog\/optimize-wordpress-1gb-ram-server\/\">optimizing WordPress on a 1 GB server<\/a>.<\/p>\n<p>The commands assume Ubuntu 24.04. Before changing anything, take a backup of the databases and the configuration, as shown in Step 6.<\/p>\n<h2>Where database memory goes<\/h2>\n<p>Five areas account for almost all of a small WordPress database&#8217;s memory:<\/p>\n<ul>\n<li><strong>The InnoDB buffer pool.<\/strong> It caches table and index pages and is the largest fixed allocation. The default is 128 MB in both MySQL 8.0 and MariaDB.<\/li>\n<li><strong>Per-connection memory.<\/strong> A connection can allocate sort, join and read buffers when a query needs them. Many connections multiplied by large buffers is how memory disappears.<\/li>\n<li><strong>Temporary tables.<\/strong> Queries with <code>GROUP BY<\/code>, <code>ORDER BY<\/code> or <code>DISTINCT<\/code> can build temporary tables in memory. In MySQL 8.0 the TempTable engine may use up to 1 GiB of RAM for them across all connections by default, which is far too generous for a small server.<\/li>\n<li><strong>Performance Schema.<\/strong> MySQL enables this monitoring feature by default, and it holds its own memory.<\/li>\n<li><strong>Minor caches.<\/strong> Table caches, the thread cache and, on MariaDB, the MyISAM key buffer and Aria page cache. MariaDB ships larger defaults for the last two than a pure InnoDB WordPress site needs, so check them with <code>SHOW VARIABLES<\/code> if you run MariaDB.<\/li>\n<\/ul>\n<p>On the 1 GB server measured in the other guides, a MySQL 8.0 process was using about 188 MB resident out of 911 MiB, roughly a fifth of all RAM, while serving a Laravel application and a WordPress blog. That makes the database the largest single consumer on the machine, so it is worth understanding before you touch it.<\/p>\n<h2>Step 1: Measure how big your data really is<\/h2>\n<p>The buffer pool only needs to hold the data you actually use, so start with the size of your databases:<\/p>\n<pre><code>sudo mysql -e \"SELECT table_schema AS db, ROUND(SUM(data_length+index_length)\/1024\/1024,1) AS size_mb FROM information_schema.tables GROUP BY table_schema ORDER BY size_mb DESC;\"<\/code><\/pre>\n<p>Then look inside your WordPress database (replace <code>wordpress<\/code> with its name) for the largest tables:<\/p>\n<pre><code>sudo mysql -e \"SELECT table_name, ROUND((data_length+index_length)\/1024\/1024,1) AS size_mb FROM information_schema.tables WHERE table_schema='wordpress' ORDER BY (data_length+index_length) DESC LIMIT 10;\"<\/code><\/pre>\n<p>On a small site the whole database is usually tens of megabytes. Unexpectedly large tables are worth a look: <code>wp_options<\/code> full of expired transients or autoloaded plugin data, <code>wp_postmeta<\/code> bloated by an old plugin, or logging tables from scheduled-action and security plugins.<\/p>\n<h2>Step 2: See what the database is doing now<\/h2>\n<p>Check the current values and how the server has behaved since it last started:<\/p>\n<pre><code>sudo mysql -e \"SHOW VARIABLES WHERE Variable_name IN ('innodb_buffer_pool_size','max_connections','tmp_table_size','max_heap_table_size','performance_schema','temptable_max_ram');\"\nsudo mysql -e \"SHOW GLOBAL STATUS WHERE Variable_name IN ('Uptime','Max_used_connections','Innodb_buffer_pool_reads','Innodb_buffer_pool_read_requests','Created_tmp_tables','Created_tmp_disk_tables');\"\nps -C mysqld,mariadbd -o comm=,rss=<\/code><\/pre>\n<p>(<code>temptable_max_ram<\/code> exists only on MySQL 8.0; MariaDB simply will not list it.) Read the results like this:<\/p>\n<ul>\n<li><strong><code>Uptime<\/code><\/strong> is in seconds. Statistics from a database that restarted an hour ago tell you very little.<\/li>\n<li><strong><code>Max_used_connections<\/code><\/strong> is the highest number of simultaneous connections since startup. It is the honest basis for <code>max_connections<\/code>.<\/li>\n<li><strong>The buffer pool hit ratio<\/strong> is <code>1 - (Innodb_buffer_pool_reads \/ Innodb_buffer_pool_read_requests)<\/code>. A well-sized pool on a read-heavy site normally shows 99 percent or better. A cold pool right after a restart will not.<\/li>\n<\/ul>\n<p>On MySQL you can also ask the server where its memory is allocated, as long as Performance Schema is on. With it off, these views return NULL or nothing:<\/p>\n<pre><code>sudo mysql -e \"SELECT * FROM sys.memory_global_total;\"\nsudo mysql -e \"SELECT event_name, current_alloc FROM sys.memory_global_by_current_bytes LIMIT 10;\"<\/code><\/pre>\n<h2>Step 3: Size the InnoDB buffer pool<\/h2>\n<p>The rule is to make the pool large enough for the data and indexes you read regularly, and no larger, because every megabyte you give it is taken from PHP workers and the filesystem cache. On a 1 GB server shared with PHP and the operating system, 128 to 256 MB is a common range for small WordPress sites. As a hypothetical example, if Step 1 shows 90 MB in total, a 128 MB pool holds everything with room to grow, and giving it 512 MB would only starve the rest of the server.<\/p>\n<p>Once your normal traffic has run for a day, check the hit ratio from Step 2. If it is well below 99 percent and your data really does not fit, raise the pool a little at a time. You can resize it without a restart to test a value:<\/p>\n<pre><code>sudo mysql -e \"SET GLOBAL innodb_buffer_pool_size = 268435456;\"<\/code><\/pre>\n<p>That is 256 MB in bytes. The change is not permanent, so put the value you settle on in the configuration file in Step 6.<\/p>\n<h2>Step 4: Cap connections and temporary tables<\/h2>\n<p>Set <code>max_connections<\/code> a little above the highest <code>Max_used_connections<\/code> you have seen, with room for cron jobs, backups and your own admin sessions. If PHP-FPM is limited to a handful of workers, 40 is generous. Do not set it too low, because WordPress then shows &#8220;Error establishing a database connection&#8221; as soon as the limit is reached.<\/p>\n<p>Limit in-memory temporary tables. <code>tmp_table_size<\/code> and <code>max_heap_table_size<\/code> together set the largest in-memory temporary table, so lower both to the same modest value such as 32 MB. If <code>Created_tmp_disk_tables<\/code> is a large share of <code>Created_tmp_tables<\/code>, a few queries are spilling to disk, and the slow query log in Step 7 will show which.<\/p>\n<p>On MySQL 8.0 only, also cap the TempTable engine:<\/p>\n<pre><code>temptable_max_ram = 64M<\/code><\/pre>\n<p>Do not put that line in a MariaDB configuration file. MariaDB does not recognise the variable and will refuse to start.<\/p>\n<p>Leave <code>sort_buffer_size<\/code>, <code>join_buffer_size<\/code>, <code>read_buffer_size<\/code> and <code>read_rnd_buffer_size<\/code> at their defaults. Guides that raise them to several megabytes multiply that cost by every connection that needs them.<\/p>\n<h2>Step 5: Decide whether to turn off Performance Schema<\/h2>\n<p>Performance Schema is MySQL&#8217;s built-in monitoring. It also holds memory, and on a very small server you may prefer to get that back. First check whether it is already off: the <code>performance_schema<\/code> line in the Step 2 output shows <code>ON<\/code> or <code>OFF<\/code>, and if it says <code>OFF<\/code> there is nothing to do in this step. If it is on, the <code>sys.memory_global_by_current_bytes<\/code> query in Step 2 shows rows named <code>memory\/performance_schema\/...<\/code>, which tell you how much it uses on your server. If the total is significant and you do not rely on the diagnostics, add this and restart:<\/p>\n<pre><code>performance_schema = OFF<\/code><\/pre>\n<p>The trade-off is that the <code>sys<\/code> views stop working, so you lose that visibility until you enable it again and restart. The slow query log in Step 7 is separate and keeps working. If you are unsure, leave it on and start with the other settings.<\/p>\n<h2>Step 6: Apply the changes safely<\/h2>\n<p>First take a backup of the database and the configuration (replace <code>wordpress<\/code> with your database name):<\/p>\n<pre><code>sudo mysqldump --single-transaction --databases wordpress | gzip &gt; ~\/wordpress-$(date +%F).sql.gz\nsudo cp -a \/etc\/mysql ~\/mysql-config-backup-$(date +%F)<\/code><\/pre>\n<p>Put your settings in a separate override file so package updates never overwrite them. On MySQL, name it so that it loads after the packaged <code>mysqld.cnf<\/code>, for example <code>\/etc\/mysql\/mysql.conf.d\/zz-lowram.cnf<\/code>. On MariaDB use <code>\/etc\/mysql\/mariadb.conf.d\/99-lowram.cnf<\/code>:<\/p>\n<pre><code>[mysqld]\ninnodb_buffer_pool_size = 128M\nmax_connections = 40\ntmp_table_size = 32M\nmax_heap_table_size = 32M<\/code><\/pre>\n<p>These are starting points, not universal answers. Replace them with the values your own Steps 1 to 3 justify, and add the MySQL-only lines from Steps 4 and 5 if they apply. Then validate, restart, and confirm the values took effect:<\/p>\n<pre><code>sudo mysqld --validate-config\nsudo systemctl restart mysql\nsudo mysql -e \"SHOW VARIABLES LIKE 'innodb_buffer_pool_size';\"\nsudo tail -n 20 \/var\/log\/mysql\/error.log<\/code><\/pre>\n<p>On MariaDB, restart the <code>mariadb<\/code> service instead and skip <code>--validate-config<\/code>. A restart interrupts the site for a few seconds, so pick a quiet time. To roll back, delete your override file and restart. Always confirm the new values with <code>SHOW VARIABLES<\/code>, because a value set in a file that loads earlier can be silently overridden by a later one.<\/p>\n<h2>Step 7: Find the slow queries<\/h2>\n<p>Tuning memory cannot fix a bad query. The slow query log shows which queries are slow. Enable it for testing without a restart:<\/p>\n<pre><code>sudo mysql -e \"SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1;\"<\/code><\/pre>\n<p>New connections pick up the threshold, and MySQL writes to its default slow log file unless you configure another. To make it permanent, add these lines to your override file:<\/p>\n<pre><code>slow_query_log = 1\nslow_query_log_file = \/var\/log\/mysql\/mysql-slow.log\nlong_query_time = 1<\/code><\/pre>\n<p>After a day of normal traffic, summarise the log, sorted by total time:<\/p>\n<pre><code>sudo mysqldumpslow -s t -t 10 \/var\/log\/mysql\/mysql-slow.log<\/code><\/pre>\n<p>Repeated slow queries usually point at a plugin. Use <code>EXPLAIN<\/code> to see whether a query is scanning a whole table, and check the size of autoloaded options, expired transients and old revisions, as described in the <a href=\"\/blog\/optimize-wordpress-1gb-ram-server\/\">1 GB WordPress guide<\/a>. Make sure logrotate covers the slow log so it cannot fill the disk.<\/p>\n<h2>What this looked like on a real 1 GB server<\/h2>\n<p>These are real numbers from the MySQL 8.0 server on Ubuntu 24.04 that runs a Laravel application and a WordPress blog on 911 MiB of usable RAM. They were taken after 4 hours and 22 minutes of uptime, so treat them as an example of what to read, not as targets.<\/p>\n<table>\n<thead>\n<tr>\n<th>Setting<\/th>\n<th>Value<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>innodb_buffer_pool_size<\/code><\/td>\n<td>134217728 (128 MiB, the default)<\/td>\n<\/tr>\n<tr>\n<td><code>max_connections<\/code><\/td>\n<td>50<\/td>\n<\/tr>\n<tr>\n<td><code>tmp_table_size<\/code><\/td>\n<td>16777216 (16 MiB, the default)<\/td>\n<\/tr>\n<tr>\n<td><code>temptable_max_ram<\/code><\/td>\n<td>1073741824 (1 GiB, the default)<\/td>\n<\/tr>\n<tr>\n<td><code>performance_schema<\/code><\/td>\n<td>OFF<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<table>\n<thead>\n<tr>\n<th>Status counter<\/th>\n<th>Value<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>Uptime<\/code><\/td>\n<td>15,723 seconds (4 h 22 min)<\/td>\n<\/tr>\n<tr>\n<td><code>Max_used_connections<\/code><\/td>\n<td>3<\/td>\n<\/tr>\n<tr>\n<td><code>Innodb_buffer_pool_read_requests<\/code><\/td>\n<td>549,229<\/td>\n<\/tr>\n<tr>\n<td><code>Innodb_buffer_pool_reads<\/code><\/td>\n<td>1,414<\/td>\n<\/tr>\n<tr>\n<td><code>Created_tmp_tables<\/code><\/td>\n<td>1,187<\/td>\n<\/tr>\n<tr>\n<td><code>Created_tmp_disk_tables<\/code><\/td>\n<td>0<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>What they say:<\/p>\n<ul>\n<li><strong>The buffer pool is already big enough.<\/strong> The hit ratio is 1 &#8211; (1,414 \u00f7 549,229), about 99.74 percent, so almost every read comes from memory. The misses include the reads that warm the pool after a restart, so the steady-state figure is likely higher. The default 128 MiB is enough here, and raising it would only take memory from PHP and the filesystem cache. For scale, the process measured about 188 MB resident in an earlier snapshot, and the buffer pool is roughly two-thirds of that.<\/li>\n<li><strong>The connection limit is generous.<\/strong> The busiest moment used 3 connections against a limit of 50. A high limit costs nothing until connections are actually used, so there is no reason to change it.<\/li>\n<li><strong>Temporary tables stay in memory.<\/strong> 1,187 were created and none went to disk, so the 16 MiB limit is not being hit. The 1 GiB <code>temptable_max_ram<\/code> default is larger than this machine&#8217;s total RAM. Nothing approaches it today, but capping it at something like 64M is a cheap safeguard against a runaway query.<\/li>\n<li><strong>Performance Schema is off,<\/strong> which is why <code>sys.memory_global_total<\/code> returned NULL. That is the trade-off from Step 5: it saves memory, but the memory views need it. On this server, use <code>ps<\/code> to see what the database process uses.<\/li>\n<\/ul>\n<p>The conclusion is that this database needed no tuning, and that is a legitimate result. Measuring first showed it was already right-sized, and the right move was to leave it alone.<\/p>\n<h2>Common mistakes<\/h2>\n<ul>\n<li><strong>Copying a large &#8220;my.cnf for performance&#8221; from a guide.<\/strong> Those files are written for big servers and can allocate more than your whole machine has.<\/li>\n<li><strong>Raising per-connection buffers.<\/strong> Their cost multiplies with every connection.<\/li>\n<li><strong>Weakening durability for speed.<\/strong> Leave <code>innodb_flush_log_at_trx_commit<\/code> at its default. A crash can then cost you committed data.<\/li>\n<li><strong>Making the buffer pool bigger than the data.<\/strong> It gains nothing and starves PHP and the filesystem cache.<\/li>\n<li><strong>Trusting a tuning script blindly.<\/strong> Scripts such as MySQLTuner give useful hints, but their advice is generic and often suggests raising values. Treat it as a checklist to investigate, not instructions.<\/li>\n<li><strong>Tuning without a backup.<\/strong> Take the dump and the config copy first, every time.<\/li>\n<\/ul>\n<h2>When tuning is not enough<\/h2>\n<p>If the buffer pool hit ratio stays low because the working data genuinely exceeds the RAM you can spare, or the server swaps under ordinary load, the fix is more memory or a separate database server, not more settings. You can move to <a href=\"\/wordpress\">managed WordPress hosting<\/a>, where the database stack is already tuned, or to a <a href=\"\/dedicated-server\">dedicated server<\/a> for sustained high traffic.<\/p>\n<h2>Frequently asked questions<\/h2>\n<h3>How much RAM should MySQL get on a 1 GB server?<\/h3>\n<p>There is no fixed answer. Measure your data size, keep the buffer pool close to it, and leave enough for PHP workers and the filesystem cache. For a small WordPress site, a buffer pool of 128 to 256 MB is a common range.<\/p>\n<h3>Is MariaDB lighter than MySQL?<\/h3>\n<p>There is no reliable general rule. Both run well on small servers, their defaults differ, and the results depend on your workload. Measure your own server instead of switching on a rumour.<\/p>\n<h3>Should I use MySQLTuner?<\/h3>\n<p>You can run it as a read-only check, but treat its recommendations as things to investigate, not settings to paste. It knows nothing about your PHP workers or the rest of your memory budget.<\/p>\n<h3>Do I need a separate database server?<\/h3>\n<p>Not until measurements say so. If the database and PHP fit comfortably together and the server is not swapping, keeping them on one machine is simpler and faster.<\/p>\n<p>The short version: measure the data, size the buffer pool to it, cap connections and temporary tables, verify every change with <code>SHOW VARIABLES<\/code>, and let the slow query log point you at the real problems.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A measure-first guide to tuning MySQL or MariaDB on a small WordPress server without risking your data or your uptime.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[16],"tags":[30,22,29,28,23],"class_list":["post-3289","post","type-post","status-publish","format-standard","hentry","category-servers","tag-innodb-buffer-pool","tag-low-ram-server","tag-mariadb","tag-mysql","tag-wordpress-performance"],"_links":{"self":[{"href":"https:\/\/siteharbour.com\/blog\/wp-json\/wp\/v2\/posts\/3289","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/siteharbour.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/siteharbour.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/siteharbour.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/siteharbour.com\/blog\/wp-json\/wp\/v2\/comments?post=3289"}],"version-history":[{"count":2,"href":"https:\/\/siteharbour.com\/blog\/wp-json\/wp\/v2\/posts\/3289\/revisions"}],"predecessor-version":[{"id":3292,"href":"https:\/\/siteharbour.com\/blog\/wp-json\/wp\/v2\/posts\/3289\/revisions\/3292"}],"wp:attachment":[{"href":"https:\/\/siteharbour.com\/blog\/wp-json\/wp\/v2\/media?parent=3289"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/siteharbour.com\/blog\/wp-json\/wp\/v2\/categories?post=3289"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/siteharbour.com\/blog\/wp-json\/wp\/v2\/tags?post=3289"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}