
August 19, 2026
10 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
Horizontal Pod Autoscaling in Kubernetes automatically adjusts the number of pod replicas based on observed CPU, memory, or custom metrics to match actual demand. For PHP and Laravel applications running in production, getting this right prevents both downtime during traffic spikes and wasted resources during quiet periods. If you are hiring a Laravel developer or managing infrastructure yourself, understanding HPA mechanics is essential because misconfigured autoscaling causes more outages than it prevents.
How does Horizontal Pod Autoscaling in Kubernetes actually work?
The HPA controller runs as a control loop inside the kube-controller-manager. Every 15 seconds by default, it queries the Metrics API for current metric values, compares them against your defined targets, and calculates the desired replica count. The formula is straightforward: desiredReplicas = ceil[currentReplicas × (currentMetricValue / desiredMetricValue)]. If you have 3 pods averaging 80% CPU against a 50% target, the controller computes ceil[3 × (80/50)] = 5 replicas and instructs the Deployment to scale up.
In practice, the raw calculation gets smoothed by stabilization windows and tolerance settings. Without these, a momentary spike during deployment or cache warming triggers aggressive scale-up followed immediately by scale-down, causing request failures and log noise. I've seen this repeatedly on Laravel applications where queue workers briefly saturate CPU during batch processing, only to idle moments later. The HPA must distinguish sustained load from transient bursts.
Metric sources available in 2026
- Resource metrics: CPU and memory utilization per container, provided by metrics-server. This is the baseline most tutorials cover and sufficient for many stateless PHP-FPM workloads.
- Custom metrics: Application-specific values like active queue jobs, concurrent requests, or database connection pool usage, exposed via Prometheus Adapter or KEDA. Essential when CPU doesn't correlate with actual business load.
- External metrics: Values from outside the cluster such as SQS queue depth, Redis list length, or third-party API rate limit consumption. Useful for event-driven scaling independent of pod resource usage.
How do you configure HPA for PHP-FPM and Laravel applications?
PHP-FPM workloads behave differently than long-running Go or Java services. Each FPM worker handles one request at a time, so CPU utilization often stays moderate even under heavy load because workers block on I/O. Relying solely on CPU targets frequently leaves you under-scaled during database-heavy operations or over-scaled during static asset serving. On production Laravel projects, I combine CPU with custom metrics that reflect actual application pressure.
<!-- hpa-laravel.yaml -->
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: laravel-app-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: laravel-app
minReplicas: 2
maxReplicas: 12
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 50
periodSeconds: 60
- type: Pods
value: 2
periodSeconds: 60
selectPolicy: Max
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 25
periodSeconds: 120
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 65
- type: Pods
pods:
metric:
name: php_fpm_active_processes
target:
type: AverageValue
averageValue: "8" This configuration addresses three common failure modes. The 60-second scale-up stabilization prevents reaction to deployment-related CPU spikes. The 300-second scale-down window avoids premature teardown after traffic dips — PHP-FPM process startup isn't free, and losing warm workers hurts latency. The dual-metric approach ensures scaling responds to either compute saturation or FPM worker exhaustion, whichever hits first.
Exposing PHP-FPM metrics to Kubernetes
You need the Prometheus Adapter or KEDA to make php_fpm_active_processes available to the HPA. First, ensure your PHP-FPM status endpoint is enabled in the pool configuration:
; www.conf
pm.status_path = /fpm-status
ping.path = /fpm-ping Then deploy a sidecar or standalone exporter like hipages/php-fpm_exporter that scrapes this endpoint and exposes Prometheus-format metrics. The adapter maps these into the Kubernetes Metrics API. Verify availability before deploying the HPA:
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/production/pods/*/php_fpm_active_processes" If this returns empty or errors, the HPA will show <unknown> for the custom metric and refuse to scale. I've debugged this exact issue on client projects where the exporter ran but the ServiceMonitor selector didn't match the pod labels. Always validate the metric pipeline end-to-end before trusting autoscaling in production.
What are the common HPA mistakes that cause production incidents?
After maintaining Kubernetes deployments for Laravel and Symfony applications, certain failure patterns recur. These aren't theoretical — they're the issues I troubleshoot when DevOps automation breaks down or clients report intermittent slowness.
Setting minReplicas too low for stateful or slow-starting apps
A minReplicas of 1 seems cost-efficient until that single pod restarts during a rolling update or OOM kill. For any application handling user sessions, payment callbacks, or legal document processing, minimum 2 replicas provides basic resilience. The HPA can still scale down to this floor during off-hours, but you never drop below survivable capacity. On Nepal-based legal-tech portals handling court marriage applications, I set minReplicas to 3 because even brief unavailability during peak filing hours generates support calls.
Ignoring scale-down stabilization
Default scale-down behavior in older HPA versions was aggressive. Even with autoscaling/v2 defaults improved in 2026, explicit stabilization windows prevent thrashing. A 5-minute scale-down window means the controller waits 300 seconds of sustained low metrics before removing pods. This costs slightly more during genuine lulls but eliminates the far greater cost of premature scale-down followed by immediate scale-up when traffic resumes. Calculate your typical traffic dip duration and set the window accordingly.
Mismatched resource requests and limits
HPA CPU utilization is calculated against resource requests, not limits. If your container requests 250m CPU but has a 2000m limit, hitting 100% utilization means using 250m — potentially far below actual saturation. Set requests close to observed p50 usage and limits at p99 plus headroom. Misaligned values make the HPA either trigger too late (requests too low) or waste resources (requests too high). Audit this with kubectl top pods correlated against actual response latency.
| Mistake | Symptom | Fix |
|---|---|---|
| CPU-only scaling for I/O-bound PHP | High latency despite low CPU utilization | Add custom metrics (active FPM workers, queue depth) |
| No scale-down stabilization | Rapid scale-up/down cycles, increased error rates | Set scaleDown.stabilizationWindowSeconds ≥ 300 |
| minReplicas = 1 for critical services | Downtime during single-pod restarts | Minimum 2 replicas; 3+ for revenue/legal workflows |
| Resource requests ≠ actual usage | HPA triggers too early or too late | Align requests to p50 observed CPU/memory |
| Missing metric validation | HPA shows <unknown>, refuses to scale | Test metric endpoint before deploying HPA |
When should you use custom metrics instead of CPU for autoscaling?
CPU works when compute correlates linearly with request volume. For many PHP applications, it doesn't. Database-heavy endpoints, file processing, and external API calls create load patterns where CPU stays moderate while the application is effectively saturated. In these cases, custom metrics provide accurate scaling signals.
For Laravel queue workers, scale on pending job count rather than CPU. A worker processing video transcoding may sit at 30% CPU while blocking on ffmpeg, but ten pending jobs indicate backlog requiring more workers. Expose queue depth via a Prometheus exporter reading from Redis or your queue backend, then reference it in the HPA as an external or pods metric. This aligns replica count with actual business backlog, not misleading resource utilization.
Similarly, for applications with connection pooling or rate-limited dependencies, scale on those constraints directly. If your MySQL pool maxes at 50 connections per pod and you observe connection wait times increasing, that's a better scaling signal than CPU which might remain flat. The principle: scale on the bottleneck, not a proxy. When building Laravel queue systems for high traffic, this distinction determines whether autoscaling actually resolves performance issues or just adds expensive idle pods.
How do you test and validate HPA behavior before production?
Never deploy an untested HPA directly to production traffic. The feedback loop is slow (15-second intervals plus stabilization windows), and misconfigurations manifest as user-facing errors. Follow a structured validation process.
- Verify metric availability: Run
kubectl get hpaand confirm all metrics show numeric values, not<unknown>. If unknown, debug the metric pipeline before proceeding. - Load test with realistic patterns: Use tools like k6 or Locust to generate traffic matching production profiles. Don't just hammer a single endpoint — simulate mixed read/write ratios, authenticated vs anonymous traffic, and background job submission.
- Observe scaling timing: Record when load increases, when the HPA detects it, and when new pods become ready. The gap between detection and readiness is your effective response latency. If it exceeds acceptable thresholds, optimize pod startup time or adjust stabilization windows.
- Test scale-down behavior: After load subsides, verify pods scale down according to your stabilization policy. Confirm no requests fail during termination (ensure preStop hooks and graceful shutdown periods exceed your longest request duration).
- Simulate failure scenarios: Kill pods manually during load to test recovery. Verify the HPA doesn't overcorrect. Test what happens when the Metrics API becomes temporarily unavailable — the HPA should maintain current replicas, not scale to zero or max.
Document observed behavior alongside your HPA manifest. When on-call engineers respond to scaling alerts at 2 AM, they need context about expected behavior versus anomalies. This documentation pays for itself during incident response and reduces mean time to resolution significantly.
Implementing reliable Horizontal Pod Autoscaling in Kubernetes
Horizontal Pod Autoscaling in Kubernetes delivers genuine operational value when configured with application-aware metrics, appropriate stabilization, and validated behavior. Start with conservative targets and explicit bounds, measure actual performance under load, then tune iteratively. Avoid copying example manifests without adapting them to your workload's specific characteristics — PHP-FPM isn't Node.js, and legal-tech portals have different availability requirements than internal dashboards.
If you're implementing Kubernetes autoscaling for Laravel, Symfony, or other PHP applications and want to avoid the production pitfalls described here, reach out to discuss your infrastructure. Getting HPA right upfront prevents costly debugging later and ensures your scaling investment actually improves reliability rather than introducing new failure modes.

