Deploying a PHP application on Apache is complete only when the application files, document root, PHP handler, virtual host, and reload sequence all point at the same site. A server can have Apache and PHP installed and still show the default page, expose the wrong directory, or serve PHP source as text when one of those pieces is missing.

On Debian and Ubuntu systems, the usual deployment path is to place the application under /var/www, serve only its public directory, enable PHP-FPM through Apache's FastCGI proxy modules, create a file under /etc/apache2/sites-available, test the syntax with apache2ctl configtest, and restart or reload the apache2 service after the test passes.

The examples below use the current Ubuntu 26.04 package defaults, where php-fpm installs php8.5-fpm and the Apache snippet is enabled with a2enconf php8.5-fpm. Replace app.example.test, /var/www/app.example.test/public, and the 8.5 PHP branch with the hostname, public directory, and packaged PHP branch on the target host.

Steps to deploy a PHP application on Apache:

  1. Choose the hostname, application root, and public document root before copying files.
    ItemExample value
    Hostnameapp.example.test
    Application root/var/www/app.example.test
    Public document root/var/www/app.example.test/public
    Virtual host file/etc/apache2/sites-available/app.example.test.conf

    Point DocumentRoot at the application's public entry directory, not at the whole project tree. Framework files such as .env, vendor, storage, or source directories should not be web-readable unless the application explicitly places them under the public directory.

  2. Install Apache and the distro PHP-FPM package.
    $ sudo apt update
    ##### snipped #####
    Reading package lists... Done
    
    $ sudo apt install --assume-yes apache2 php-fpm
    Reading package lists... Done
    Building dependency tree... Done
    Reading state information... Done
    The following NEW packages will be installed:
      apache2 php-fpm php8.5-fpm
    ##### snipped #####
    Setting up php8.5-fpm (8.5.4-0ubuntu1.1) ...
    NOTICE: Not enabling PHP 8.5 FPM by default.
    NOTICE: To enable PHP 8.5 FPM in Apache2 do:
    NOTICE: a2enmod proxy_fcgi setenvif
    NOTICE: a2enconf php8.5-fpm

    Install application-specific extensions such as php-mysql, php-pgsql, php-gd, or php-xml only when the application requires them.

  3. Confirm the versioned PHP-FPM service and Apache configuration snippet installed by the package.
    $ systemctl list-unit-files 'php*-fpm.service' --no-legend
    php8.5-fpm.service enabled enabled
    
    $ ls /etc/apache2/conf-available/php*-fpm.conf
    /etc/apache2/conf-available/php8.5-fpm.conf

    Use the same PHP branch in the service name, Apache snippet, and any socket path. A host running another distro release may show php8.3-fpm, php8.4-fpm, or another packaged branch.

  4. Start PHP-FPM and confirm the service is active before connecting Apache to it.
    $ sudo systemctl enable --now php8.5-fpm
    
    $ systemctl is-active php8.5-fpm
    active

    If PHP-FPM is not active, fix that service first. Apache can parse a PHP-FPM configuration file even when the target socket is missing, but PHP requests will fail at runtime.

  5. Create the public document root and place the PHP application entry point there.
    $ sudo install --directory --mode=0755 /var/www/app.example.test/public
    
    $ sudo tee /var/www/app.example.test/public/index.php >/dev/null <<'EOF'
    <?php
    header('Content-Type: text/plain');
    echo "Deployed PHP app OK\n";
    echo "sapi=" . PHP_SAPI . "\n";
    EOF

    For a real application, copy the release files into the same application root and keep only the public entry directory exposed as DocumentRoot. Grant write permission only to application-owned storage, cache, or upload directories that need it.

  6. Set ownership so Apache and PHP-FPM can read the deployed files.
    $ sudo chown -R root:www-data /var/www/app.example.test

    The www-data group is the packaged Apache and PHP-FPM runtime group on Debian and Ubuntu. Keep code owned by an administrative account or deployment account; do not make the web server user the owner of the whole application tree unless the application packaging model requires it.

  7. Enable the Apache modules and packaged PHP-FPM snippet.
    $ sudo a2enmod proxy_fcgi setenvif
    Considering dependency proxy for proxy_fcgi:
    Enabling module proxy.
    Enabling module proxy_fcgi.
    Module setenvif already enabled
    To activate the new configuration, you need to run:
      service apache2 restart
    
    $ sudo a2enconf php8.5-fpm
    Enabling conf php8.5-fpm.
    To activate the new configuration, you need to run:
      service apache2 reload

    mod_proxy_fcgi passes PHP requests to PHP-FPM. The packaged php8.5-fpm.conf snippet contains the PHP handler and points Apache at the matching /run/php/php8.5-fpm.sock socket.

  8. Create the Apache virtual host file for the PHP application.
    $ sudo tee /etc/apache2/sites-available/app.example.test.conf >/dev/null <<'EOF'
    <VirtualHost *:80>
      ServerName app.example.test
      DocumentRoot /var/www/app.example.test/public
    
      <Directory /var/www/app.example.test/public>
        Options -Indexes +FollowSymLinks
        AllowOverride None
        Require all granted
      </Directory>
    
      DirectoryIndex index.php index.html
      ErrorLog ${APACHE_LOG_DIR}/app.example.test-error.log
      CustomLog ${APACHE_LOG_DIR}/app.example.test-access.log combined
    </VirtualHost>
    EOF

    Use AllowOverride All only when the application depends on .htaccess rules. Keeping rewrite and access rules in the virtual host is easier to review when the application supports it.

  9. Enable the virtual host.
    $ sudo a2ensite app.example.test.conf
    Enabling site app.example.test.
    To activate the new configuration, you need to run:
      service apache2 reload

    Disable 000-default.conf only when this PHP application should become the fallback site for unmatched port-80 requests.

  10. Test the Apache configuration before applying the site.
    $ sudo apache2ctl configtest
    AH00558: apache2: Could not reliably determine the server's fully qualified domain name, using 192.0.2.40. Set the 'ServerName' directive globally to suppress this message
    Syntax OK

    The AH00558 line is a global ServerName warning on fresh installs. Syntax OK confirms the enabled site and PHP-FPM snippet parsed successfully.

  11. Restart Apache after enabling new modules and the PHP-FPM handler.
    $ sudo systemctl restart apache2

    Use sudo systemctl reload apache2 for later virtual-host-only edits that do not add or remove modules. A restart is the safer first apply after enabling proxy_fcgi or changing the PHP handler.

  12. Confirm Apache loaded the new name-based virtual host.
    $ sudo apache2ctl -S
    VirtualHost configuration:
    *:80                   is a NameVirtualHost
             default server 192.0.2.40 (/etc/apache2/sites-enabled/000-default.conf:1)
             port 80 namevhost 192.0.2.40 (/etc/apache2/sites-enabled/000-default.conf:1)
             port 80 namevhost app.example.test (/etc/apache2/sites-enabled/app.example.test.conf:1)
    ServerRoot: "/etc/apache2"
    Main DocumentRoot: "/var/www/html"
    ##### snipped #####

    If app.example.test is missing from this output, the site file is not enabled, the filename does not end in .conf, or the configuration test is still failing.

  13. Request the application through Apache with a matching Host header.
    $ curl --silent --show-error --fail --header 'Host: app.example.test' http://127.0.0.1/
    Deployed PHP app OK
    sapi=fpm-fcgi

    sapi=fpm-fcgi confirms the request reached PHP through PHP-FPM. If the output shows PHP source code or downloads the file, the PHP handler is not active for this site.

  14. Test the public hostname after DNS or a temporary hosts-file entry points at the server.
    $ curl --silent --show-error --fail http://app.example.test/
    Deployed PHP app OK
    sapi=fpm-fcgi

    A successful localhost Host-header test proves Apache can route the vhost on the server. A successful hostname request proves the client-side name resolution path also reaches the same application.