Deep-dive on the Next Gen Platform. Join the Webinar!

Skip Navigation
Show nav
Dev Center
  • Get Started
  • Documentation
  • Changelog
  • Search
  • Get Started
    • Node.js
    • Ruby on Rails
    • Ruby
    • Python
    • Java
    • PHP
    • Go
    • Scala
    • Clojure
    • .NET
  • Documentation
  • Changelog
  • More
    Additional Resources
    • Home
    • Elements
    • Products
    • Pricing
    • Careers
    • Help
    • Status
    • Events
    • Podcasts
    • Compliance Center
    Heroku Blog

    Heroku Blog

    Find out what's new with Heroku on our blog.

    Visit Blog
  • Log inorSign up
Hide categories

Categories

  • Heroku Architecture
    • Compute (Dynos)
      • Dyno Management
      • Dyno Concepts
      • Dyno Behavior
      • Dyno Reference
      • Dyno Troubleshooting
    • Stacks (operating system images)
    • Networking & DNS
    • Platform Policies
    • Platform Principles
  • Developer Tools
    • Command Line
    • Heroku VS Code Extension
  • Deployment
    • Deploying with Git
    • Deploying with Docker
    • Deployment Integrations
  • Continuous Delivery & Integration (Heroku Flow)
    • Continuous Integration
  • Language Support
    • Node.js
      • Working with Node.js
      • Troubleshooting Node.js Apps
      • Node.js Behavior in Heroku
    • Ruby
      • Rails Support
      • Working with Bundler
      • Working with Ruby
      • Ruby Behavior in Heroku
      • Troubleshooting Ruby Apps
    • Python
      • Working with Python
      • Background Jobs in Python
      • Python Behavior in Heroku
      • Working with Django
    • Java
      • Java Behavior in Heroku
      • Working with Java
      • Working with Maven
      • Working with Spring Boot
      • Troubleshooting Java Apps
    • PHP
      • PHP Behavior in Heroku
      • Working with PHP
    • Go
      • Go Dependency Management
    • Scala
    • Clojure
    • .NET
      • Working with .NET
  • Databases & Data Management
    • Heroku Postgres
      • Postgres Basics
      • Postgres Getting Started
      • Postgres Performance
      • Postgres Data Transfer & Preservation
      • Postgres Availability
      • Postgres Special Topics
      • Migrating to Heroku Postgres
    • Heroku Key-Value Store
    • Apache Kafka on Heroku
    • Other Data Stores
  • AI
    • Working with AI
  • Monitoring & Metrics
    • Logging
  • App Performance
  • Add-ons
    • All Add-ons
  • Collaboration
  • Security
    • App Security
    • Identities & Authentication
      • Single Sign-on (SSO)
    • Private Spaces
      • Infrastructure Networking
    • Compliance
  • Heroku Enterprise
    • Enterprise Accounts
    • Enterprise Teams
    • Heroku Connect (Salesforce sync)
      • Heroku Connect Administration
      • Heroku Connect Reference
      • Heroku Connect Troubleshooting
  • Patterns & Best Practices
  • Extending Heroku
    • Platform API
    • App Webhooks
    • Heroku Labs
    • Building Add-ons
      • Add-on Development Tasks
      • Add-on APIs
      • Add-on Guidelines & Requirements
    • Building CLI Plugins
    • Developing Buildpacks
    • Dev Center
  • Accounts & Billing
  • Troubleshooting & Support
  • Integrating with Salesforce
  • Language Support
  • PHP
  • PHP Behavior in Heroku
  • Optimizing PHP Application Concurrency

Optimizing PHP Application Concurrency

English — 日本語に切り替える

Last updated April 14, 2025

Table of Contents

  • Default Settings and Behavior
  • Tuning Concurrency Using memory_limit
  • Tuning Concurrency Manually

PHP applications on Heroku run under the PHP-FPM FastCGI Process Manager and communicate with the Apache or Nginx web servers using the FastCGI protocol.

PHP-FPM spawns and manages child processes that execute the actual PHP application code. Each of these processes handle one request from the web server at a time. More processes yield greater concurrency and better application performance under higher traffic conditions.

