Apache’s reputation for being heavy usually comes from an old default setup: the prefork MPM with mod_php, where every connection holds a full PHP-enabled process. Modernised, Apache is fast and efficient. These steps make the biggest difference on a VPS.

1. Check what you’re running

apache2ctl -V | grep -i mpm
apache2ctl -M | grep -E 'mpm|php'

If you see prefork and php_module, start with step 2.

2. Switch to event MPM + PHP-FPM

The event MPM uses threads and hands idle keep-alive connections to a listener, so slow clients don’t tie up workers. PHP runs separately in PHP-FPM with its own process pool.

sudo apt install -y php-fpm
sudo a2dismod php8.3 mpm_prefork          # adjust the PHP version
sudo a2enmod mpm_event proxy_fcgi setenvif
sudo a2enconf php8.3-fpm
sudo systemctl restart apache2 php8.3-fpm

Verify with a PHP info page that the “Server API” is FPM/FastCGI. Memory usage under load drops dramatically, and HTTP/2 becomes possible (it doesn’t work with prefork).

3. Size the event MPM to your RAM

/etc/apache2/mods-available/mpm_event.conf:

<IfModule mpm_event_module>
    StartServers             2
    MinSpareThreads         25
    MaxSpareThreads         75
    ThreadLimit             64
    ThreadsPerChild         25
    MaxRequestWorkers      150
    MaxConnectionsPerChild   0
</IfModule>

With PHP in FPM, Apache threads are light (a few MB per process of 25 threads). MaxRequestWorkers caps simultaneous requests; 150–400 is typical for a small-to-medium VPS. The real memory budget goes to PHP-FPM workers — tune pm.max_children there. See PHP-FPM tuning.

4. KeepAlive

KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5

KeepAlive lets browsers reuse connections; with the event MPM it’s cheap. Keep the timeout short (2–5 s) so idle connections don’t linger.

5. Disable .htaccess lookups where possible

With AllowOverride All, Apache checks for .htaccess in every directory of every request path. If you control the server, move the rules into the virtual host and disable lookups:

<Directory /var/www/example.com>
    AllowOverride None
    # rules from .htaccess go here
</Directory>

Keep AllowOverride only where an application must write its own rules (some WordPress plugins). See the .htaccess guide.

6. Disable modules you don’t use

apache2ctl -M
sudo a2dismod status autoindex cgi negotiation   # examples — check you don't need them
sudo systemctl restart apache2

Fewer modules = less memory per process and a smaller attack surface.

7. Compression and browser caching

sudo a2enmod deflate expires headers

Compress text responses and set long cache lifetimes for static assets (examples in the .htaccess guide). Brotli is available via mod_brotli on current Apache versions.

8. HTTP/2

sudo a2enmod http2

In the SSL virtual host: Protocols h2 http/1.1. Requires the event (or worker) MPM.

9. Caching dynamic pages

  • Application-level page caching (WordPress cache plugins) is usually the biggest win.
  • mod_cache with mod_cache_disk can cache responses at the server level — powerful but needs careful rules for logged-in users.
  • OPcache for PHP must be enabled (default on Ubuntu) and sized — see PHP OPcache.
  • Object caching with Redis reduces database load — see WordPress Redis object cache.

10. Nginx in front (optional)

For very high traffic or many slow clients, put Nginx in front of Apache for TLS termination, static files and caching, while Apache handles PHP apps that need .htaccess. See Nginx vs Apache.

Measure

Before and after each change:

ab -n 2000 -c 50 https://example.com/          # Apache Bench (apache2-utils)

or the more realistic k6. Watch memory with free -h and processes with htop during the test. Enable mod_status (restricted to localhost) to see worker states:

<Location /server-status>
    SetHandler server-status
    Require local
</Location>

curl http://localhost/server-status?auto shows busy/idle workers.

Related: installing Apache on Ubuntu, Apache event MPM with PHP-FPM and website speed optimization.

  • Apache
  • Performance
  • PHP