Configuring Apache for load: MPM, php-fpm, .htaccess

Peak loads turn a stable site into a queue of failures, and the cause is often in web server settings. We configure Apache for your traffic profile as a turnkey service: request processing modes, dynamic content handling, rules, and limits. The result is predictable response and a configuration your team understands and can evolve.

Our competencies:

Frequently Asked Questions

Latest works

  • Development of a web application for FEEDME
    Development of a web application for FEEDME
    1365
  • Development of an online store for the company FURNORO
    Development of an online store for the company FURNORO
    1330
  • B2B Advance company logo design
    B2B Advance company logo design
    776
  • Development of a web application for Enviok
    Development of a web application for Enviok
    1077
  • AIDER company logo development
    AIDER company logo development
    1022
  • CRM development for Chasseurs
    CRM development for Chasseurs
    1113

Tuning Apache for load: where failures begin

A typical picture: the site handles normal traffic calmly, but during a sale, on a news publication day, or at the evening peak, response time grows, some requests fail with 502 and 504, and the CPU is far from fully loaded. The Apache configuration remains at default — no one recalculated it for the real request profile.

Default values are designed for compatibility, not for load.

The default processing mode keeps a separate process or thread per connection, php-fpm gets a fixed pool of workers, and directories with overrides allowed force the web server to re-read .htaccess on every request — where a single rule in the virtual host configuration would suffice.

The web server bottleneck manifests as a cascade. First the queue for free PHP workers grows, then the concurrent connection limit is exhausted, then the proxying layer returns an error to the client — and the visitor leaves without waiting for a response. Failures at peaks hit conversion harder than it seems: leads, payments, and ad clicks are lost.

Server overload does not require anything exotic: it is enough for the peak profile to not match the pool sizing, and for routing rules to sit in .htaccess instead of the virtual host level.

Below we analyze how this is resolved — from choosing the processing mode and sizing the php-fpm pool to moving rules and configuring persistent connections.

What web server retuning delivers: measurable effects

Default settings are designed for a steady and small flow: while there are few requests, pages are served quickly and nothing foreshadows problems.

As soon as a peak begins — a newsletter, a sale, a spike in search referrals — the picture changes: the request queue grows, and response time increases not smoothly but in a jump. Below is what exactly changes in numbers after reconfiguring Apache and why.

  • Response time under load. Instead of growing severalfold with the same number of concurrent visitors, it stays within specified bounds: heavy requests (exports, search, integrations) stop blocking light ones, and a typical page responds in a predictable time.
  • Throughput. The number of requests the server processes per unit of time grows without hardware replacement — due to the correct ratio of worker processes, serving static content bypassing the application, and caching repeated responses.
  • Share of rejected requests. Queue overflow errors and connection drops go from percentages to isolated cases; failure now occurs only upon real resource exhaustion, not because of configuration limits.
  • Peak resilience. A separate process pool for scripts with headroom and a limit on long-running requests smooths the burst: the server does not “collapse” over a short interval but smoothly serves the accumulated load.
  • Failure classes that disappear. Memory leaks in server modules, fragmentation and degradation after a day of operation, locks when accessing .htaccess on every request — all of this is treated by configuration, not by scheduled restarts.

To separate effect from coincidence, we capture metrics before and after: percentile response time, number of processed requests, failure rate under synthetic and real peak.

The difference is visible on a graph, not in words — and remains reproducible during the next configuration change.

Tuning Apache: comparing request processing modes

The connection processing mode and the method of connecting the application handler are two independent decisions, and together they set the ceiling for concurrent requests.

Below are configurations that are actually chosen between when tuning Apache for load, with the limitations of each.

Configuration When suitable Limitations
prefork + built-in PHP module Low traffic, legacy code with global state, extensions requiring full process isolation Separate process per connection: memory consumption grows linearly, few concurrently live requests, long keep-alive is unprofitable
worker + php-fpm via proxy Mixed steady load: dynamic content goes to the handler pool, static to the server Threads share address space: a module without multithreading support will crash the process
event + php-fpm via proxy Many short requests and active persistent connections, serving static from the same server Requires careful timeouts, buffers, and connection reuse in the proxying block
event + proxying to a separate application service Backend in another language, separation of application and entry point, distribution across multiple servers Need availability check and retry policies, otherwise a backend error goes to the client as is

