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
  • Working with PHP
  • Deploying Symfony 4/5/6 Apps on Heroku

Deploying Symfony 4/5/6 Apps on Heroku

English — 日本語に切り替える

Last updated December 02, 2024

Table of Contents

  • Prerequisites
  • Initial Setup
  • Logging
  • Environment variables
  • URL Rewrites
  • Trusting the Heroku Router
  • Using the Nginx Web Server
  • Further reading

This guide will walk you through the steps of deploying a Symfony version 4 or later application on Heroku. For an introduction to using PHP on Heroku, please refer to Getting Started with PHP on Heroku.

This article is for Symfony versions 4 and later. If you’re instead looking to deploy a Symfony 3 application, please refer to Deploying Symfony 3 apps on Heroku.

Prerequisites

  • Knowledge of the PHP language;
  • A Heroku user account, signup is free and instant;
  • Familiarity with the getting Started with PHP on Heroku guide, with PHP, Composer and the Heroku CLI installed on your computer;
  • An existing Symfony 4 or later application you’d like to deploy (or an empty skeleton app, see below).

Creating a Symfony application

If you’d like to simply follow the along this guide, you may create an empty Symfony application using the following steps.

The application in this tutorial is based on the Symfony Quick Tour guide. It’s worth a read before (or while) following the instructions in this article.

Installing a Symfony Website Skeleton project

Use the composer create-project symfony/website-skeleton symfony-heroku command to bootstrap a new project based on the Symfony website application skeleton, which provides you with the basic structure for a website application. The command below sets it up in a directory named symfony-heroku using the latest version of Symfony.

After downloading the skeleton’s dependencies, the installer will prompt you to enter a few details regarding a database connection and mailer transports. For now, you can simply hit enter at each of these prompts to accept the default values; you can always change them later.

After it’s done, Composer has set up a fully functional Symfony website project in the directory you specified, so you can cd to it.

$ composer create-project symfony/website-skeleton symfony-heroku/
Installing symfony/website-skeleton (v4.3.1.4)
  - Installing symfony/website-skeleton (v4.3.1.4): Downloading (100%)

Created project in symfony-heroku/
Loading composer repositories with package information
Installing dependencies (including require-dev) from lock file
…

$ cd symfony-heroku

Initializing a Git repository

It’s now time to initialize a Git repository and commit the current state:

$ git init -b main
Initialized empty Git repository in ~/symfony-heroku/.git/
$ git add .
$ git commit -m "initial import"
[main (root-commit) 241cbc8] initial import
 42 files changed, 4397 insertions(+)
…

Initial Setup

You’re now going to create an application on Heroku, configure the Symfony environment to use, and then you can simply git push to deploy for the first time!

Creating a new application on Heroku

To create a new Heroku application that you can push to, use the CLI’s create command:

$ heroku create
Creating app... done
https://floating-badlands-41656.herokuapp.com/ | https://git.heroku.com/floating-badlands-41656.git

You are now almost ready to deploy the application. Follow the next section to ensure your Symfony app runs with the settings for the right environment.

Creating a Procfile

To deploy your application to Heroku, you must often first create a Procfile, which tells Heroku what command to use to launch the web server with the correct settings. By default, Heroku will launch an Apache web server together with PHP to serve applications.

However, a special circumstance applies to your Symfony application: the document root is in the public/ directory, and not in the root directory, of the application.

Some older versions of Symfony sometimes created this file automatically, but you should explicitly create this Procfile as part of your application, something that will become necessary anyway once you begin using additional process types.

Simply create a file with the correct command for the web process type and commit it:

$ echo 'web: heroku-php-apache2 public/' > Procfile
$ git add Procfile
$ git commit -m "Heroku Procfile"

Configuring Symfony to run in the prod environment

If you don’t explicitly configure the environment (dev, prod, etc.) to use, Symfony will, by default, use the dev environment in console commands and at runtime.

For Symfony to know it needs to use the prod environment at all times, it reads from the APP_ENV environment variable. You can set environment variables using the heroku config feature. Running following command will set a configuration variable that lets Symfony to know to run in production mode:

$ heroku config:set APP_ENV=prod
Setting config vars and restarting floating-badlands-41656... done
APP_ENV: prod

Deploying to Heroku

Next up, it’s finally time to deploy to Heroku for the first time:

