Websites & apps

Tune Apache MPM settings to stop memory exhaustion

Size Apache's MaxRequestWorkers to the RAM you really have, choose between prefork and event, set ServerLimit and KeepAlive sensibly and spot the MaxRequestWorkers warning in your logs.

AdvancedLinux server5 min read

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 sudo access.
  • 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

Bash
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:

Bash
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:

Bash
free -m

Note 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:

Apache
<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:

Apache
<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:

Apache
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 2

A 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

Bash
sudo apachectl configtest
sudo systemctl restart apache2    # or httpd

Warning: 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:

Bash
sudo grep -i "MaxRequestWorkers" /var/log/apache2/error.log   # Ubuntu and Debian
sudo grep -i "MaxRequestWorkers" /var/log/httpd/error_log      # AlmaLinux and Rocky

The 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:

Bash
free -m
sudo journalctl -k | grep -i "out of memory"

Troubleshooting

  • AH00180: WARNING: MaxRequestWorkers of X exceeds ServerLimit value: raise ServerLimit, or for event, raise ServerLimit or ThreadsPerChild so their product covers MaxRequestWorkers.
  • 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

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.