The mode choice is determined not by the peak number but by the profile: how many connections live concurrently, whether the code has global state, who serves static content.

Pairing with the PHP handler changes the picture more than the mode itself — so we test configurations on staging under your scenario, not by general recommendations.

Work sequence: from capturing metrics to configuration handover

Configuration audit comes first: without captured metrics it is unclear which change had an effect and what to roll back on regression. Then we work in steps — change one block of parameters, look at the result on the same load profile, and only then move on. The sequence is the same for any project.

  1. Configuration audit — record the current state: request processing mode, process limits, php-fpm pools, .htaccess contents, set of enabled modules. Result — a report to compare each next step against.
  2. Capturing metrics under the real profile — how many concurrent connections the server holds, where the queue starts growing, at what load failures appear.
  3. Target scheme — select the MPM mode in Apache for the nature of requests, calculate the number of worker processes and pools, move rules from .htaccess to the server configuration.
  4. Changes — tuning for load proceeds one parameter block per step. After each we look at metrics: the contribution of the change is visible and a rollback point remains.
  5. Load testing — repeat the audit profile and compare response time, failure rate, memory consumption. If target values are not reached, return to sizing.
  6. Configuration handover — files, diagram, and explanations of parameters: why this number of processes was chosen and what changes if it is raised or lowered.
  7. Team training — show how to capture metrics and change parameters without us, so the configuration lives on.

Configurations, diagrams, and regulations that remain with you

A tuned server by itself is a weak result: changes live on one host, and no one but the author knows why exactly these limits are set.

Therefore we close the work not with SSH access but with a set of artifacts in the repository — they can be deployed on a new machine and handed to another engineer.

  • Configuration files in version control. MPM mode, number of workers and request queue, keepalive parameters, php-fpm pools, .htaccess rules — with edit history and review, not edits on a live server.
  • Infrastructure diagram. Request path from load balancer to application pools, failure points, and where you will hit first as load grows.
  • Access policies. What is closed in .htaccess, which directories and service sections are not exposed externally, what restrictions by addresses and methods apply.
  • Maintenance regulations. Procedure for reloading configuration without dropping active connections, how to service nodes one by one, and what to check after deployment.
  • Load profiles. Results of runs at different numbers of concurrent connections and the point where the queue starts growing — capacity is planned, not guessed.
  • Monitoring map with thresholds. php-fpm queue, busy workers, response time, share of erroneous responses — and values at which on-call intervenes.
  • Rollback procedure. Previous configuration saved, return path described, recovery time known.
  • Walkthrough with your team. We show what and why is set, so you make further changes yourself and do not depend on us.

Case study: how we eliminated peak failures and sped up response

Before: peak turned into a queue

The project is an online store with sharp peaks: sale start and evening hours produced fivefold growth in concurrent visitors.

Apache ran in prefork mode, where a separate process is allocated per connection: at peak, processes hit the limit, requests queued, and some of them closed with a 502/504 error.

The failure rate reached 7% of requests, and catalog response stretched from 1.2 to 6–7 seconds — buyers left before reaching payment.

What we changed and what became on the same profile

Connection processing was moved to the event model, and PHP was moved into separate php-fpm pools, split by sites and request types: heavy scenarios stopped blocking the others.

Parsing .htaccess on every request was disabled — rules were moved to the main config, keep-alive limits, number of worker processes, and queues were selected from the measured profile, not from documentation.

Metrics were captured on the same sale scenario reproduced by a load test. The failure rate went to zero, catalog response at peak dropped below a second, the server stopped going into memory shortage mode — CPU headroom remained twofold.

This check was included in the regulations: any configuration change is confirmed by a run on the saved profile, otherwise the bottleneck returns at the next promotion.