$ git push heroku main
…
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> PHP app detected
remote: -----> Bootstrapping...
remote: -----> Installing system packages...
…
remote: -----> Installing dependencies...
remote:        Composer version 1.8.6
remote:        Loading composer repositories with package information
remote:        Installing dependencies from lock file
…
remote: -----> Preparing runtime environment...
remote: -----> Checking for additional extensions to install...
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote:
remote: -----> Compressing... done, 87.3MB
remote: -----> Launching...
remote:        Released v4
remote:        https://floating-badlands-41656.herokuapp.com/ deployed to Herok
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/floating-badlands-41656.git
 * [new branch]      main -> main

And that’s it! If you now open your browser, either by manually pointing it to the URL heroku create gave you, or by using the Heroku CLI to launch a new browser window, the application will respond:

$ heroku open
Opening floating-badlands-41656... done

If you created an empty application skeleton earlier, you will see a 404 error page that reads “Oops! An Error Occurred” in red letters. This is expected behavior - your application is completely empty, and Symfony is correctly serving a 404 page as a result.

It is likely that you have to set additional environment variables, enable logging, and so forth, for your application to be functional, so follow the next steps to finalize your deployment.

Logging

By default, Symfony will use a built-in minimalist PSR-3 compliant logger that writes its messages to STDERR, meaning they will show up in Heroku’s logs correctly.

If you’re instead using Monolog for logging, then the default configuration will result in logs getting written into your application’s var/log/ directory, which isn’t ideal as Heroku uses an ephemeral filesystem and treats logs as streams of events across all running dynos.

Changing the log destination for production

All that’s required to have Monolog write its output to STDERR is changing config/packages/prod/monolog.yaml. Locate each section in this file that uses a stream logger, and change the value of path to "php://stderr", so it looks roughly like this:

monolog:
    handlers:
        main:
            type: fingers_crossed
            action_level: error
            handler: nested
            excluded_http_codes: [404, 405]
        nested:
            type: stream
            path: "php://stderr"
            level: debug

You can now git add, git commit, and git push heroku main that change as usual.

Viewing application logs

Next, run heroku logs --tail to keep the stream of logs from Heroku open in your terminal. Switch back to your browser and navigate to your Heroku application. As you refresh the page, you’ll see the web server’s access logs on your terminal, as well as anything your application may be logging.

Press Ctrl+C on your keyboard again to leave the heroku logs --tail command.

Environment variables

Your application will likely want to connect to a database, communicate with an email gateway, or just dynamically react to configuration. For this purpose, Heroku will expose any config var, whether it was set by yourself or by an add-on, as an environment variable that you may read in PHP code from the $_ENV superglobal or using getenv().

Using config vars to define environment specific info such as database credentials, log levels, or e-mail gateway information is one of the fundamental principles of the Twelve-Factor App.

Some environment variables, such as APP_SECRET or DATABASE_URL, are used in the default configs for a new application. For example, your config/packages/doctrine.yaml probably looks like this:

doctrine:
    dbal:
        # configure these for your database server
        driver: 'pdo_mysql'
        server_version: '5.7'
        charset: utf8mb4
        default_table_options:
            charset: utf8mb4
            collate: utf8mb4_unicode_ci
        url: '%env(resolve:DATABASE_URL)%'

And in config/packages/framework.yaml, the APP_SECRET environment variable is used:

framework:
    secret: '%env(APP_SECRET)%'

This is how you configure Symfony using environment variables, and some variables have pre-defined defaults in your app’s .env file. Make sure you override any values in that file that need custom values at runtime by running heroku config:set.

For example, to change APP_SECRET in production (this will set it to a random 32 character string):

$ heroku config:set APP_SECRET=$(php -r 'echo bin2hex(random_bytes(16));')

A DATABASE_URL environment variable, on the other hand, will automatically be set when you add a Heroku Postgres instance to your app.

It is recommended that you do not use the symfony/dotenv package in production, but instead set all required environment variables explicitly using heroku config. Moving symfony/dotenv from require to require-dev in your composer.json will ensure that .env files from your application root aren’t picked up in the first place. This corresponds to option #2 in post-deploy step B of the Symfony documentation.

URL Rewrites

Symfony 4 and newer applications no longer contain a .htaccess or other configuration to allow rewriting of URLs in such a way that they do not require the index.php script in the path.

In order to quickly enable rewriting for the Apache web server, you can install the symfony/apache-pack recipe, which places a suitable .htaccess file into your public/ directory:

$ composer require symfony/apache-pack
$ git add composer.json composer.lock symfony.lock public/.htaccess
$ git commit -m "apache-pack"

After deploying this change using git push heroku main, you can access any URL on your application without having to include /index.php in the path.

Trusting the Heroku Router