On Heroku, PHP-FPM uses the static process managing mode, which spawns a fixed number of child processes. This configuration is optimal for environments like Heroku, where dyno instances are fully isolated and have a fixed RAM allocation.

The configured PHP memory limit applies to each child process that runs the application, which can consume memory up to that limit before it gets terminated. Configuring the memory limit is the primary method of adjusting PHP application concurrency on Heroku.

Heroku Enterprise customers with Premier or Signature Success Plans can request in-depth guidance on this topic from the Customer Solutions Architecture (CSA) team. Learn more about Expert Coaching Sessions here or contact your Salesforce account executive.

Default Settings and Behavior

If your application uses multiple buildpacks, ensure that the PHP buildpack, as the primary language buildpack of your application, executes after other language buildpacks. Otherwise, the WEB_CONCURRENCY defaults of the other buildpack can overwrite the value previously set by the PHP buildpack.

The number of child processes spawned is controlled by the pm.max_children setting of PHP-FPM. That setting is determined using the WEB_CONCURRENCY environment variable that Heroku’s PHP buildpack automatically sets to a suitable value.

When booting an application, we automatically detect the dyno type and set the WEB_CONCURRENCY environment variable to an appropriate default value.

$ heroku ps:scale web=1:standard-2x
$ heroku logs
2024-02-06T14:52:40… heroku[web.1]: State changed from down to starting
2024-02-06T14:52:42… heroku[web.1]: Starting process with command `heroku-php-apache2`
2024-02-06T14:52:43… app[web.1]: Available RAM is 1G Bytes
2024-02-06T14:52:43… app[web.1]: PHP memory_limit is 128M Bytes
2024-02-06T14:52:43… app[web.1]: Starting php-fpm with 8 workers...
2024-02-06T14:52:43… app[web.1]: Starting httpd...
2024-02-06T14:52:44… heroku[web.1]: State changed from starting to up

WEB_CONCURRENCY Defaults by Dyno Type

The default value for memory_limit is 128M in all currently supported versions of PHP, but you can configure a different value. The following sections outline the WEB_CONCURRENCY defaults Heroku uses for the different generations of the Heroku platform.

All defaults are intentionally chosen to not leave any “headroom” for the PHP-FPM parent process or the web server processes. Applications are unlikely to consume their entire memory limit on each request and at full saturation, so dynos are slightly over-subscribed by default.

WEB_CONCURRENCY on Cedar Dynos

For dynos in Cedar, the following WEB_CONCURRENCY defaults apply for the possible dyno types at three different example PHP memory_limit values:

Dyno Type Dyno RAM CPU cores WEB_CONCURRENCY for memory_limit of…
64M 128M1 256M
Eco
Basic
Standard-1X
512 MB shared 8 4 2
Standard-2X 1 GB shared 16 8 4
Private-S
Shield-S
1 GB 2 16 8 4
Performance-M
Private-M
Shield-M
2.5 GB 2 40 20 10
Performance-L
Private-L
Shield-L
14 GB 8 224
962
112
482
56
242
Performance-L-RAM
Private-L-RAM
Shield-L-RAM
30 GB 4 128 64 32
Performance-XL
Private-XL
Shield-XL
62 GB 8 288 144 72
Performance-2XL
Private-2XL
Shield-2XL
126 GB 16 640 320 160
1: The default value for memory_limit is 128M in all currently supported versions of PHP.
2: On Performance-L dynos, WEB_CONCURRENCY defaults to lower values for PHP versions before 7.4 for backwards compatibility.

For backwards compatibility, the defaults for the performance-l dyno type don’t use the entire amount of memory available for PHP versions before 7.4. If you want to use more processes than Heroku automatically assigns, see Tuning Concurrency Manually.

WEB_CONCURRENCY on Fir Dynos

For dynos in Fir, the following WEB_CONCURRENCY defaults apply for the possible dyno types at three different example PHP memory_limit values:

