How to Reduce Apache’s Memory Usage

How to reduce Apache's memory usage

There was a stretch a few years back where I was running several small client sites on a single 1GB RAM VPS, and Apache kept getting OOM-killed by the kernel during traffic bumps. That forced me to get serious about trimming Apache’s memory footprint rather than just throwing more RAM at the problem (which, frankly, isn’t always an option on a budget).

Why Apache’s Memory Usage Gets Out of Hand

Apache’s memory consumption is driven primarily by three things:

  1. The MPM in use (Prefork spawns a full process per connection — expensive; Event/Worker share memory across threads — cheaper).
  2. Loaded modules, many of which you might not actually need.
  3. The application layer behind Apache (like mod_php embedding the entire PHP interpreter into every Apache process).

Each of these compounds the others. A Prefork setup running mod_php is often the worst-case scenario for memory usage, since every single process carries a full copy of PHP in memory, whether or not that particular request needs it.

Prerequisites

  • Sudo/root access to your server.
  • Ability to restart Apache safely (ideally during low-traffic hours the first time).
  • A baseline measurement of current memory usage, so you can prove the improvements to yourself afterward.

Step 1: Measure Your Current Memory Baseline

Before changing anything, establish where you stand:

ps -ylC apache2 --sort:rss | awk '{sum+=$8; count++} END {print "Average process size: " sum/count/1024 " MB, Total processes: " count}'

Also check overall system memory:

free -h

Write these numbers down. I always forget how satisfying it is to see the “after” numbers if I don’t record the “before” first.

Step 2: Switch to Event MPM (If You Haven’t Already)

If you’re still on Prefork MPM, switching to Event MPM is usually the single biggest memory win available, since threads inside a process share memory far more efficiently than fully separate processes.

sudo a2dismod mpm_prefork
sudo a2enmod mpm_event

If you’re using mod_php, note that it’s not compatible with threaded MPMs like Event or Worker. You’ll need to migrate to PHP-FPM first (see Step 3).

Step 3: Move from mod_php to PHP-FPM

This was the change that had the biggest impact for me personally. mod_php embeds the PHP interpreter directly into every single Apache worker process, ballooning memory usage even for requests that serve plain static files.

Install PHP-FPM:

sudo apt install php-fpm

Enable the required Apache modules:

sudo a2enmod proxy_fcgi setenvif
sudo a2enconf php8.3-fpm

(Adjust the PHP version number to match what you have installed.)

Disable the old mod_php module if it’s active:

sudo a2dismod php8.3

Restart Apache:

sudo systemctl restart apache2
sudo systemctl restart php8.3-fpm

Now, Apache workers stay lightweight, and PHP execution happens in a separate, independently-managed pool of processes that only spin up as needed.

Step 4: Disable Unnecessary Modules

Every loaded module adds to Apache’s baseline memory footprint, even if it’s rarely exercised. List your currently enabled modules:

apache2ctl -M

Common modules I’ve been able to safely disable on many servers (only if you’re not using their functionality):

sudo a2dismod autoindex
sudo a2dismod status        # only if you don't need /server-status
sudo a2dismod negotiation
sudo a2dismod cgid

Be careful here — don’t disable something like mod_rewrite or mod_ssl if you actually depend on it. Test after each change.

Step 5: Tune MaxRequestWorkers and ThreadsPerChild

Referring back to my Apache worker tuning post, set MaxRequestWorkers to match your actual available RAM rather than leaving Apache’s generous defaults in place. Lower concurrency ceilings directly cap how much memory Apache can consume at peak.


    StartServers             2
    MinSpareThreads          25
    MaxSpareThreads          75
    ThreadsPerChild          25
    MaxRequestWorkers        100
    MaxConnectionsPerChild   1000

Notice MaxConnectionsPerChild is set to a non-zero value here — recycling processes periodically helps prevent slow memory creep from any module with a memory leak.

Step 6: Enable Compression to Reduce Bandwidth-Related Memory Pressure

While this doesn’t directly cut Apache’s own memory footprint, enabling mod_deflate reduces the amount of data buffered and transmitted per request, which indirectly eases pressure on the system overall:

sudo a2enmod deflate

    AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/javascript application/json

Step 7: Set Sensible Timeouts

Long-lived idle connections tie up threads (and their associated memory) longer than necessary. Reduce your Timeout and KeepAliveTimeout values to something reasonable:

