Apache's defaults assume a machine with plenty of memory. On a small VPS a traffic spike can start more Apache processes than RAM can hold, the server starts swapping, and the kernel's out-of-memory killer ends MySQL or Apache itself. Tuning the MPM (Multi-Processing Module) puts a ceiling on that growth. This guide shows how to measure your processes and set limits that fit.
Before you start
- Apache running on Ubuntu, Debian, AlmaLinux or Rocky Linux, with root or
sudoaccess. - Some real traffic, or a quiet period where you can run a short load test. Measurements on an idle server are too low.
- Know what else runs on the VPS, such as MySQL, Redis or PHP-FPM. They need memory too.
Know which MPM you run
apachectl -V | grep -i mpm- prefork: one process per connection, no threads. Required only by
mod_php. Heavy on memory, because each process contains PHP. - event: a few processes, each with many threads. Idle keep-alive connections are parked cheaply. Use it with PHP-FPM.
- worker: like event, but without the keep-alive handling. There is little reason to choose it today.
If you still run prefork with mod_php, the biggest single win is moving to event and PHP-FPM. See Run PHP-FPM with Apache using proxy_fcgi. The tuning below covers both.
Measure memory per process
Under normal load, list the resident memory of Apache processes. The process name is apache2 on Ubuntu and Debian and httpd on AlmaLinux and Rocky:
ps -C apache2 -o rss= | awk '{sum+=$1; n++} END {printf "%d procs, avg %.0f MB, total %.0f MB\n", n, sum/n/1024, sum/1024}'RSS counts shared memory in every process, so the total overstates real use. That is fine; it makes your limit cautious.
Then look at overall memory:
free -mNote the memory used by everything that is not Apache: the database, PHP-FPM, the OS. Leave room for the page cache too, as it speeds up disk reads.
Size MaxRequestWorkers for prefork
With prefork, every worker is a process, so the formula is direct:
MaxRequestWorkers = (RAM for Apache in MB) / (average MB per process)Example: a 4 GB VPS where MySQL and the OS need about 1.5 GB leaves about 2.5 GB. If each Apache process averages 60 MB, 2500 / 60 is about 41. Round down to 40.
On Ubuntu and Debian edit /etc/apache2/mods-available/mpm_prefork.conf. On AlmaLinux and Rocky create /etc/httpd/conf.d/mpm.conf:
<IfModule mpm_prefork_module>
StartServers 5
MinSpareServers 5
MaxSpareServers 10
ServerLimit 40
MaxRequestWorkers 40
MaxConnectionsPerChild 2000
</IfModule>ServerLimit caps how high MaxRequestWorkers can ever go, so keep the two equal for prefork. MaxConnectionsPerChild recycles each process after a number of connections, which stops slow memory leaks from growing forever.
Size MaxRequestWorkers for event
With event, workers are threads. Threads are cheap, so PHP-FPM, not Apache, is usually the memory limit. Set Apache to accept more connections than FPM has children, and let FPM queue the rest:
<IfModule mpm_event_module>
StartServers 2
ServerLimit 4
ThreadsPerChild 25
MaxRequestWorkers 100
MinSpareThreads 25
MaxSpareThreads 75
MaxConnectionsPerChild 0
</IfModule>The rule to remember: MaxRequestWorkers must not exceed ServerLimit × ThreadsPerChild. Here that is 4 × 25 = 100. On Ubuntu and Debian this goes in /etc/apache2/mods-available/mpm_event.conf.
Size PHP-FPM's pm.max_children from its own per-process memory. Tune PHP-FPM pools walks through that calculation.
Set KeepAlive sensibly
KeepAlive lets a browser reuse one connection for several files. With prefork, every idle kept-alive connection holds a whole process, so a long timeout wastes workers. In apache2.conf or httpd.conf:
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 2A timeout of 2 to 5 seconds is a good range for prefork. The event MPM handles idle connections without tying up a worker, so the default of 5 is fine there.
Apply and watch
sudo apachectl configtest
sudo systemctl restart apache2 # or httpdWarning: Setting MaxRequestWorkers too low makes visitors wait in a queue; too high brings back the memory problem. Change it in small steps and watch memory for a day after each change.
Check it works
Search the main error log for the warning that means Apache hit its limit:
sudo grep -i "MaxRequestWorkers" /var/log/apache2/error.log # Ubuntu and Debian
sudo grep -i "MaxRequestWorkers" /var/log/httpd/error_log # AlmaLinux and RockyThe line reads server reached MaxRequestWorkers setting, consider raising the MaxRequestWorkers setting. Occasional hits during spikes are acceptable if memory stays healthy. Frequent hits with plenty of free memory mean you can raise the limit.
Also watch for swapping and OOM kills:
free -m
sudo journalctl -k | grep -i "out of memory"Troubleshooting
AH00180: WARNING: MaxRequestWorkers of X exceeds ServerLimit value: raiseServerLimit, or for event, raiseServerLimitorThreadsPerChildso their product coversMaxRequestWorkers.- Changes have no effect: another file sets the same directives later. Search with
grep -Rn MaxRequestWorkers /etc/apache2/(or/etc/httpd/), and remember a reload does not change some MPM limits; restart. - Server still runs out of memory: PHP-FPM or the database is the real consumer. Use Monitor Linux server resources to find out which.
- Site slow but memory is fine: workers are busy waiting on slow PHP or database queries. Raising limits will not help; find the slow requests.
Next steps
- Lower the work per request with Enable gzip compression in Apache.