Dyno Type Dyno RAM CPU cores WEB_CONCURRENCY for memory_limit of…
64M 128M1 256M
dyno-1c-0.5gb 512 MB 1 8 4 2
dyno-2c-1gb 1 GB 2 16 8 4
dyno-1c-4gb 4 GB 1 20 10 5
dyno-2c-8gb 8 GB 2 40 20 10
dyno-4c-16gb 16 GB 4 112 56 28
dyno-8c-32gb 32 GB 8 256 128 64
dyno-16c-64gb 64 GB 16 576 288 144
dyno-2c-4gb 4 GB 2 40 20 10
dyno-4c-8gb 8 GB 4 96 48 24
dyno-8c-16gb 16 GB 8 224 112 56
dyno-16c-32gb 32 GB 16 512 256 128
dyno-32c-64gb 64 GB 32 1024 512 256
dyno-1c-8gb 8 GB 1 24 12 6
dyno-2c-16gb 16 GB 2 56 28 14
dyno-4c-32gb 32 GB 4 128 64 32
dyno-8c-64gb 64 GB 8 288 144 72
dyno-16c-128gb 128 GB 16 640 320 160
1: The default value for memory_limit is 128M in all currently supported versions of PHP.

WEB_CONCURRENCY Default Calculations

To compute a WEB_CONCURRENCY value that doesn’t exceed the available RAM nor spawn too many PHP-FPM worker processes per available CPU core, Heroku calculates:

  1. A RAM-based limit for WEB_CONCURRENCY using the PHP memory_limit and available RAM (the RAM-based limit)
  2. A CPU-based limit for WEB_CONCURRENCY using the PHP memory_limit, available RAM, and a logarithmic scaling factor that includes the CPU core count

Heroku uses the lower of these two limits as the value for WEB_CONCURRENCY. In all cases, the configured PHP memory_limit is determined automatically.

The default memory_limit on Heroku is the default for the respective PHP version. The limit is currently 128 MB for all versions of PHP.

If the CPU-based limit overrides the RAM-based limit, a message emits during startup:

$ heroku ps:scale web=1:performance-xl
$ heroku logs
2024-02-06T14:52:40… heroku[web.1]: State changed from down to starting
2024-02-06T14:52:42… heroku[web.1]: Starting process with command `heroku-php-apache2`
2024-02-06T14:52:43… app[web.1]: Available RAM is 62G Bytes
2024-02-06T14:52:43… app[web.1]: PHP memory_limit is 128M Bytes
2024-02-06T14:52:43… app[web.1]: Maximum number of workers that fit available RAM at memory_limit is 496
2024-02-06T14:52:43… app[web.1]: Limiting number of workers to 144
2024-02-06T14:52:43… app[web.1]: Starting php-fpm with 144 workers...
2024-02-06T14:52:43… app[web.1]: Starting httpd...
2024-02-06T14:52:44… heroku[web.1]: State changed from starting to up

Regardless of which calculation is used for WEB_CONCURRENCY, there’s a linear correlation between the PHP memory_limit and the number of PHP-FPM child processes. For example, halving the memory_limit value doubles the WEB_CONCURRENCY result, and vice versa, as shown in the table further above.

Tuning Concurrency Using memory_limit

Configuring the memory limit is the primary method of adjusting PHP application concurrency on Heroku. Refer to this table to see how the number of child processes changes for each dyno type and memory limit setting. See Determining a Suitable Memory Limit for more guidance.

Configuring the memory_limit for PHP-FPM

Setting Memory Limit via .user.ini

You can add a .user.ini config file containing a memory limit setting to your application’s document root, usually the top-level directory of your application. For example, to set a memory limit of 64 MB for an application, set it in the .user.ini:

memory_limit = 64M

You must use the correct shorthand notation required by PHP to indicate megabytes using the M suffix.

Your application’s document root can differ from the top-level directory of your application if you configured it using a Procfile command argument.

If you deploy your app with this file, you can see the number of workers automatically adjust for the given memory limit. For example, for a Standard-1X dyno:

