Contents
- 1 Php max execution time explained
- 2 What “max execution time” really is (and what it isn’t)
- 3 Why the default 30 seconds is both too much and too little
- 4 When bumping the limit is a smell
- 5 A better way: different limits for different kinds of work
- 6 How to actually choose reasonable values
- 7 When “unlimited” sounds tempting
- 8 Handling partial work and consistency
- 9 Web, CLI, and the quiet differences
- 10 Php max execution time in the wild: hiring, jobs, and real expectations
- 11 A small practical checklist
- 12 Closing: the quiet line between enough time and too much
Php max execution time explained
There’s a particular kind of silence you only hear when a PHP script hangs.
Fans spinning. Cursor frozen. Browser tab just… loading.
You check the logs, refresh the profiler, stare at the terminal.
Then it hits:
Fatal error: Maximum execution time of 30 seconds exceeded…
If you’ve written PHP for more than a month, you’ve seen that line.
If you’ve done it for years, you’ve probably both cursed it and silently thanked it.
That little max_execution_time setting sits at the intersection of performance, reliability, and pure human stress. It’s where impatient users, overworked CPUs, and “just one more little feature” collide.
Friends, let’s talk about it properly.
Not like a documentation page.
Like two developers grabbing a late coffee after production went weird.
What “max execution time” really is (and what it isn’t)
PHP’s max_execution_time is simple in theory:
- It’s a limit, in seconds, on how long a PHP script is allowed to run.
- When the limit is hit, PHP throws a fatal error and terminates the script.
- The default value (for web SAPI) is usually 30 seconds.
So far, nothing surprising. But there are a few details that matter a lot in real projects.
Cpu time vs wall time: the OS question
On Linux and macOS, max_execution_time usually measures CPU time, not wall-clock time. That means:
- Time actually spent executing PHP code counts.
- Time spent waiting (blocked I/O, sleeping, some external calls) often doesn’t.
On Windows, it’s different: max_execution_time behaves more like real wall time. If your script sits idle for 30 seconds waiting for a slow HTTP call or a sleep(30), that still counts.
If your team mixes environments (develop on Windows, deploy on Linux), this can cause those quietly maddening “works on my machine” moments around timeouts.
What the timeout does not see
Another subtle thing: even on Linux, your script’s “idle” time isn’t always free.
Some operations absolutely do count toward max_execution_time:
- long-running database queries (especially streaming large results),
- big file reads/writes,
- certain stream operations.
So the mental model is:
It’s mostly about the time PHP is busy or waiting in ways PHP considers part of execution. It’s not just about tight loops.
Where the setting comes from
You can control max_execution_time from several layers:
php.ini(global default)- Apache or Nginx + PHP-FPM overrides
.htaccess(in some Apache setups)- Runtime, inside PHP code:
ini_set('max_execution_time', 10);set_time_limit(10);
And then, just to keep things spicy, web servers and proxies also have their own timeouts:
- Nginx
fastcgi_read_timeout - Apache
Timeout,ProxyTimeout, etc. - Load balancer timeouts
- IIS script timeouts
You can “give PHP unlimited time” with set_time_limit(0), but your web server might still cut you off after, say, 300 seconds. Many of us learn this at 2 AM while debugging an import job that dies at 4:59 every single time.
Why the default 30 seconds is both too much and too little
Most PHP installations ship with max_execution_time = 30. It feels safe, reasonable, like a neutral default.
It isn’t.
Too much for high-traffic apps
Imagine a PHP-FPM pool that can handle 100 concurrent requests.
Now imagine a request that takes 30 seconds.
That one slow script can block a lot:
- A single long request can tie up one worker for 30 seconds.
- 10 users hitting that slow endpoint at once? Now 10 workers are locked.
- If your pool is small or traffic spikes, you suddenly exhaust your workers and start serving 502/503 errors to completely normal, fast endpoints.
You don’t want that.
In a high-traffic app, 30 seconds is obscene. You want:
- quick failure,
- graceful degradation,
- background work for heavy stuff.
In that world, timeouts around 5–10 seconds for most web requests are way more healthy.
Too little for heavy legitimate work
On the other hand, you have pages that legitimately need more time:
- big CSV imports,
- PDF generation,
- heavy reporting queries,
- external APIs that are slow but required.
Sometimes you really do need 60 seconds. Or 120. Or more. And no, not everything can be optimized away in a sprint.
The conflict is very real:
- Business: “We just need to import a 100 MB CSV; why is that so hard?”
- Devs: “Because the web stack is built for fast, short-lived requests, not long-running jobs.”
- Infra: “If you keep 100 workers busy for 2 minutes, the site dies.”
This is where max_execution_time moves from “simple setting” to “architecture decision.”
When bumping the limit is a smell
Everyone in PHP has done it:
ini_set('max_execution_time', 300);
// or
set_time_limit(300);
You push, it works, people cheer, and you move on.
But deep down, you know: this is duct tape. Sometimes necessary, but still duct tape.
Some warning signs that raising the limit is hiding real problems:
- You increase
max_execution_timeevery few months on the same feature. - The script mostly does database work and you haven’t touched the queries.
- The endpoint is popular and under heavy load.
- You’re streaming tons of data to the browser with no pagination or batching.
- The logic is doing way more than “one request should.”
Timeout errors are painful, but they’re often the only honest feedback in a system that politely hides its own suffering.
When a script regularly hits the max execution time, it’s rarely just “bad luck.” It’s usually:
- missing indexes,
- N+1 queries,
- too much work packed into a single request,
- or functionality that simply doesn’t belong in a synchronous web request.
A better way: different limits for different kinds of work
What finally calms systems down is accepting that:
There is no single best
max_execution_timefor your entire PHP app.
You don’t treat all requests equally in your head, so stop giving them the same timeout.
Separate read and write requests
One practical heuristic that works well:
-
Read requests (
GET,HEAD)- lower timeouts (e.g., 3–10 seconds),
- they’re usually safe to abort,
- they make up most of your traffic.
-
Write requests (
POST,PUT, etc.)- slightly higher timeouts (e.g., 10–30 seconds),
- mid-execution failures can leave inconsistent state,
- often involve more logic and more I/O.
You can implement this centrally, e.g. using Symfony’s Request or just $_SERVER:
$requestMethod = $_SERVER['REQUEST_METHOD'] ?? 'GET';
if ($requestMethod === 'GET' || $requestMethod === 'HEAD') {
ini_set('max_execution_time', 5); // reads: fast and strict
} else {
ini_set('max_execution_time', 15); // writes: more tolerance
}
Not fancy. Very effective.
Slow endpoints get their own rules
Some URLs are special:
/admin/report/annual/admin/tools/export/legacy/import/customers
They’re low-traffic but heavy. That’s a perfect place to raise the limit a little:
$uri = $_SERVER['REQUEST_URI'] ?? '/';
if (str_starts_with($uri, '/admin/report')) {
ini_set('max_execution_time', 60);
}
This way you don’t punish the entire site with a 60-second default value just because a handful of admin pages need it.
Multiple PHP-FPM pools, different personalities
In more serious setups, you can go further and separate slow from fast work at the PHP-FPM level:
-
One pool with:
- many workers,
- strict
max_execution_time, - meant for the main user-facing app.
-
Another pool with:
- fewer workers,
- higher timeouts,
- used for heavy admin tasks, long exports, weird legacy endpoints.
Then you route certain paths (say /admin/long-running) to the “slow” pool via Nginx or Apache. Slow jobs no longer block fast ones.
It feels like overkill until the day your main pool doesn’t collapse when someone runs a 2-minute export. Then it feels like the smartest thing you’ve done all quarter.
How to actually choose reasonable values
This is the part many teams skip. They argue opinions instead of looking at data.
If you want sane max_execution_time values, you need to know:
- How long your endpoints actually take.
- How often they’re called.
- What their 95th and 99th percentile latencies look like.
Measure first, decide after
Use whatever you have:
- Tideways, Blackfire, New Relic, Datadog, OpenTelemetry,
- framework-level profiling,
- even simple logging with microtime.
Collect:
- average duration,
- 95th percentile,
- 99th percentile,
- volume of requests per endpoint.
Possible rule of thumb:
-
If an endpoint:
- is very frequent,
- is usually fast (< 200 ms at P95),
- and suddenly spikes in duration sometimes,
→ give it a low timeout. Let it fail fast and loudly if it goes weird, rather than occupying a worker for 20 seconds.
-
If an endpoint:
- is infrequent,
- legitimately needs 15–60 seconds sometimes (like exports),
→ give it a higher timeout, but route it to a pool that can’t take down your whole app.
This is where PHP performance stops being abstract and turns into a negotiation between product, ops, and your own patience.
When “unlimited” sounds tempting
You can write:
set_time_limit(0);
ini_set('max_execution_time', 0);
And PHP will interpret that as: no time limit.
A seductive thought. Especially when you’re trying to install something, run a migration, or process a massive file and you’re tired of watching it die at 600 seconds.
But here’s what “unlimited” can mean in practice:
- Workers tied up indefinitely.
- Leaks and runaway loops eating CPU forever.
- Web servers killing the connection anyway after their own timeout, but PHP happily keeps running pointlessly in the background.
- Jobs that never finish and never fail clearly.
Unlimited time is rarely what you want.
What you usually want is:
- enough time to complete the job once,
- clear visibility when it fails,
- and preferably not on the main web request.
Better pattern: move heavy work off the request
Some kinds of work simply shouldn’t happen inside a user-facing HTTP request:
- imports,
- mass emailing,
- big data transformations,
- large media processing.
That’s what queues are for.
- Push the job into a queue (Beanstalkd, Redis, RabbitMQ, SQS, whatever).
- Return immediately to the user with “Your import has started.”
- Let a CLI worker process the job with its own execution time strategy.
In CLI, PHP’s max_execution_time is usually 0 anyway (no limit at the SAPI level). But you can still apply your own limits:
- run workers with
timeout 300 php worker.phpon Unix, - build your own watchdog to restart hung workers,
- or use a queue system that manages that for you.
Suddenly, max_execution_time stops being a panic lever and becomes just one small piece in a bigger, calmer architecture.
Handling partial work and consistency
Timeouts in write requests have a particularly sharp edge: they can leave your state half-changed.
Picture this:
- User submits a big form.
- You:
- create some entities,
- send emails,
- update several tables,
- call external APIs.
- At second 29, PHP decides you’ve had enough. Fatal error. Script killed.
Now what?
- Maybe the database transaction finished. Maybe not.
- Maybe half the changes are in.
- Maybe the email was sent, but the status wasn’t stored.
- Maybe the external API saw the request twice.
This is where thoughtful PHP developers earn their keep.
Defensive strategies against messy timeouts
A few things help:
-
Database transactions
Wrap complex changes in a transaction. If a timeout happens before commit, most DBs will roll back automatically. You lose work, but you don’t corrupt state. -
Idempotent operations
Design external calls and internal logic so that “doing it twice” is safe:- operations keyed by unique IDs,
- upserts instead of blind inserts,
- job states stored in tables.
-
Checkpoints
For long-running scripts (imports, migrations), store progress regularly. If a timeout happens, you can resume from where you left off instead of starting from scratch. -
Own timeout logic
Sometimes you don’t want PHP to just hard-kill you. You want to:- detect that you’re close to some time budget,
- finish the current small chunk of work,
- store progress,
- stop gracefully.
You can do something like:
$start = microtime(true);
$budget = 25; // seconds, slightly below max_execution_time
foreach ($rows as $row) {
processRow($row);
if (microtime(true) - $start > $budget) {
saveProgress();
break;
}
}
Here, PHP might still have a hard limit at 30 seconds, but your code behaves like a responsible adult and stops around 25, in control, with everything saved.
Web, CLI, and the quiet differences
There’s another dimension that often goes unnoticed: SAPI.
Web SAPI (FPM, Apache, IIS)
- Default
max_execution_timeapplies. - If you don’t change it, 30 seconds is your ceiling.
- Web server/proxy timeouts might be lower or higher.
- Users are directly affected; slow scripts hurt UX and throughput.
CLI
max_execution_timeis typically0by default; no limit.- No users waiting on HTTP responses.
- Jobs can run as long as you allow them—minutes, hours, even days.
But the absence of a limit doesn’t mean “problem solved.” It just means you have to think about:
- runaway processes,
- memory leaks,
- bugs that make workers spin forever.
Using OS-level tools like timeout on Unix can be a practical compromise:
timeout 300 php artisan queue:work
Five minutes for each worker attempt to handle jobs. After that, the OS kills it. That’s its own kind of mercy.
Php max execution time in the wild: hiring, jobs, and real expectations
Since this is for the Find PHP community, let’s talk about the human side.
When employers are looking for a PHP developer, they rarely write:
“Must deeply understand PHP’s max_execution_time handling.”
But they do say:
- “Scales under load.”
- “Understands performance and optimization.”
- “Can build reliable import/export tools.”
- “Experience with queues and background jobs.”
The funny part is: you don’t get any of that without bumping into max_execution_time and staring it down.
As a candidate
If you want to stand out as a PHP developer:
- Be the person who can explain:
- why simply setting
max_execution_time = 600is dangerous, - how to move long-running work into queues,
- how to design reports, exports, and cron jobs cleanly.
- why simply setting
- Share concrete stories:
- “We used to hit 30-second timeouts on report X. We measured it, split the work, pushed the heavy part to a background job, and lowered our request timeout to 10 seconds while improving reliability.”
Those stories are worth more than yet another bullet point about “knowledge of MVC frameworks.”
As an employer or team lead
If you’re trying to hire PHP specialists:
- Listen for how they talk about timeouts, performance, and background work.
- Ask:
- “What do you do when a script often hits the max execution time?”
- “Describe how you’d implement a large CSV import in a PHP web app.”
- “Have you ever used multiple PHP-FPM pools?”
You’ll quickly hear whether someone has just read about max_execution_time or has fought with it on a sleepy Sunday morning with production on fire.
In a way, max_execution_time is a tiny proxy for something bigger: does this person think in terms of architecture, or just in terms of one script at a time?
A small practical checklist
When you next touch max_execution_time, maybe keep this short mental list nearby:
-
For main HTTP requests:
- Aim for 5–15 seconds, not 60+.
- Shorter for reads, slightly longer for writes.
-
For heavy admin endpoints:
- Higher limit is ok,
- but preferably separate pool or path.
-
For background jobs:
- Move them to CLI and queues.
- Use OS-level or supervisor-level timeouts if needed.
-
Before increasing the limit:
- Check database queries (indexes, N+1, unnecessary work).
- Review external calls (add client-side timeouts).
- Verify you aren’t doing “everything in one giant loop.”
-
After changes:
- Measure again. Don’t trust feelings.
- Watch your error logs for timeout patterns.
Not glamorous. But this is the quiet craftsmanship part of PHP work. The bit that keeps sites alive when people start using them for real.
Closing: the quiet line between enough time and too much
Some settings in PHP feel like configuration.max_execution_time feels more like a boundary.
Too low, and you punish normal work.
Too high, and you hide systemic pain until it erupts.
There’s something oddly human about it.
Every developer knows the feeling of “just five more minutes” before sleep, before shipping, before burnout.
Every system knows the stress of “just 300 more seconds” before the next timeout increases, the next workaround, the next patch.
Learning to set max_execution_time wisely is, in a small way, learning to say:
This is how much time we can give this job, without hurting everything else.
And when you start thinking like that—about limits, tradeoffs, and kindness to both users and servers—you’re no longer just writing PHP scripts.
You’re taking responsibility for how they live, how they fail, and how they give you enough space to quietly build the next thing a little better.