Heroku PHP Support

This document describes the general behavior of Heroku as it relates to the recognition and execution of PHP applications.

Activation

The Heroku PHP Support will be applied to applications only when the application has a file named composer.json in the root directory. Even if an application has no Composer dependencies, it must include at least an empty ( {} ) composer.json in order to be recognized as a PHP application.

When Heroku recognizes a PHP application, it will respond accordingly during a push:

$ git push heroku master -----> PHP app detected …

If composer.json specifies dependencies of any kind in its require section, the corresponding composer.lock that gets generated by running composer update must also be committed to the repository, or the push will be rejected. This ensures that the dependencies Heroku installs are exactly the same as in any other environment. Please refer to the “Manage Dependencies” section of the Deploying PHP guide for detailed instructions.

PHP runtimes

Heroku allows you to run your application using the official PHP runtime.

Supported runtimes

Heroku’s PHP support extends to applications using PHP 7.2 (64-bit), PHP 7.3 (64-bit) or PHP 7.4 (64-bit).

The support for PHP release series on the Heroku platform follows the PHP Group’s support policy, typically with active updates for two years after an initial x.y.0 version, followed by a year of security updates.

Stack-specific notes

the Argon2 password hashing algorithm (supported by PHP 7.2 and later) is available on the heroku-18 stack only;

stack only; the sodium extension (bundled with PHP 7.2 and later) is not available on the deprecated cedar-14 stack.

Runtime settings

All PHP runtimes use the respective release’s php.ini-production file as their base php.ini configuration.

Notwithstanding the above, the following INI directives are set to Heroku-specific values:

date.timezone is set to UTC

is set to error_reporting is set to E_ALL & ~E_STRICT

is set to expose_php is set to Off

is set to session.sid_length is set to 32 (for PHP 7.1 and later)

is set to (for PHP 7.1 and later) short_open_tag is set to On

is set to user_ini.cache_ttl is set to 86400

is set to variables_order is set to EGPCS

In addition, the PHP runtimes always have OPcache enabled for improved performance, with a configuration optimized for the specific characteristics of Heroku’s dynos:

opcache.enable_cli is set to 1

is set to opcache.validate_timestamps is set to 0

is set to opcache.memory_consumption is set to 128 (for PHP 5)

is set to (for PHP 5) opcache.interned_strings_buffer is set to 8 (for PHP 5)

is set to (for PHP 5) opcache.max_accelerated_files is set to 4000 (for PHP 5)

PHP CLI

The memory_limit PHP INI directive for the php CLI executable defaults to the full available dyno memory for PHP versions 7.2, 7.3 and 7.4. This means that e.g. worker dynos using a php … command use this memory limit instead of the default PHP INI value of 128M .

Default runtime

Applications that do not use a composer.json , or where composer.lock contains no requirements for package php even in any dependent package, will pick the latest possible version of PHP 5 on the heroku-16 stack and the (deprecated) cedar-14 stack, and the latest possible version of PHP 7 on the heroku-18 and later stacks.

Applications that have suitably permissive version selectors specified for package php in composer.json and all dependencies in composer.lock will use PHP 7 if all required extensions are also available for that version (see below).

Selecting a runtime

You may select the runtime(s) to use via Composer Platform Packages in composer.json . Upon a push, Heroku will read the necessary information from composer.lock , if present, and fall back to composer.json otherwise.

For example, the following composer.json will instruct Heroku to use the latest version of PHP 7 greater or equal to 7.2.0, but not PHP 8 (once that would be released):

{ "require": { "php": "^7.2.0" } }

Never specify an exact version like “7.2.13” for PHP, or any other package. Instead, use the “ ^ ” or “ ~ ” next significant release operators to ensure that you get appropriate updates upon push as they become available. For PHP, that means specifying e.g. “ ~7.3.0 ” will always get you the latest 7.3.x release, which will be fully compatible with other releases from the 7.3 series (but may contain security or performance updates), but not 7.4.0 or later. To get the latest PHP 7.3 or later, but not PHP 8 (once that’s released), you’d specify “ ^7.3.0 ”.

Heroku will print the versions that were resolved and will be installed:

-----> Installing platform packages... - php (7.4.10)

Specifying an unknown or unsupported version will result in an error listing potential alternative versions.

PHP

Specify “ php ” as a dependency in the require section of your composer.json to use PHP as the runtime; for instance, for PHP 7.2 or later:

{ "require": { "php": "^7.2.0" } }

