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.
Related: How to create a virtual host in Apache
Related: How to configure PHP-FPM with Apache
Related: How to test Apache configuration
Related: How to manage the Apache web server service
| Item | Example value |
|---|---|
| Hostname | app.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.
$ 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.
$ 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.
$ 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.
$ 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.
$ 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.
$ 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.
Related: How to configure PHP-FPM with Apache
$ 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.
$ 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.
$ 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.
$ 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.
$ 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.
$ 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.
Related: Override Host header in cURL
$ 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.