I used to think Apache “just worked” out of the box, until I put a moderately busy WordPress site on a small VPS and watched it fall over during a traffic spike. That was my introduction to the world of Multi-Processing Modules (MPMs), workers, and threads. Tuning these settings properly turned out to be one of the highest-leverage things I could do for server stability and performance.
Understanding Apache’s MPMs
Apache handles concurrent requests using a Multi-Processing Module, and the MPM you choose fundamentally changes how “worker” and “thread” settings behave. There are three main MPMs:
- Prefork MPM: Spawns a separate process for each connection. No threading involved. Safer for non-thread-safe modules like older versions of
mod_php, but heavier on memory. - Worker MPM: Uses multiple child processes, each managing multiple threads. More memory-efficient than prefork.
- Event MPM: An evolution of Worker MPM, optimized for handling Keep-Alive connections more efficiently by dedicating a thread to managing them separately.
You can check which MPM is currently active with:
apache2ctl -M | grep mpm
or
httpd -V | grep -i mpm
If you’re running a modern stack (PHP-FPM, or any language handled via mod_proxy_fcgi), Event MPM is almost always the right choice today. Prefork is really only necessary if you’re stuck with legacy modules that aren’t thread-safe.
Why Tuning Matters
Out of the box, Apache’s defaults are conservative and designed to work reasonably well on a wide range of hardware — which also means they’re rarely optimal for your specific server. If your settings are too low, Apache will queue or reject connections during traffic spikes. If they’re too high, Apache can spawn more processes/threads than your RAM can support, leading to swapping, which is often worse than rejecting a few connections outright.
I learned this the hard way — I once set MaxRequestWorkers far too high on a 2GB RAM server, and a moderate traffic bump caused the entire box to grind to a halt as it tried to swap memory to disk.
Prerequisites
- Root or sudo access to your server.
- Knowledge of which MPM you’re running (see above).
- An idea of your average process memory footprint (I’ll show you how to calculate this).
- Apache installed and currently functioning normally.
Step 1: Identify Your MPM Configuration File
On Debian/Ubuntu:
/etc/apache2/mods-available/mpm_event.conf
/etc/apache2/mods-available/mpm_prefork.conf
/etc/apache2/mods-available/mpm_worker.conf
On CentOS/RHEL, MPM settings usually live directly in /etc/httpd/conf.modules.d/00-mpm.conf or within httpd.conf itself.
Step 2: Understand the Key Directives
Here’s the Event MPM block as an example, since it’s the most commonly used today:
<IfModule mpm_event_module>
StartServers 3
MinSpareThreads 75
MaxSpareThreads 250
ThreadLimit 64
ThreadsPerChild 25
MaxRequestWorkers 400
MaxConnectionsPerChild 0
</IfModule>
Let me explain what each of these actually does:
- StartServers: How many child processes Apache spawns immediately at startup.
- MinSpareThreads / MaxSpareThreads: The range of idle threads Apache tries to maintain, scaling child processes up or down to hit this target.
- ThreadsPerChild: How many threads each child process manages.
- ThreadLimit: A hard ceiling on
ThreadsPerChildthat can only be changed by restarting Apache (not just reloading). - MaxRequestWorkers (formerly
MaxClients): The absolute maximum number of simultaneous connections Apache will serve. Once this cap is hit, new connections queue up. - MaxConnectionsPerChild (formerly
MaxRequestsPerChild): How many connections a child process handles before it’s recycled. Setting this to0means never recycle, which can be risky if you have memory leaks in your stack.
For Prefork MPM, it looks a bit different since there’s no threading:
<IfModule mpm_prefork_module>
StartServers 5
MinSpareServers 5
MaxSpareServers 10
MaxRequestWorkers 150
MaxConnectionsPerChild 0
</IfModule>
Step 3: Calculate Your Ideal MaxRequestWorkers
This is the single most important number to get right. The formula I use is:
MaxRequestWorkers = (Total RAM available for Apache) / (Average size of an Apache process)
To find your average Apache process size, run this while your server is under typical load:
ps -ylC apache2 --sort:rss | awk '{sum+=$8; count++} END {print sum/count/1024 " MB"}'
(Replace apache2 with httpd on CentOS/RHEL systems.)
Say your average process uses 40MB, and you want to dedicate 3GB of your server’s RAM to Apache (leaving room for MySQL, PHP-FPM, and the OS itself):
3000MB / 40MB ≈ 75 workers
That means setting MaxRequestWorkers to around 75 for a Prefork setup, or calculating the equivalent using ServerLimit × ThreadsPerChild for threaded MPMs.
Step 4: Apply Your Configuration
Edit the appropriate MPM file:
sudo nano /etc/apache2/mods-available/mpm_event.conf
Update the values based on your calculations. Then test your syntax:
sudo apache2ctl configtest
And restart Apache (a full restart is required for MPM changes, not just a reload):
sudo systemctl restart apache2
Step 5: Monitor and Iterate
After applying changes, watch how Apache behaves under real traffic:
apachectl status
Or, if mod_status is enabled, visit http://yourserver/server-status (restricted to localhost or your IP for security) to see live worker states — busy, idle, reading, writing, and so on.
Enable mod_status if it isn’t already:
sudo a2enmod status
Add this block to your config, ideally restricted to trusted IPs:
<Location "/server-status">
SetHandler server-status
Require ip 127.0.0.1
</Location>
Real-World Use Cases
- High-traffic WordPress sites: Tuning Event MPM alongside PHP-FPM to handle traffic spikes from ad campaigns or viral posts.
- API backends: Ensuring enough threads are available to serve concurrent client requests without artificial bottlenecks.
- Shared hosting environments: Balancing worker limits across multiple sites so no single site can exhaust server resources.
- Low-memory VPS instances: Deliberately capping
MaxRequestWorkerslow to prevent out-of-memory crashes.
Troubleshooting Common Issues
Problem: “server reached MaxRequestWorkers setting, consider raising the MaxRequestWorkers setting” in error logs. This means Apache hit its concurrency ceiling. Either raise the value (if you have RAM headroom) or investigate why requests are taking so long to complete in the first place — slow backend queries are a common culprit.
Problem: Server randomly becomes unresponsive under moderate load. This is almost always a sign that MaxRequestWorkers is set higher than your available RAM can support, causing swapping. Lower the value and recalculate using the memory formula above.
Problem: Changes to ThreadsPerChild don’t seem to apply. Remember that ThreadLimit caps ThreadsPerChild, and ThreadLimit requires a full restart (not reload) to take effect.
Common Mistakes to Avoid
- Copy-pasting MPM settings from a blog post (including this one!) without recalculating for your own server’s RAM and workload.
- Confusing
reloadwithrestart— MPM changes need a full restart. - Setting
MaxConnectionsPerChildto0on a server running modules with known memory leaks, which lets memory usage creep upward indefinitely. - Ignoring
mod_statusand tuning blind, purely by guesswork. - Forgetting that switching MPMs (say from Prefork to Event) may also require adjusting your PHP handler, since
mod_phpisn’t compatible with threaded MPMs.
Security Best Practices
- Restrict access to
/server-statusand/server-infoto trusted IPs only — exposing these publicly leaks internal server details. - Keep
MaxConnectionsPerChildat a reasonable non-zero value (e.g., 10,000) in production to guard against slow memory leaks from third-party modules. - Pair worker tuning with request timeouts (
Timeoutdirective) to prevent slow or malicious clients from holding threads open indefinitely.
Performance Optimization Tips
- Combine MPM tuning with
KeepAliveTimeoutadjustments (covered in my dedicated Keep-Alive post) — a too-long timeout ties up threads unnecessarily. - If you’re running PHP, pair Event MPM with PHP-FPM rather than
mod_php, since PHP-FPM manages its own worker pool independently and plays much nicer with threading. - Use caching (page-level or object-level) to reduce the actual work each request requires, which indirectly reduces how many concurrent workers you need.
- Regularly re-run your average process size calculation after major software updates, since memory footprints can shift.
Frequently Asked Questions
Which MPM should I use in 2026? Event MPM is the modern default and the right choice for the vast majority of setups, especially when paired with PHP-FPM or other FastCGI-based backends.
Do I need to restart or just reload Apache after changing MPM settings? A full restart. Reloading only re-reads certain configuration aspects; MPM-level settings require the master process to restart entirely.
How do I know if my current settings are too low? Check your error logs for “MaxRequestWorkers” warnings, and check /server-status to see if workers are consistently maxed out during peak hours.
Can I change MPM settings without downtime? A graceful restart (apachectl graceful) minimizes disruption by finishing in-flight requests before restarting, but there’s technically a brief moment where the master process restarts.
Summary and Key Takeaways
Tuning Apache’s worker and thread settings isn’t about following a universal formula — it’s about understanding your server’s actual memory budget and matching your MPM configuration to it.
Key points to remember:
- Identify your active MPM (Event is recommended for most modern stacks).
- Calculate your average Apache process memory footprint under real load.
- Set
MaxRequestWorkersbased on available RAM divided by process size, not arbitrary defaults. - Always test configuration syntax before restarting, and remember MPM changes require a full restart.
- Monitor continuously with
mod_statusand adjust as your traffic patterns evolve.
Once I started treating this as an ongoing tuning exercise rather than a one-time setup task, my servers became dramatically more stable during traffic spikes.