How does prefork differ from worker and event under load?

The modes differ in what exactly consumes a resource per connection: a process, a thread, or an asynchronous handler. Apache HTTP Server fixes this model at start, and the choice determines how sharply memory consumption grows at peak and how the server behaves with slow clients.

prefork spawns a separate process per connection. This is the most memory-expensive model: with several hundred concurrent visitors, processes duplicate, and the machine hits memory limit before CPU.

But isolation is complete — a failing request does not affect neighbors, and modules without thread support continue to work. Therefore prefork remains the choice for legacy code with an embedded PHP handler that cannot be moved to php-fpm.

worker maintains a pool of processes, inside each a pool of threads, and a thread serves the connection. Memory consumption per connection drops severalfold, and throughput grows.

The downside is shared address space: unsafe access to shared state crashes the process along with all its connections, and every extension gets a thread-safety requirement.

event works like worker, but moves maintenance of persistent (keep-alive) connections to a separate listener thread. While a client slowly finishes reading the response, its channel does not occupy a worker thread — the server serves it asynchronously.

This is exactly what addresses the scenario with many half-open connections from mobile clients, where prefork consumes all processes even before peak load.

Mode choice is determined by three questions: which PHP handler is used, whether there are thread-unsafe extensions, and how many persistent connections the load holds. A modern stack with php-fpm moves to event, legacy code with mod_php to prefork. Next we calculate the number of workers, connection limits, and timeouts — more on that below.

Technical doubts we resolve before starting work

Tuning a web server for load almost always happens on a live project where its own layers are already deployed: load balancer, cache, control panel, containers.

So we start not with editing configs but with measurements — where exactly response time is lost and which settings even participate in request processing. Below are technical questions worth resolving before work starts.

Will the service need to be stopped?

Switching MPM modes and php-fpm pools is done one by one on nodes behind the load balancer: while one reloads, traffic goes to the others, and visitors do not notice.

If there is a single node, we agree on a window and prepare return to previous configs in advance — rollback takes minutes.

We already have cache and load balancer configured — won't you break them?

First we capture the map: what is deployed, which parameters are overridden, where the boundary of service responsibility lies. We make changes in isolation — in a separate virtual host and process pool, without touching someone else's logic. We move directives from .htaccess to the server config so the file is not read on every request.

What if the bottleneck isn't in the web server?

A common case: a request hangs on a database call or a slow external service, and the web server has nothing to do with it. So before changes we look at time distribution across layers and only then touch MPM and php-fpm.

If the bottleneck is elsewhere, we will say so directly and propose a separate task instead of useless tuning.

What about support after handover?

We hand over version-controlled configs, instructions for typical operations, and a list of parameters to watch as load grows. For the first period after launch we stay in touch: if the traffic profile changes and metrics drift, we return to measurements and adjust modes.

Let's discuss your task and come back with an estimate and plan

For the conversation to be substantive rather than “tell us about Apache”, a few inputs are enough. We review the current configuration, traffic profile, and logs — and come back with a scope estimate and timeline plan.

  • Configs: main file, included vhost’s, selected MPM (prefork, worker, event) and its child and connection limits.
  • php-fpm: pool mode, max_children, memory limits, request processing timeouts, and waiting queue length.
  • .htaccess: which directives execute on every request and where AllowOverride is set wider than needed.
  • Load profile: peak requests per second, ratio of dynamic to static, daily graph, average response size.
  • Logs: access and error for a typical day, excerpts from the php-fpm slow log, share of 5xx, response time, memory consumption.
  • Environment: load balancer in front of servers, their number, cache and CDN, fault-tolerance scheme.

In response you will receive a scope estimate: where parameter tuning is enough and where a transition to event MPM with dynamic handling via proxy_fcgi is needed.

Then a work plan with the order of changes and rollback points, a timeline plan, and a list of load checks: behavior at peak, degradation when a node fails, recovery after restart.

If some data is missing, we will collect it on site. Describe the task — we will come back with a substantive estimate and plan.