A WordPress database that crashes under load is almost always a memory accounting problem, not a hardware problem. Every piece of your stack, MySQL, PHP, and your caches, is allowed to claim memory at the same time, and if those promises add up to more than the box holds, the kernel kills the database at your busiest moment. The fix is to audit what each part is allowed to use, cap the numbers so the total fits your RAM with room to spare, and add an object cache so the database is asked fewer questions. Do this on the server you already have, and the site stays up when it counts.
An outage almost never picks a quiet Tuesday. It waits for your busiest moment: the campaign email that just hit ten thousand inboxes, the post that got shared around, the product drop everyone queued for. That is precisely when the most people are watching, and precisely when a dead page costs the most. Every visitor who meets an error instead of your site is a sale you already spent money to win, gone at the worst possible second, plus a scratch on the reputation that earned the visit.
On one site I run, ampy.se, the database was crashing several times a week under load. The database is where your site keeps everything: pages, orders, settings, users. When it dies, the whole site dies with it. I fixed it without buying a bigger server. I did it by working out where the memory was going and rebalancing it. The crashes stopped. This is exactly how, step by step, in a way you can copy.
The crash that taught me the real problem
The crashes on ampy.se were not random bad luck, they were arithmetic. Once I saw that the memory settings together promised more than the box held, the solution was obvious: change the numbers, not the hardware.
The part nobody warns you about
When a database crashes under load, the first instinct is to blame the hardware and buy more of it. That is usually wrong, and it is expensive. Most of the time the box has enough power. The real problem is that your software has been told it may use more memory than the box actually has.
Here is what happens. MySQL or MariaDB reserves memory for itself. PHP reserves memory for every request. Your object cache wants memory. Your page cache wants memory. Each of these settings looks reasonable on its own. Added together, they promise more RAM than exists. Under light traffic you never reach the limit, so everything looks fine. Under load, all those promises come due at once, the server runs out of RAM, and the Linux kernel steps in and kills the biggest process to save itself. That process is almost always MySQL.
This is the OOM killer, short for out of memory. It is not a bug. It is the kernel doing its job. Your job is to make sure the numbers never add up to more than the box holds.
Why it matters before we touch a config file
If you understand this one idea, you can fix the class of problem instead of the symptom. A bigger server just moves the ceiling higher. If your settings still promise more than you have, you will crash again, later, at a worse moment. Getting the memory math right means the site stays up when it matters, on the hardware you already pay for.
I stopped the crashes not by buying more hardware, but by making sure the numbers never added up to more than the box actually holds.
Step one: confirm it really is the OOM killer
Do not guess. Prove it. When MySQL vanishes, there are two places that tell you the truth.
First, the kernel log. Log into the server over SSH and look for the OOM killer in action.
sudo dmesg | grep -i -E "killed process|out of memory|oom" If you see a line like "Out of memory: Killed process 1234 (mysqld)", that is your answer. The kernel ran out of RAM and killed the database. On systems with systemd you can also read it this way.
sudo journalctl -k | grep -i "killed process" Second, the MySQL error log. A clean shutdown looks orderly. A kill looks violent, with the server suddenly restarting and mentioning that it was not shut down cleanly.
sudo tail -n 100 /var/log/mysql/error.log If the kernel log shows the kill and the MySQL log shows a crash recovery right after, you have confirmed the cause. Now you can fix the right thing.
Step two: add up what every part is allowed to use
This is the step people skip, and it is the whole game. You are going to write down, for each piece of the stack, how much RAM it can claim at peak, then compare the total to what the box has.
Find your total RAM first
free -m Note the total. Say it reads 4000 MB. That is your budget. Everything below has to fit inside it, with room left over for the operating system itself. Never plan to use the last drop. Leave a real margin.
The InnoDB buffer pool
The single biggest chunk MySQL holds is the InnoDB buffer pool. It caches your data and indexes in memory so the database does not hit the disk constantly. Bigger is faster, up to a point. But this memory is reserved, so it counts fully against your budget. Check it.
mysql -e "SHOW VARIABLES LIKE 'innodb_buffer_pool_size';" The value is in bytes. Divide by 1048576 to get megabytes. A common mistake is to set this to most of the RAM, forgetting that everything else also needs a share.
The per-connection buffers, times max connections
Here is the trap that catches almost everyone. MySQL sets aside a small amount of memory for each connection, for sorting, joining, and reading. On its own each buffer is tiny. The danger is the multiplier. If every connection can use those buffers and you allow hundreds of connections, the worst case is huge.
Look at the per-connection buffers and the connection cap together.
mysql -e "SHOW VARIABLES LIKE '%buffer_size'; SHOW VARIABLES LIKE 'max_connections';" The rough worst case for per-connection memory is the sum of the per-connection buffers multiplied by max_connections. If sort_buffer_size, join_buffer_size, and read_buffer_size add up to a few megabytes, and max_connections is 500, that is a large number that only appears under load. This is why the crash happens at your busiest moment and never in testing.
PHP, and how many workers run at once
PHP does not use one lump of memory. It runs a pool of workers, and each worker can grow up to your memory_limit. So the real cost is memory_limit times the number of PHP-FPM workers you allow. Check the limit in wp-config or php.ini, and check the worker count in your PHP-FPM pool config, the line that reads pm.max_children.
php -i | grep memory_limit
grep -E "pm.max_children|pm =" /etc/php/*/fpm/pool.d/www.conf If memory_limit is 256M and pm.max_children is 20, PHP alone can claim around 5000 MB at peak. On a 4000 MB box, that number by itself already breaks the budget before MySQL asks for anything.
The caches
Your object cache, usually Redis or Memcached, holds data in memory too. So does any full-page cache that keeps pages in RAM. Give each a firm limit, do not let them grow without a ceiling, and write those ceilings into your tally.
Put it on one line
Now add them up: InnoDB buffer pool, plus the per-connection worst case, plus PHP workers times their limit, plus the object cache ceiling, plus the operating system. If that total is bigger than your RAM, you have found your crash. It is not the hardware. It is the arithmetic.
Step three: tune the numbers so they fit
Now we make the total fit inside the budget, with margin. These are edits to the MySQL config, usually at /etc/mysql/mariadb.conf.d/50-server.cnf or a file it includes. Change values, then restart MySQL and watch.
Size the buffer pool sensibly
The old advice of giving InnoDB most of the RAM assumes the database has the box to itself. On a WordPress server it shares with PHP and caches, so be more modest. Give it enough to hold the working set of your data, but leave clear room for everything else. On a shared 4000 MB box, something in the range of a quarter to a bit under half is often sane, not most of it.
[mysqld]
innodb_buffer_pool_size = 1G The goal is that your hot data mostly lives in this pool without you having promised RAM you do not have.
Cap max connections to a number you can actually afford
This is the highest-leverage change. A high connection cap does not make you faster. It just lets the server accept more work than it can survive, which is how you get killed under load. Set the cap to a number where the per-connection worst case still fits your budget. It is far better to make a few requests wait a moment than to let the database die and take every request down with it.
[mysqld]
max_connections = 100 Pair that with a matching PHP-FPM pm.max_children, so PHP cannot open more connections than MySQL is willing to serve. The two numbers should be chosen together.
Set max_connections and pm.max_children at the same time, treating them as a pair. If PHP can spawn more workers than MySQL allows connections, the overflow hits your database as a wall of refused connections under load, which defeats the purpose of capping either number alone.
Restart and watch under real conditions
sudo systemctl restart mariadb
watch -n 2 free -m Keep an eye on the free memory as traffic comes in. You want to see it hold a comfortable margin at peak, not creep toward zero.
Step four: ask the database to do less work
Right-sizing memory stops the crash. The next move is reducing the load itself, so you reach the danger zone far less often. Two things do most of the work here.
Find the slow queries
A handful of slow, heavy queries can drag the whole database down under traffic. Turn on the slow query log to find them.
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1 This records any query that takes longer than one second. After a busy period, read the log. Often you will find one badly indexed query, or a plugin doing something wasteful, that accounts for most of the pain. Fixing or caching that one query can change everything.
Add an object cache so the same questions are not asked twice
WordPress asks the database a lot of the same questions on every page load. A persistent object cache remembers the answers in memory so the database is asked far less. This is the biggest single reduction in database load you can make on a WordPress site.
Install Redis on the server, then a WordPress drop-in that uses it. Keep your credentials out of the code by reading them from wp-config constants, never hard-coding them.
// In wp-config.php, above the "stop editing" line.
define( 'WP_REDIS_HOST', '127.0.0.1' );
define( 'WP_REDIS_PORT', 6379 );
define( 'WP_REDIS_PASSWORD', getenv( 'REDIS_PASSWORD' ) );
define( 'WP_CACHE', true ); Give Redis its own firm memory ceiling and a policy that discards the least useful entries when full, so it can never grow into the space the database needs.
# In /etc/redis/redis.conf
maxmemory 256mb
maxmemory-policy allkeys-lru Remember to add that 256mb back into your memory tally. The cache is worth the space, because every question it answers is one the database does not have to.
Prove it works
Do not trust a fix you have not stressed. Send the site some real load and watch the memory hold.
ab -n 2000 -c 100 https://YOUR_DOMAIN/ While that runs, keep free -m open in another window. Success looks like this: the free memory dips but keeps a healthy margin, MySQL stays alive, dmesg shows no new kills, and the slow query log stays quiet. Run it again harder. A fix that survives being pushed is a fix you can trust on your busiest day.
The payoff, in business terms
Here is what this actually buys you, past the jargon.
- Your site stays up during the exact moments that matter most: the campaign, the viral post, the launch, when the most customers are watching and downtime is most expensive.
- You keep the sales and sign-ups you paid to attract, instead of losing them to an error page at the finish line.
- You protect trust. A site that dies under attention teaches people not to rely on it. A site that holds steady earns the opposite.
- You avoid paying for a bigger server you did not need, because the real fix was settings, not hardware.
You do not need a magic plan or an expensive upgrade. You need to know where your memory goes, make the numbers add up to less than the box holds, cap the connections you cannot afford, and ask the database fewer questions. Build that, and the site stays up precisely when it counts.