Heroku’s HTTP Routing routes each request through a layer of reverse proxies which are, among other things, responsible for load balancing and terminating SSL connections. This means that requests received by a dyno will have the last router’s IP address in the REMOTE_ADDR environment variable, and the internal request will always be made using the HTTP protocol, even if the original request was made over HTTPS.

Like most common load balancers or reverse proxies, Heroku provides the original request information in X-Forwarded-… headers (as documented here). Symfony can easily be configured to trust such headers.

Since Heroku sends all requests to an application through a load balancer first, and that load balancer always sets the headers (making it impossible for a client to forge their values), you can configure Symfony to treat the current remote addresses (which is the Heroku router) as a trusted proxy in index.php.

It is important to also prevent Symfony from trusting the Forwarded and X-Forwarded-Host headers, because Heroku’s router does not set those, but Symfony trusts them out of the box once a trusted proxy is set.

This can be achieved by setting the proxy trust method to HEADER_X_FORWARDED_AWS_ELB, which is a shorthand for the alternative notation, Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST.

By default, your public/index.php already contains a section that uses the TRUSTED_PROXIES environment variable to quickly configure this aspect of your application, and it uses the correct Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST combination of allowed headers:

…
if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? $_ENV['TRUSTED_PROXIES'] ?? false) {
    Request::setTrustedProxies(explode(',', $trustedProxies), Request::HEADER_X_FORWARDED_ALL ^ Request::HEADER_X_FORWARDED_HOST);
}
…

However, the internal remote address of the Heroku routing node that forwards the request to a dyno will be different every time, so it must be added to the list of trusted proxies manually if the application is running in the prod environment.

In order to preserve the ability to set additional trusted proxy IP ranges (for instance when using a CDN), while at the same time trusting the Heroku router, you could combine the existing logic that reads the TRUSTED_PROXIES environment variable with a conditional addition of REMOTE_ADDR to the list depending on the application env, like so:

...
$trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? $_ENV['TRUSTED_PROXIES'] ?? false;
$trustedProxies = $trustedProxies ? explode(',', $trustedProxies) : [];
if($_SERVER['APP_ENV'] == 'prod') $trustedProxies[] = $_SERVER['REMOTE_ADDR'];
if($trustedProxies) {
    Request::setTrustedProxies($trustedProxies, Request::HEADER_X_FORWARDED_AWS_ELB);
}
...

This approach is safe on Heroku, because all traffic to your application must go through Heroku’s router, so you can rely on the remote IP address being a trustworthy proxy. In other environments, this may not be the case.

Using the Nginx Web Server

In the initial deployment step further above, the Procfile you created uses the heroku-php-apache2 command to launch the application. The .htaccess file installed by the symfony/apache-pack Flex recipe ensures that URLs are correctly rewritten to the index.php “front controller” script. You may instead use Nginx to run your application.

Creating an Nginx configuration include

Nginx does not support a mechanism similar to Apache’s .htaccess configs, so you have to create a dedicated configuration include and instruct Nginx to use it.

For a Symfony application, the rewrites needed are very simple; you can place the following directives into a file named nginx_app.conf in your application’s root directory:

location / {
    # try to serve file directly, fallback to rewrite
    try_files $uri @rewriteapp;
}

location @rewriteapp {
    # rewrite all to index.php
    rewrite ^(.*)$ /index.php/$1 last;
}

location ~ ^/index\.php(/|$) {
    try_files @heroku-fcgi @heroku-fcgi;
    # ensure that /index.php isn't accessible directly, but only through a rewrite
    internal;
}

Creating a Procfile for Nginx

This time around, you will use the heroku-php-nginx script, and pass it your custom configuration snippet using the -C option:

$ echo 'web: heroku-php-nginx -C nginx_app.conf public/' > Procfile
$ git add Procfile nginx_app.conf
$ git commit -m "Nginx Procfile and config"

After the next git push heroku main, your application will run using the Nginx web server.

Further reading

  • Check out the PHP on Heroku Reference to learn about available versions, extensions, features and behaviors.
  • Review the instructions on customizing web server and runtime settings for PHP to learn more about configuring Apache, Nginx and PHP.
  • Browse the PHP category on Dev Center for more resources.

Keep reading

  • Working with PHP

Feedback

Log in to submit feedback.

Uploading Files to S3 in PHP Managing PHP Extensions

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
  • © 2025 Salesforce, Inc. All rights reserved. Various trademarks held by their respective owners. Salesforce Tower, 415 Mission Street, 3rd Floor, San Francisco, CA 94105, United States
  • heroku.com
  • Legal
  • Terms of Service
  • Privacy Information
  • Responsible Disclosure
  • Trust
  • Contact
  • Cookie Preferences
  • Your Privacy Choices