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_diskcan 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.