$ heroku logs
2019-01-15T07:51:24.476056+00:00 heroku[web.1]: State changed from down to starting
2019-01-15T07:51:30.765076+00:00 heroku[web.1]: Starting process with command `heroku-php-apache2`
2019-01-15T07:51:33.188816+00:00 app[web.1]: Optimizing defaults for 1X dyno...
2019-01-15T07:51:33.370674+00:00 app[web.1]: 8 processes at 64MB memory limit.
2019-01-15T07:51:33.414407+00:00 app[web.1]: Starting php-fpm...
2019-01-15T07:51:33.414423+00:00 app[web.1]: Starting httpd...
2019-01-15T07:51:35.865579+00:00 heroku[web.1]: State changed from starting to up

Additional .user.ini files in sub-directories of the document root don’t get evaluated when Heroku determines the memory limit at dyno boot time. They can take effect at runtime as documented when serving requests to PHP files in such directories. Keep this in mind in the unlikely case you have different memory_limit settings for different sub-directories of your application. We recommend using the same settings for the sub-directories.

Setting the Memory Limit using PHP-FPM Configuration

Instead of a .user.ini file, you can also use a PHP-FPM config include to add a php_value or php_admin_value directive to change the memory_limit setting. For example, to set a memory limit of 64 MB for an application, create a file named, for example, fpm_custom.conf:

php_value[memory_limit] = 64M

For these settings to take effect, you must use the -F option in your Procfile command to load the config:

web: heroku-php-apache2 -F fpm_custom.conf

If you deploy your app with the new fpm_custom.conf and the changed Procfile, you can see the number of workers automatically adjust for the given memory limit. For example, for a Standard-1X dyno:

$ heroku logs
2019-01-15T07:51:24.476056+00:00 heroku[web.1]: State changed from down to starting
2019-01-15T07:51:30.765076+00:00 heroku[web.1]: Starting process with command `heroku-php-apache2 -F fpm_custom.conf`
2019-01-15T07:51:33.109122+00:00 app[web.1]: Using PHP-FPM configuration include 'fpm_custom.conf'
2019-01-15T07:51:33.188816+00:00 app[web.1]: Optimizing defaults for 1X dyno...
2019-01-15T07:51:33.370674+00:00 app[web.1]: 8 processes at 64MB memory limit.
2019-01-15T07:51:33.414407+00:00 app[web.1]: Starting php-fpm...
2019-01-15T07:51:33.414423+00:00 app[web.1]: Starting httpd...
2019-01-15T07:51:35.865579+00:00 heroku[web.1]: State changed from starting to up

Runtime Changes of memory_limit

Any change made to the memory limit at runtime using ini_set("memory_limit", ...) won’t affect the concurrency, as the memory limit used for calculating WEB_CONCURRENCY is determined at boot time.

If you have many processes increasing the memory limit beyond its initially configured value at runtime using ini_set(), and these processes are actually consuming that additional memory, you can get R14 errors. That error indicates that your application started paging to disk, which can degrade performance. In this case, either increase the static memory_limit, or set WEB_CONCURRENCY manually to a lower value.

In many cases, it can be desirable to have a lower memory limit to achieve higher concurrency. Use ini_set() to dynamically set a higher limit at runtime for only the few code paths in your application that temporarily require a higher memory limit in this case.

Determining a Suitable Memory Limit

The amount of memory your application needs depends on the amount of data it processes during a request, and how much of that data it holds in memory at the same time. Tasks like image processing or handling large database result sets are typically memory intensive.

The default memory limit of 128 MB in PHP is a conservative default intended to give enough “breathing room” for virtually any kind of application. It’s likely that your code doesn’t consume that much memory during a request, so lowering the limit is a great way of optimizing your application performance.

After determining the maximum memory usage for your application, allow for a safety margin when setting your limit for future growth and unforeseen circumstances. For example, growing data sets over time.

Measuring and Optimizing Memory Usage Locally

On your development machine, use the memory_get_peak_usage() function in your code, usually towards the very end of a script, to determine the peak memory used. For instance, put file_put_contents("path/to/logfile", memory_get_peak_usage()."\n", FILE_APPEND); at the end of your code, run a load/feature test on it, and determine the highest value in that log file. For example, using sort path/to/logfile | tail -n 1.