It is recommended you always prefix the minimum version you’d like to receive with the ^ selector. This ensures that you’ll receive updated versions as they become available. In the example above, you’d get PHP 7.2.0 or later, including newer versions in the 7.x series, but not PHP 8, once released (which you may want to test your application against first in case of any unexpected backwards compatibility issues).

Next, ensure that your new requirements are “frozen” to composer.lock by running:

$ composer update

Finally, don’t forget to git add and git commit both files!

Supported versions

PHP

The following PHP versions are available on Heroku and receive regular updates:

7.2.33

7.3.22

7.4.10 (not available on the deprecated cedar-14 stack)

PHP 7.2 is in security-only maintenance mode and will be fully end-of-life at the end of 2020. Only critical security fixes will be provided by the PHP maintainers for this release series. Users are strongly encouraged to update their applications to PHP 7.3 or later at their earliest convenience. For more information on support timelines for PHP releases, refer to the Supported Versions page on the official PHP website.

Legacy versions

PHP

The following legacy PHP versions are still available for builds on Heroku, but no longer receive updates of any kind:

5.5.38 (deprecated cedar-14 stack only)

stack only) 5.6.39 (deprecated cedar-14 stack and heroku-16 stack only)

stack and stack only) 7.0.33 (deprecated cedar-14 stack and heroku-16 stack only)

stack and stack only) 7.1.33

PHP 5.5, 5.6, 7.0 and 7.1 are end-of-life. No more bugfixes, including critical security fixes, will be provided by the PHP maintainers for these versions. Users are strongly encouraged to update their applications to PHP 7.3 or later at their earliest convenience. For more information on support timelines for PHP releases, refer to the Supported Versions page on the official PHP website.

Upgrades

If you deploy an application that does not declare a runtime version dependency, the then-latest version of PHP 7 will be used. Your application will be upgraded to more recent versions of PHP 7 if available automatically upon the next deploy.

If your application declares runtime version dependencies, the most recent version matching the version constraint will be selected for installation.

Extensions

PHP 7.2

The following built-in extensions are enabled automatically on Heroku (this list does not include extensions that PHP enables by default, such as DOM, JSON, PCRE or PDO):

The following built-in extensions have been built “shared” and can be enabled through composer.json (internal identifier names given in parentheses):

The following third-party extensions can be enabled through composer.json (internal identifier names given in parentheses):

PHP 7.3

The following built-in extensions are enabled automatically on Heroku (this list does not include extensions that PHP enables by default, such as DOM, JSON, PCRE or PDO):

The following built-in extensions have been built “shared” and can be enabled through composer.json (internal identifier names given in parentheses):

The following third-party extensions can be enabled through composer.json (internal identifier names given in parentheses):

PHP 7.4

The following built-in extensions are enabled automatically on Heroku (this list does not include extensions that PHP enables by default, such as DOM, JSON, PCRE or PDO):

The following built-in extensions have been built “shared” and can be enabled through composer.json (internal identifier names given in parentheses):

The following third-party extensions can be enabled through composer.json (internal identifier names given in parentheses):

Using optional extensions

You may declare any optional extensions you want to use via composer.json using Composer Platform Packages; simply prefix any of the identifiers in the list of extensions above with “ ext- ” in the package name.

For example, to enable bcmath, MCrypt, Memcached, and the third-party MongoDB:

{ "require": { "ext-bcmath": "*", "ext-mcrypt": "*", "ext-memcached": "*", "ext-mongodb": "^1.1.0" } }

It is recommended that you use “ * ” as the version selector when specifying extensions that are bundled with PHP, as their version numbers can be highly inconsistent (they often report their version as “0”).

Next, ensure that your new requirements are “frozen” to composer.lock by running:

$ composer update

Finally, don’t forget to git add and git commit both files!

If you do not have the desired extension available locally on your computer, the composer update step would fail because the requirements in composer.json cannot be satisfied. If you cannot install the missing extension on your computer using pecl , brew , or similar methods (something you absolutely should do in the interest of maintaining dev/prod parity), you can instruct composer to ignore the missing (so-called “platform”) requirements: $ composer update --ignore-platform-reqs The same --ignore-platform-reqs flag may also be used when running composer install on subsequent dependency installations in new environments, e.g. on other developers’ computers, where the extension is similarly unavailable.

Upon the next push, Heroku will install and enable the corresponding PHP extensions:

