High-performance PHP hosting for SaaS and APIs picks a stack that keeps RPS steady and P95 low. Choose tuned containers for steady load above 50 RPS. Choose FrankenPHP for low-latency, event-driven handlers that face bursts.
Quick comparison
Compare cost per request, P95 latency, cold-start behavior, operational maturity, and compliance when choosing a stack. For steady 50+ RPS, containerized PHP usually wins on cost per request. For bursty low traffic, FrankenPHP or serverless can be cheaper.
| Stack / Provider |
Cost per request |
P95 latency (typical) |
Cold starts |
Best fit |
| Tuned PHP‑FPM on containers (VMs) |
Low for sustained traffic |
50–200 ms |
Near-zero when warmed |
50+ RPS, steady workloads |
| FrankenPHP (serverless-like runtimes) |
Competitive at bursty low-medium loads |
40–150 ms (CPU-light handlers) |
Minimal with warm pools |
Event-driven APIs, low latency cold starts |
| Managed PHP hosting (shared/managed) |
May be higher if limits hit |
100–300 ms |
Varies by vendor |
Low-cost, low-concurrency apps |
| Serverless functions (general) |
Low at tiny scale, rises with concurrency |
100–500 ms including cold starts |
50–500 ms unless provisioned |
Very spiky, unpredictable traffic |
Cost and breakpoints
For steady 50+ RPS, containerized VMs usually win on cost per request. Serverless shows savings below about 5–20 RPS. Run a cost model with real exec time and concurrency to find your breakeven point, and run the tests before any production migration begins.
Decision quick win
Run a short 10-minute load test in us-east-1 and us-west-2 to compare regional latency. Use the k6 scripts and Terraform templates in this kit to make results reproducible. Compare P95 and cost per million requests across regions.
FrankenPHP vs PHP-FPM
FrankenPHP cuts cold-start variance for short handlers and lowers single-request overhead. PHP-FPM keeps the widest ecosystem support and predictable behavior for I/O heavy paths. Teams must benchmark both with real DB and cache behavior before choosing.
FrankenPHP trade-offs
FrankenPHP often cuts P95 by 10–30 percent for CPU-light JSON endpoints. The gain depends on request size, CPU work, and DB impact. Measure end-to-end with DB and cache in place to confirm gains for each workload.
PHP-FPM strengths
PHP-FPM keeps mature tools like OPcache and many extensions. It outperforms when requests run many DB queries or rely on blocking I/O. Tune pm.max_children and OPcache preload for best results.
Practical note on runtimes
The most common mistake is choosing from pure PHP microbenchmarks. Real APIs see DB latency, cache misses, and network waits. Those factors usually dominate end-to-end P95.
FrankenPHP reduces cold-start variance for short-lived handlers and can cut tail latency by around 10–30% for many API routes; measure end-to-end with DB and cache in place to confirm gains for your workload.
Containers vs serverless
Containers give predictable costs and near-zero warm latency when scaled correctly. Serverless can look cheaper at tiny scale but adds variable cold-start latency. Choose based on traffic shape, concurrency, and ops capacity.
When containers win
Containers win for sustained traffic above serverless breakeven. They give control over memory, CPU, and DB connection pooling. That control lowers latency and cost per request at scale; run the tests before any production migration begins.
When serverless wins
Serverless fits low average RPS with rare spikes and minimal ops work. Provisioned concurrency removes many cold-starts but raises base cost. Evaluate provisioned costs against expected burst patterns.
Cold-start numbers and concurrency
Cold starts vary by provider and runtime from about 50 ms to 500 ms for PHP-like environments. A 200–300 ms cold start will break a P95 <200 ms target. Measure cold starts under realistic load.
Typical P95 ranges
Containers (PHP-FPM): 50–200 ms
Serverless cold-start affected: 100–500 ms
Measure these three
P95 latency
Cold start
Cost per 1M requests
Reproducible benchmarks & production artifacts
A reproducible kit removes opinion and shows real cost and latency differences. The kit here includes Dockerfiles, Nginx and PHP-FPM configs, a FrankenPHP example, Terraform, k6 tests, and a cost-per-request calculator. Running the kit in two regions gives numbers with variance under ±15 percent when tests are controlled.
Dockerfile and nginx pattern
Start with a multi-stage Dockerfile that builds PHP deps and sets OPcache at build time. Use Nginx as the front end and pass PHP via a unix socket for lowest latency.
Dockerfile
FROM php:8.1-fpm AS build
WORKDIR /app
COPY composer.json composer.lock ./
RUN apt-get update && apt-get install -y git zip unzip
RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');" && php composer-setup.php --install-dir=/usr/local/bin --filename=composer
RUN composer install --no-dev --optimize-autoloader
FROM php:8.1-fpm
COPY --from=build /app /var/www/html
COPY ./docker/php.ini /usr/local/etc/php/conf.d/zz-custom.ini
COPY ./docker/php-fpm.d/www.conf /usr/local/etc/php-fpm.d/www.conf
COPY ./docker/nginx.conf /etc/nginx/nginx.conf
EXPOSE 80
CMD ["php-fpm"]
- nginx user www-data
- worker_processes auto
- events { worker_connections 1024
- } http { sendfile on
- tcp_nopush on
- keepalive_timeout 65
- server_tokens off
- server { listen 80 default_server
- server_name _
- root /var/www/html/public
- location / { try_files $uri /index.php$is_args$args
- } location ~ .php$ { fastcgi_pass unix:/var/run/php/php-fpm.sock
- include fastcgi_params
- } } }
ini
[www]
user = www-data
group = www-data
listen = /var/run/php/php-fpm.sock
pm = dynamic
pm.max_children = 20
pm.start_servers = 4
pm.min_spare_servers = 2
pm.max_spare_servers = 6
FrankenPHP minimal config
ini
extension=frankenphp.so
frankenphp.max_workers=40
frankenphp.request_timeout=30
frankenphp.keepalive=1
hcl
provider "aws" { region = var.region }
resource "aws_instance" "api" {
ami = data.aws_ami.ubuntu.id
instance_type = var.instance_type
tags = { Name = "php-api" }
}
resource "aws_lb" "api" { / ... / }
K6 test
js // ./tests/load.js import http from 'k6/http'; import { sleep } from 'k6'; export let options = { stages:
- [ { duration: '2m', target: 100 } ] }
- export default function () { http.get('https://api.example.test/health')
- sleep(0.1)
- }
Cost-per-request calculator
- js // ./tools/costCalc.js function costPerMillion(execMs, memoryMb, pricePerGbHour) { const seconds = execMs / 1000
- const gbSec = (memoryMb / 1024) * seconds
- const costPerReq = gbSec * (pricePerGbHour / 3600)
- return costPerReq * 1_000_000
- } module.exports = { costPerMillion }
This guidance is less relevant for hobby sites, simple blogs, or apps with negligible concurrency and latency needs where shared hosting is sufficient and the cost and complexity of containers or serverless are not justified.
Reproducible comparative benchmarks and methodology
A reproducible benchmark must show exact methodology and sample outputs so teams can trust claims. Describe the k6 scenario, payload size, stages, and virtual users. Describe backend topology, DB latency injection, cache warm and cold states, and exact metrics to record.
Record RPS, P50/P95/P99, cold-start distribution, error rate, and cost per request. Include a sample results table for region, instance type, concurrency, RPS, P95, cold-start percentile, and measured cost per one million requests.
This makes claims about FrankenPHP, PHP-FPM tuning, containerized PHP, or serverless PHP verifiable. It also shows how PHP tuning and cold start work under real load.
Production VM/systemd and PHP tuning
For VMs, add a systemd unit and OPcache preload instructions. Set Restart=on-failure and increase file descriptor limits in the unit file. Add opcache.preload=/var/www/html/preload.php and generate a preload during build time.
For Laravel APIs, enable optimized autoloading, route:cache, and config:cache. Run composer dump-autoload --optimize at build time. Tune pm.max_children and pm.max_requests to fit memory.
These steps cut cold-start variance and are essential when running non-containerized SaaS hosts.
A practical IaC section should show a parameterized Terraform module with variables.tf and modules for compute, LB, ASG, and IAM roles. Add a CI/CD pipeline that builds images, runs k6 tests, and deploys with blue/green or canary.
Include autoscaling policies using target tracking on CPU and a custom metric like request count or queue length. Fold provisioned capacity and idle costs into the cost-per-request math. Linking k6 into CI gives a repeatable path to measure latency under autoscaling.
Multi-tenant and scaling choices
Pick tenant data models by expected tenant count, noise, and compliance. Shared schema fits early-stage SaaS. Schema-per-tenant or DB-per-tenant fits noisy or compliance-bound customers. Migrations are possible but need careful pool and backup planning.
Tenant model thresholds
Use a shared schema for up to about 1,000 tenants with controlled row counts. Move to schema-per-tenant between about 1,000 and 50,000 tenants when noisy neighbors appear. Isolate restores and backups when tenant isolation is needed.
DB and pooling patterns
Add connection pooling like ProxySQL or pgbouncer before adding DB replicas. Without pooling, many short connections inflate latency and resource use. Pooling reduces DB load and stabilizes latency. Run the tests before any production migration begins.
Observability, security, and migration pitfalls
Set SLIs and SLOs before any migration. A suggested SLO set is P95 <200 ms, error rate <0.1 percent, and availability 99.95 percent. Pair SLOs with tracing and automated rollback gates in CI.
Monitor P50, P95, and P99 latencies, 5xx rate, DB query P95, and cache hit ratio. Use OpenTelemetry for traces and Datadog or New Relic for APM. Map request spans to DB queries and external calls.
Security and compliance checklist
Require TLS 1.2 or higher, WAF rules for common attacks, secrets stored in a vault, and regular dependency scanning. For PCI DSS or HIPAA, map controls to hosting choices and keep audit proof ready.
Migration pitfalls and a common case
The most frequent error is moving runtimes without warming OPcache or testing DB pooling. A common case: a Laravel app moved to containers and saw 200–400 ms spikes on first requests. Running composer dump-autoload --optimize and adding OPcache preload at build time avoids those spikes.
Run the provided Terraform and k6 scripts in an isolated test account before any production cutover to produce repeatable RPS and P95 baselines for your stack and region.
Frequently asked questions
What is the cheapest option for low traffic PHP
Serverless often costs less for tiny, low-concurrency traffic under about 5–20 RPS. Measure average execution ms and memory to compare against VM pricing. Run the cost-per-request calculator with real numbers.
Do FrankenPHP gains apply to laravel and symfony
Gains apply mainly to small, CPU-light handlers. Large frameworks with many middleware layers may not see the same 10–30 percent P95 gain. Trim autoload and boot time to improve results for full frameworks.
How to measure cold starts reliably?
Clear runtime pools and run a load test that forces new instances. Compare P95 and P99 for the first request versus warmed requests. Repeat runs and record cold-start percentiles.
When should a SaaS move from shared hosting to dedicated hosting
Move when sustained traffic routinely exceeds 50 RPS or when latency and control needs justify added ops. Move earlier if compliance or tenant isolation require it. Validate costs and runbooks before cutover.
What SLOs are realistic for a US-based API
Aim for P95 latency under 200 ms, error rate under 0.1 percent, and availability at 99.95 percent. Map these targets to monitoring, alerts, and runbooks for incidents.
How to compare cost per request between providers?
Compute cost per million requests using average exec ms, memory footprint, and provider price per GB-hour. Include idle or provisioned capacity in the model to reflect real autoscaling behavior. Use the cost-per-request calculator in this kit.
Final synthesis and recommended next steps
For steady SaaS traffic above 50 RPS, tuned containers running PHP-FPM usually give the best cost per request and predictable P95. FrankenPHP lowers cold-start variance and can beat containers for CPU-light, event-driven APIs. Serverless fits very low average RPS with rare spikes, but test provisioned concurrency and cost profiles before committing.
The evidence points to two practical paths: containerized PHP-FPM for predictable, sustained workloads, and FrankenPHP for latency-sensitive handlers. The choice depends on I/O profile, concurrency, and compliance needs.
A final actionable step: run the included Terraform and k6 kit in your target regions, measure P95 and cost per million requests, then pick the stack whose measured results meet your SLOs and budget. For PHP governance and runtime details, see the PHP Foundation site PHP Foundation.