Another approach is lowering the memory limit configured in PHP in subsequent steps, for example, increments of 16 MB, until you start getting “memory limit exceeded” error messages. A limit of 64 MB is often safe and results in twice as many worker processes compared to the default configuration.

When performing load tests locally, using ab, siege, httperf, or similar, you can also observe the amount of memory consumed by your php-fpm processes using ps or top.

To properly profile your applications during development, we recommend XHProf. The complementing xhprof.io GUI makes navigating profiling results easy and convenient.

Remember to use realistic conditions when performing tests locally, for example, using suitable large result sets from a database. It’s also highly recommended to audit your code for any functions, loops, or algorithms that can scale unfavorably with an increase in input data size and optimize these accordingly.

Measuring Memory Usage on Heroku

Platform-specific nuances aside, an application’s memory consumption, given the same input, should be virtually identical between your local development environment and Heroku, so it’s recommended to work on finding the optimal memory limit in development first.

The log-runtime-metrics Heroku Labs functionality will periodically report memory consumption to the heroku logs stream. When performing load tests, a memory usage that’s drastically lower than what’s available for the respective dyno type may be an indicator that your memory limit is set too high. Try lowering the limit to increase concurrency and thus actual memory usage.

You may also inspect a basic memory usage report and graph in the overview section for your app on the Heroku Dashboard. Note that this displays an average value across all running dynos.

Application performance monitoring tools such as New Relic will record memory usage and report them for your analysis.

Heroku Pipelines help automate a workflow for promoting deployments from one environment to another (e.g., from staging to production). This makes it easy to measure and optimize changes to memory usage on a non-production app that you can then promote to production when the changes are ready.

Tuning Concurrency Manually

To manually set the number of child processes running your application, you can adjust the WEB_CONCURRENCY environment variable by setting a config var.

For instance, to statically set the number of child processes to 8, use heroku config:set:

$ heroku config:set WEB_CONCURRENCY=8

When setting WEB_CONCURRENCY manually, ensure that its value multiplied by your memory_limit doesn’t exceed the amount of RAM available on your dyno type.

Setting the config var causes your application to restart, and your dyno(s) report the static setting during startup:

$ heroku logs
2019-01-15T07:51:24.476056+00:00 heroku[web.1]: State changed from down to starting
2019-01-15T07:51:30.765076+00:00 heroku[web.1]: Starting process with command `heroku-php-apache2 -F fpm_custom.conf`
2019-01-15T07:51:33.109122+00:00 app[web.1]: Using PHP-FPM configuration include 'fpm_custom.conf'
2019-01-15T07:51:33.370674+00:00 app[web.1]: Using WEB_CONCURRENCY=8 processes.
2019-01-15T07:51:33.414407+00:00 app[web.1]: Starting php-fpm...
2019-01-15T07:51:33.414423+00:00 app[web.1]: Starting httpd...
2019-01-15T07:51:35.865579+00:00 heroku[web.1]: State changed from starting to up

If you set WEB_CONCURRENCY to a fixed value, remember to adjust it when you scale to a different dyno type to optimize for the amount of available RAM on the new dyno type.

Keep reading

  • PHP Behavior in Heroku

Feedback

Log in to submit feedback.

PHP Behavior in Heroku PHP Application Logging

Information & Support

  • Getting Started
  • Documentation
  • Changelog
  • Compliance Center
  • Training & Education
  • Blog
  • Support Channels
  • Status

Language Reference

  • Node.js
  • Ruby
  • Java
  • PHP
  • Python
  • Go
  • Scala
  • Clojure
  • .NET

Other Resources

  • Careers
  • Elements
  • Products
  • Pricing
  • RSS
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku Blog
    • Heroku News Blog
    • Heroku Engineering Blog
  • Twitter
    • Dev Center Articles
    • Dev Center Changelog
    • Heroku
    • Heroku Status
  • Github
  • LinkedIn
  • heroku.com
  • Terms of Service
  • Privacy (日本語)
  • Cookies
  • Cookie Preferences
  • Your Privacy Choices
  • © 2025 Salesforce.com