-----> Installing platform packages... - php (7.4.10) - ext-bcmath (bundled with php) - ext-mcrypt (bundled with php) - ext-mongodb (1.8.0) - ext-memcached (3.1.5) - apache (2.4.46) - nginx (1.18.0)

Any PHP extension required by a dependency of a project pushed to Heroku will be installed automatically, as the list of extensions to be installed is read from composer.lock . For example, if a project depends on the stripe/stripe-php PHP SDK for Stripe, the mbstring extension required by the Stripe SDK will automatically be installed upon deploy, without the ext-mbstring package having to be listed explicitly in the require section of your main composer.json .

Customizing settings

PHP

Any .user.ini file that is placed into a project according to the instructions in the PHP manual will be loaded after the main php.ini . You can use these to set any directive permitted in PHP_INI_ALL , PHP_INI_USER and PHP_INI_PERDIR contexts.

For additional details on this and other ways of customizing settings for the PHP runtime, please refer to the corresponding Dev Center article.

Build behavior

Installation of dependencies

The following command is run during a deploy to resolve dependencies unless composer.json is empty and no composer.lock is present:

$ composer install --no-dev --prefer-dist --optimize-autoloader --no-interaction

Heroku will not install development dependencies from the require-dev section of composer.json . However, if the require-dev section contains a PHP runtime version requirement or lists a dependency that contains such a requirement, then the require section of composer.json must also contain a PHP runtime version requirement or list a dependency that contains such a requirement. This is to ensure that Heroku does not select a default PHP runtime version that conflicts with what other environments (which include require-dev dependencies) install.

The installed version of Composer will be printed for your reference before installation begins. Builds are currently run using Composer version 1.10.13.

Composer’s cache directory is preserved between pushes to speed up package installation.

Custom compile step

For applications that wish to execute an additional compilation step during a build that shouldn’t be part of a standard post-install-cmd Composer script, for example an asset compilation or cache pre-warming procedure, a compile custom command, if present in composer.json , will be executed using the following command:

$ composer compile --no-dev --no-interaction

Any such custom script command defined in composer.json can either be a single string, or an array of multiple commands to execute; example: { "scripts": { "compile": [ "php app/console assetic:dump --env=prod --no-debug", "MyVendor\\MyClass::postDeployComposerCallback" ] } } Composer’s bin-dir is pushed on top of $PATH during command execution, so binaries installed by dependencies are easily accessible as CLI commands when writing scripts without having to use vendor/bin/ or a similar prefix.

Private repositories

To use private repositories such as Private Packagist, or packages from a source that requires authentication (such as from a private GitHub repository), Composer must be provided with authentication details (typically a token generated by the provider or service).

On a development machine, these are typically stored by Composer in auth.json , but on Heroku, such secrets are stored as environment variables. The COMPOSER_AUTH environment variable is automatically read by Composer; its JSON structure is identical to auth.json .

The following entries are allowed as top-level keys in the JSON document:

Each entry then contains a hash of domains as keys and authentication details as values; the authentication detail structure is specific to each of the sources above and described in the documentation.

When using GitHub Enterprise or the Self-Managed version of GitLab, remember to also set the github-domains or gitlab-domains config option inside your project’s composer.json .

For example, to store authentication details for a Private Packagist, account, you’d set the COMPOSER_AUTH variable using heroku config:set with http-basic details (replacing “YOURTOKEN” with the actual token Private Packagist generated, of course):

$ heroku config:set COMPOSER_AUTH='{"http-basic":{"repo.packagist.com":{"username":"token","password":"YOURTOKEN"}}}'

To give another example, when using code from private GitHub repositories as Composer dependencies, a personal OAuth token can be set for authentication. After creating a new Token, you can set it on Heroku (replacing “YOURTOKEN” with the actual token GitHub generated, of course):

$ heroku config:set COMPOSER_AUTH='{"github-oauth":{"github.com":"YOURTOKEN"}}'

The private repository URL in your composer.json must use the https:// and not the git:// protocol for Composer to be able to use the OAuth token for authentication.

Several sets of authentication details can of course also be combined into a single document; for example, to use both private GitHub and private BitBucket repositories:

$ heroku config:set COMPOSER_AUTH='{ "github-oauth": {"github.com": "YOURTOKEN"}, "bitbucket-oauth": {"bitbucket.org": { "consumer-key": "YOURKEY", "consumer-secret": "YOURSECRET"} } }'