Timeout 60
KeepAliveTimeout 5

Real-World Use Cases

  • Budget VPS hosting: Running several WordPress sites on a 1-2GB RAM droplet without constant OOM kills.
  • Containerized deployments: Keeping Apache’s memory footprint predictable inside Docker containers with strict memory limits.
  • Legacy application migration: Moving off mod_php as part of a broader modernization effort, gaining both memory savings and better PHP version isolation.
  • High-density shared hosting: Packing more customer sites onto the same physical hardware.

Troubleshooting Common Issues

Problem: Apache processes keep getting killed by the OOM killer. Check dmesg | grep -i oom to confirm. Lower MaxRequestWorkers, and consider adding swap space as a safety net (though swap should never substitute for proper tuning).

Problem: Memory usage climbs steadily over days, never releasing. This points to a memory leak, often in a third-party module or in your application code. Set MaxConnectionsPerChild to a reasonable non-zero number so processes recycle periodically and release accumulated memory.

Problem: Site broke after switching to PHP-FPM. Double check your .htaccess files don’t reference mod_php-specific directives like php_value or php_flag, since these aren’t recognized once mod_php is disabled. Move those settings into your pool’s php.ini or the FPM pool config instead.

Common Mistakes to Avoid

  • Disabling modules blindly without checking what depends on them first.
  • Assuming more RAM is always the answer instead of addressing root causes like mod_php or misconfigured MPM settings.
  • Setting MaxConnectionsPerChild to 0 “for performance,” inadvertently allowing memory leaks to accumulate indefinitely.
  • Forgetting that a full restart (not reload) is required after MPM or PHP handler changes.
  • Not measuring before and after, making it impossible to know if changes actually helped.

Security Best Practices

  • Disabling unused modules also reduces your attack surface, not just memory usage — a nice two-for-one benefit.
  • Keep PHP-FPM pools isolated per site (separate pool configs, separate users) so a compromise in one site doesn’t directly expose others.
  • Regularly patch Apache and PHP-FPM, since memory-related bugs are sometimes tied to security vulnerabilities as well.

Performance Optimization Tips

  • Pair memory reduction with caching (see my caching setup post) — fewer PHP executions means less peak memory demand.
  • Use a lightweight reverse proxy cache (like mod_cache or an external Varnish layer) in front of Apache for static or semi-static content.
  • Monitor memory trends over time with tools like htop, vmstat, or a proper monitoring stack (Netdata, Prometheus + Node Exporter) rather than checking manually and inconsistently.

Frequently Asked Questions

Does switching to Event MPM always reduce memory usage? In almost all cases, yes, especially compared to Prefork, since threads share memory more efficiently than fully separate processes. The exception is if you’re stuck using non-thread-safe modules that force you back to Prefork.

Is PHP-FPM harder to manage than mod_php? It has a slightly different configuration structure (pools, sockets), but most people find it not meaningfully harder — and the memory and flexibility benefits are substantial.

How much memory can I realistically save? In my own experience, moving from Prefork + mod_php to Event + PHP-FPM cut average memory usage by roughly 40-60% on typical WordPress-style workloads, though your results will vary based on your specific applications.

Will reducing MaxRequestWorkers cause dropped connections? Only if actual concurrent demand exceeds the new limit. Monitor /server-status after changes to confirm you haven’t set the ceiling too low for your real traffic.

Summary and Key Takeaways

Reducing Apache’s memory usage is rarely about one single tweak — it’s a combination of choosing the right MPM, moving heavy interpreters like PHP out of the main Apache process, disabling what you don’t need, and tuning concurrency limits to match your actual hardware.

The changes that gave me the biggest wins, in order of impact:

  1. Migrating from mod_php to PHP-FPM.
  2. Switching from Prefork MPM to Event MPM.
  3. Tuning MaxRequestWorkers and ThreadsPerChild to match available RAM.
  4. Disabling unused modules.
  5. Setting sane timeouts to free up resources faster.

Do these in order, measure after each step, and you’ll likely find — like I did — that your server can comfortably handle far more traffic on the same hardware than you thought possible.

References

Total
0
Shares

Leave a Reply

Previous Post
How to configure Apache to use a Content Delivery Network (CDN)

How to Configure Apache to Use a Content Delivery Network (CDN)

Next Post
How to tune Apache's worker and thread settings

How to Tune Apache’s Worker and Thread Settings

Related Posts