Most legacy PHP applications run behind Apache, and most Apache configurations are a collection of directives accumulated over years of Stack Overflow answers. They work, but they are rarely optimised, rarely secured to modern standards, and almost never reviewed as a whole.
This guide provides a complete, modern baseline Apache 2.4 configuration for PHP applications. It is not a minimal example. It is a production-ready starting point that handles MPM selection, PHP-FPM integration, TLS configuration, security headers, compression, caching, and logging. Every directive is explained so you understand what it does and why it is there.
Prerequisites
This guide assumes:
- Apache 2.4.x (2.4.54 or later recommended)
- PHP-FPM (not mod_php) as the PHP handler
- TLS termination at Apache (not a reverse proxy)
- A single virtual host serving one PHP application
If you are running PHP-FPM and want to optimise the PHP-FPM side, see the PHP-FPM Tuning by Measurement guide.
MPM Selection: event vs prefork vs worker
Apache’s Multi-Processing Module (MPM) determines how it handles concurrent connections. The choice affects memory usage, concurrency limits, and PHP-FPM compatibility.
mpm_event (Recommended)
1 | LoadModule mpm_event_module modules/mod_mpm_event.so |
mpm_event is the best choice for PHP-FPM setups. It uses threads for connection handling, with a dedicated thread for keep-alive connections. This means idle keep-alive connections consume minimal resources.
Key properties:
- Thread-based: high concurrency with lower memory
- Keep-alive connections are cheap to maintain
- Requires PHP-FPM (not compatible with mod_php)
mpm_prefork (Legacy)
mpm_prefork is the traditional choice, required when using mod_php. Each connection gets a dedicated process. This is simple but memory-intensive.
If you are still using mod_php, the single most impactful change you can make is switching to PHP-FPM plus mpm_event. The memory savings alone justify the effort.
mpm_worker (Middle Ground)
mpm_worker uses threads like mpm_event but handles keep-alive connections less efficiently. There is no practical reason to choose it over mpm_event in 2026.
MPM Tuning
For mpm_event:
1 | <IfModule mpm_event_module> |
What these mean:
- ServerLimit: Maximum number of server processes.
MaxRequestWorkers / ThreadsPerChild. - StartServers: Processes to start at launch. Start low; Apache will spawn more as needed.
- MinSpareThreads / MaxSpareThreads: Keep this many idle threads ready. Prevents latency spikes when traffic bursts arrive.
- ThreadsPerChild: Threads per process. 25 is a safe default for most systems.
- MaxRequestWorkers: Total concurrent connections. Size this based on available memory and expected concurrency.
- MaxConnectionsPerChild: Requests before a process recycles.
0means never recycle. Set to a value (e.g., 10000) if you suspect memory leaks in loaded modules.
PHP-FPM Integration via mod_proxy_fcgi
The modern way to connect Apache to PHP-FPM is through mod_proxy_fcgi, not mod_php or mod_fastcgi:
1 | LoadModule proxy_module modules/mod_proxy.so |
This sends .php requests to PHP-FPM via a Unix socket. Unix sockets are faster than TCP connections for same-machine communication.
For TCP-based PHP-FPM (common in containerised setups):
1 | <FilesMatch "\.php$"> |
Timeout Configuration
1 | ProxyTimeout 60 |
This is the time Apache waits for PHP-FPM to respond. Set it to match your PHP max_execution_time. If PHP-FPM takes longer than this, Apache returns a 504 Gateway Timeout.
Virtual Host Configuration
A complete virtual host for a PHP application:
1 | <VirtualHost *:443> |
AllowOverride None
Notice AllowOverride None. This disables .htaccess processing entirely.
Every request, Apache walks the directory tree looking for .htaccess files. On an application with deep directory structures, this can mean ten or more filesystem stat calls per request. Disabling .htaccess and putting all directives in the virtual host configuration eliminates this overhead.
If your application relies on .htaccess files, move their contents into the <Directory> block and set AllowOverride None. This is a one-time migration that permanently eliminates the per-request filesystem overhead.
Security Headers
1 | <IfModule mod_headers.c> |
Every header has a purpose. Do not remove headers without understanding what protection they provide. Adjust Content-Security-Policy to match your application’s requirements. The example above is strict and disallows inline scripts.
TLS Configuration
1 | <IfModule mod_ssl.c> |
This configuration:
- Disables SSLv3, TLS 1.0, and TLS 1.1 (all have known vulnerabilities)
- Uses only AEAD ciphers (GCM mode)
- Enables OCSP stapling for faster TLS handshakes
- Disables session tickets (potential forward secrecy risk with key rotation)
Compression
1 | <IfModule mod_deflate.c> |
DeflateCompressionLevel 6 is the sweet spot between compression ratio and CPU cost. Level 9 produces slightly smaller output but uses significantly more CPU. Level 1 is fast but barely compresses.
Static Asset Caching
1 | <IfModule mod_expires.c> |
This assumes you use versioned filenames for CSS and JavaScript (e.g., style.v3.css or style.css?v=abc123). If you change the file, you change the filename or query string. The long cache duration means returning visitors never re-download unchanged assets.
Logging
Access Log Format
1 | LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %D" combined_with_time |
The %D at the end adds the request duration in microseconds. This is essential for performance monitoring. Without it, you cannot identify slow requests from access logs.
Error Log Level
1 | LogLevel warn |
In production, warn is the right level. info and debug produce too much output and can impact performance on high-traffic sites. Temporarily increase to debug when troubleshooting specific issues.
Modules to Disable
Apache loads many modules by default. Each loaded module consumes memory and increases the attack surface. Disable what you do not need:
1 | # Modules to disable on a PHP-FPM setup |
Run apachectl -M to see what is loaded. If you cannot explain why a module is active, disable it and test.
HTTP to HTTPS Redirect
1 | <VirtualHost *:80> |
Use a simple redirect instead of RewriteRule. It is clearer, faster, and communicates intent better than a rewrite pattern.
Monitoring: server-status
1 | <IfModule mod_status.c> |
server-status shows active connections, request rates, and worker status. Restrict it to internal IPs only. Never expose it publicly.
ExtendedStatus On adds per-request details but has a small performance cost from additional tracking. Enable it in production; the monitoring value outweighs the cost.
FAQ
Should I use Apache or Nginx in 2026?
If your application relies on .htaccess files or Apache-specific modules and you are not ready to migrate, Apache remains a solid choice. If you are starting fresh, Nginx or Caddy are generally simpler for reverse-proxy-to-PHP-FPM setups.
Can I use HTTP/2 with Apache?
Yes. mod_http2 works with mpm_event. Add Protocols h2 http/1.1 to your virtual host. HTTP/2 reduces latency for pages with many assets.
Should I switch from mod_php to PHP-FPM?
Yes. Always. mod_php ties you to mpm_prefork, which is the least efficient MPM. PHP-FPM with mpm_event uses significantly less memory and handles more concurrent connections.
Applying This Baseline
Start with the complete configuration above. Adjust MaxRequestWorkers based on your available memory and expected traffic. Customise Content-Security-Policy for your application. Test with your full test suite.
For PHP-FPM pool configuration to match this Apache setup, see the PHP-FPM Tuning by Measurement guide. For understanding how the web server connects to PHP in the request lifecycle, see the Request Lifecycle Explained article.
The performance fundamentals that inform these tuning decisions are covered in the Performance Optimisation chapter of the Zend Framework book.