You may use line breaks within quotes when setting environment variables on Heroku as shown in the example above, but you must ensure that the quoting is correct when running the heroku config:set command.

Composer configuration

For convenience, the following settings for Composer are automatically set using environment variables:

$COMPOSER_MEMORY_LIMIT defaults to the available dyno memory;

defaults to the available dyno memory; $COMPOSER_MIRROR_PATH_REPOS defaults to 1 ;

defaults to ; $COMPOSER_NO_INTERACTION defaults to 1 .

Runtime behavior

The $PATH environment variable contains all necessary paths for an application to function at runtime. The Composer bin-dir is appended to $PATH for convenience.

PHP-FPM configuration

PHP-FPM is set up to automatically spawn a suitable number of worker processes depending on dyno size and the configured PHP memory_limit . Please refer to the Optimizing PHP Application Concurrency article for more details.

Timeouts

When a request reaches the Heroku router’s request timeout, a PHP-FPM process would continue to run, potentially as long as it takes for e.g. an external timeout to occur. This would tie up that PHP-FPM process, which could then no longer respond to other incoming requests.

For applications using PHP 7.4 or later, by default, PHP-FPM will therefor

log a backtrace of requests that take longer than three seconds ( request_slowlog_timeout directive), and

directive), and terminate requests that have exceeded an execution time of 30 seconds ( request_terminate_timeout directive) and therefor likely timed out.

You may adjust the settings for PHP-FPM to change these (or other) configuration settings.

Composer configuration

For convenience, the following settings for Composer are automatically set using environment variables:

$COMPOSER_MEMORY_LIMIT defaults to the available dyno memory;

defaults to the available dyno memory; $COMPOSER_MIRROR_PATH_REPOS defaults to 1 ;

defaults to ; $COMPOSER_NO_INTERACTION defaults to 1 ;

defaults to ; $COMPOSER_PROCESS_TIMEOUT defaults to 0 .

Web servers

Heroku supports Apache 2.4 (2.4.43) and Nginx 1.18 (1.18.0) as dedicated Web servers. For testing purposes, users may of course also use PHP’s built-in Web server, although this is not recommended.

In the absence of a Procfile entry for the “web” dyno type, the Apache Web server will be used together with the PHP runtime.

Apache

Apache interfaces with PHP-FPM via FastCGI using mod_proxy_fcgi .

To start Apache together with PHP-FPM and all the correct settings, use the heroku-php-apache2 script in the Composer vendor bin dir (usually vendor/bin ):

web: vendor/bin/heroku-php-apache2

Vendor binaries are usually installed to vendor/bin by Composer, but under some circumstances (e.g. when running a Symfony2 standard edition project), the location will be different. Run composer config bin-dir to find the right location.

By default, the root folder of your project will be used as the document root. To use a sub-directory, you may pass the name of a sub-folder as the argument to the boot script, e.g. “public_html”:

web: vendor/bin/heroku-php-apache2 public_html

You can use regular .htaccess files to customize Apache’s behavior, e.g. for URL rewriting. For additional details on this and other options to customize settings for Apache, please refer to the corresponding Dev Center article.

Nginx

Nginx interfaces with PHP-FPM via FastCGI.

To start Nginx together with PHP-FPM and all the correct settings, use the heroku-php-nginx script in the Composer vendor bin dir (usually vendor/bin ):

web: vendor/bin/heroku-php-nginx

Vendor binaries are usually installed to vendor/bin by Composer, but under some circumstances (e.g. when running a Symfony2 standard edition project), the location will be different. Run composer config bin-dir to find the right location.

By default, the root folder of your project will be used as the document root. To use a sub-directory, you may pass the name of a sub-folder as the argument to the boot script, e.g. “public_html”:

web: vendor/bin/heroku-php-nginx public_html

For additional details on different ways of customizing settings for Nginx, please refer to the corresponding Dev Center article.

PHP Built-in Web server

For testing purposes, you may start PHP’s built-in Web server by using php -S 0.0.0.0:$PORT as the entry for “web” in your Procfile :

web: php -S 0.0.0.0:$PORT

The Procfile must contain $PORT in the line shown above. It’s used by Heroku at runtime to dynamically bind the web server instance to the correct port for the dyno.

It is important to bind to all interfaces using 0.0.0.0 , otherwise Heroku’s routing won’t be able to forward requests to the web server!

You may also pass an alternative document root or use a so called router script to process requests. For details, please refer to the documentation for the built-in Web server.