
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
PHP applications behave differently than stateless microservices because they rely on PHP-FPM worker pools rather than thread-per-request models. When you attempt to autoscale a PHP service on Kubernetes, standard CPU-based Horizontal Pod Autoscaling (HPA) often fails to capture actual request saturation, leading to dropped connections during traffic spikes. This guide covers the specific configuration required to align Kubernetes scaling logic with PHP-FPM internals, ensuring your Laravel or Symfony application scales based on real capacity rather than misleading system metrics.
pm.max_children to match container memory limits and set appropriate stabilization windows to prevent flapping during variable web traffic patterns.Why does default CPU scaling fail when you autoscale a PHP service on Kubernetes?
Most Kubernetes tutorials assume a linear relationship between CPU usage and request throughput. For Go or Node.js services, this assumption holds reasonably well. PHP-FPM breaks this model entirely. A PHP container can be completely saturated with requests while reporting only 40% CPU utilization because workers are blocked waiting on database queries, external APIs, or file I/O.
When you configure resource limits and requests without understanding PHP's process model, you create a dangerous gap. The HPA sees "healthy" CPU metrics and refuses to scale out, while inside the pod, every PHP-FPM child process is occupied. New requests queue at the Nginx level or get rejected with 502 Bad Gateway errors. I have seen this exact scenario cause outages during product launches for Nepali e-commerce platforms where traffic spiked suddenly but database latency kept CPU usage deceptively low.
The solution requires exposing PHP-FPM's internal state to Kubernetes. You need metrics that reflect actual worker saturation: active processes, idle processes, and listen queue length. These metrics provide a true picture of remaining capacity, allowing the HPA to scale before users experience errors. This approach aligns with the principles discussed in defining meaningful SLIs and SLOs, where you measure what users actually experience rather than proxy system statistics.
How do you expose PHP-FPM metrics for autoscaling?
Kubernetes cannot natively read PHP-FPM status pages. You need an exporter sidecar or embedded library that translates FPM stats into Prometheus format. The most reliable approach in 2026 uses the php-fpm-exporter or integrates metric collection directly into your application via libraries like prometheus-php-fpm.
Configure PHP-FPM Status Endpoint
First, enable the status endpoint in your PHP-FPM pool configuration. This exposes the raw data exporters need:
; www.conf
pm.status_path = /fpm-status
ping.path = /fpm-ping
ping.response = pong Restrict access to these endpoints in your Nginx configuration to prevent public exposure:
location /fpm-status {
allow 127.0.0.1;
deny all;
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
location /metrics {
allow 127.0.0.1;
deny all;
proxy_pass http://127.0.0.1:9253/metrics;
} Deploy the Metrics Exporter
Add the exporter as a sidecar container in your deployment. This keeps metrics collection isolated from your application logic:
spec:
containers:
- name: php-app
image: myregistry/php-app:latest
ports:
- containerPort: 8080
- name: fpm-exporter
image: hipages/php-fpm_exporter:2.3
args:
- "--phpfpm.scrape-uri=tcp://127.0.0.1:9000/fpm-status"
- "--web.listen-address=:9253"
ports:
- containerPort: 9253
name: metrics Verify metrics are flowing correctly before proceeding. Run kubectl port-forward and check /metrics. You should see php_fpm_active_processes, php_fpm_idle_processes, and php_fpm_listen_queue. If these values are missing or zero, your status path configuration is incorrect or the exporter cannot reach the FPM socket. Proper observability here mirrors practices from Prometheus metrics monitoring fundamentals.
How do you configure HPA with custom PHP-FPM metrics?
With metrics exposed, configure the Horizontal Pod Autoscaler to use them. The key is targeting the right threshold. For most PHP web applications, scaling when active processes reach 70-80% of pm.max_children provides headroom for burst traffic without over-provisioning.
Install Prometheus Adapter
Your cluster needs the Prometheus Adapter to translate Prometheus queries into Kubernetes custom metrics API responses. If you use kube-prometheus-stack, this is included. Otherwise, install it separately via Helm:
helm install prometheus-adapter prometheus-community/prometheus-adapter \
--namespace monitoring \
--set prometheus.url=http://prometheus-server.monitoring.svc \
--set prometheus.port=9090 Create the HPA Manifest
This HPA configuration targets 75% worker utilization with proper stabilization to prevent flapping:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: php-app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: php-app
minReplicas: 3
maxReplicas: 20
metrics:
- type: Pods
pods:
metric:
name: php_fpm_active_processes
target:
type: AverageValue
averageValue: "38" # 75% of pm.max_children=50
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Pods
value: 4
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Pods
value: 2
periodSeconds: 120 The asymmetric stabilization windows are critical. Scale-up happens quickly (60 seconds) to handle traffic surges. Scale-down waits five minutes to avoid terminating pods during temporary lulls. This asymmetry prevents the "sawtooth" pattern that causes unnecessary pod churn and cold-start penalties. For teams managing multiple environments, Kustomize can help manage environment-specific HPA thresholds without duplicating manifests.
What PHP-FPM settings must align with Kubernetes resources?
Misaligned PHP-FPM and Kubernetes configurations are the most common failure mode. Your pm.max_children setting must account for actual per-process memory usage, not theoretical minimums. A single PHP worker handling a complex Laravel request can consume 80-150MB depending on loaded libraries and dataset size.
| Container Memory Limit | Recommended pm.max_children | Avg Memory/Worker | Overhead Reserve | Risk Level |
|---|---|---|---|---|
| 512Mi | 4-6 | 80MB | 100MB | High (tight margin) |
| 1Gi | 10-12 | 80MB | 150MB | Medium |
| 2Gi | 22-25 | 80MB | 200MB | Low (recommended) |
| 4Gi | 45-50 | 80MB | 300MB | Very Low |
Calculate your own numbers using this formula: (Memory Limit - Overhead) / Average Worker Memory = max_children. Measure actual worker memory in staging under realistic load using ps --no-headers -o rss -C php-fpm | awk '{sum+=$1} END {print sum/NR/1024 " MB"}'. Never guess. OOMKilled pods during peak traffic indicate you got this math wrong.
Set pm = dynamic with pm.start_servers at 30-50% of max_children, pm.min_spare_servers at 20%, and pm.max_spare_servers at 60%. Static process management wastes memory during low traffic and cannot adapt within a pod's lifetime. Dynamic management complements cluster-level autoscaling by optimizing resource usage within each pod.
How do you validate autoscaling works before production?
Never trust autoscaling configuration without load testing. Synthetic validation catches misconfigurations that only manifest under pressure. Use tools like k6 or Locust to simulate realistic traffic patterns including gradual ramps, sudden spikes, and sustained plateaus.
- Baseline test: Send steady traffic at 50% target capacity for 10 minutes. Verify pod count remains stable and metrics report expected values.
- Spike test: Jump from 30% to 90% capacity in 30 seconds. Confirm HPA triggers scale-up within the stabilization window and no requests return 5xx errors.
- Sustain test: Hold 80% capacity for 15 minutes. Ensure scaling stabilizes and doesn't continue climbing due to metric lag.
- Cool-down test: Drop traffic to 20%. Verify scale-down respects the longer stabilization window and doesn't prematurely terminate pods.
- Failure injection: Kill random pods during sustained load. Confirm remaining pods absorb traffic and HPA replaces lost capacity.
Monitor three signals during testing: HPA events (kubectl get events | grep HPA), actual vs desired replicas (kubectl get hpa -w), and error rates in your ingress controller logs. If errors spike before scaling completes, either reduce the target utilization threshold or decrease the scale-up stabilization window. Document these findings; they become your operational runbook.
Reliable Scaling Requires Alignment
Successfully implementing autoscaling for PHP on Kubernetes demands alignment across three layers: PHP-FPM process configuration, Kubernetes resource definitions, and HPA metric selection. Missing any one layer creates failure modes that only appear under production load. Start with accurate worker memory measurements, expose genuine saturation metrics, and validate with realistic load tests before trusting the system. If your team needs help auditing existing PHP infrastructure or designing compliant autoscaling architectures, reach out to discuss your specific requirements.