Skip to main content

Deploying PHP Apps

PHP is a popular programming language for web development. PHP is served in production server using PHP-FPM.

Popular PHP recipes is CodeIgniter and Laravel. Please read our Runner's Guide first if you haven't.

Recipes

Development Mode

source: clear
nginx:
fastcgi: on
commands:
- echo "<h1>Hello, World!</h1>" > index.php
- echo "<?php phpinfo(15);" > phpinfo.php
- echo "display_errors = On" > .user.ini
- echo "display_startup_errors = On" >> .user.ini

This creates a simple PHP that outputs "Hello, World!" and load some useful development features.


Let's extract those recipes meaning individually.

NginX Setup

FastCGI on

nginx:
fastcgi: on

This configuration enables FastCGI (PHP Processing) on Nginx. Without this, NginX will serve PHP files as static files.

If you have multiple directory setup, it's important to write fastcgi: on where the directory should also serve PHP files, for example:

nginx:
locations:
- match: /admin/
root: public_app/public
fastcgi: on
fastcgi: on

If you curious, the fastcgi: on internally represents as this in NginX configuration:

location ~ \.php$ {
fastcgi_pass unix:/path/to/socket/file.sock;
}

Rewrite root directory

root: public_html/public

The default root directory is public_html, where app files are extracted from recipes. Some modern frameworks like Laravel and CodeIgniter put static files inside public folder to avoid leaking bare *.php files be accessed maliciously and creates RCE attack.

So when your app requires this behavior, you need to change the root folder to public_html/public.

info

Not to be confused with root: inside nginx:, this setting is placed outside of nginx: because it will also tell Virtualmin to use this folder as e.g. SSL verification requests.

Reroute index.php

nginx:
locations:
- match: /
try_files: $uri $uri/ /index.php$is_args$args
fastcgi: on

If you use frameworks, they also likes to handle custom routes. The try_files: configuration here is to instruct Nginx to try to serve the root /index.php file in case no static files found in the given request URL.

Multiple website in a domain

root: public_html
nginx:
locations:
- match: ~ ^/(app|auth|api|web)/
root: public_app/public
fastcgi: on
try_files: $uri $uri/ /index.php$is_args$args
- match: /uploads/
alias: public_app/storage/app/public/
- match: /
try_files: $uri $uri/ /index.php$is_args$args
fastcgi: on

The example setup above is a typical setup for combining a WordPress (the landing page) in ~/public_html and a Laravel app in ~/public_app. Let's see what happens when we do requests from browser:

  1. /: Resolves to /public_html/index.php and loads the landing page at /.
  2. /about: Resolves to /public_html/index.php and loads the landing page at /about.
  3. /api/oauth: Resolves to /public_app/index.php and loads the Laravel app at /api/oauth.
  4. /web/login: Resolves to /public_app/index.php and loads the Laravel app at /web/login.
  5. /uploads/image.png: Resolves to /public_app/storage/app/public/image.png and loads the image (if exist).

Let's see another approach to combine multiple websites in a domain.

root: public_html
nginx:
locations:
- match: /app/
alias: public_app/public
fastcgi: on
try_files: $uri $uri/ @app
- match: "@app"
/app/(.*)$ /app/index.php last
- match: /uploads/
alias: public_app/storage/app/public/
- match: /
try_files: $uri $uri/ /index.php$is_args$args
fastcgi: on

The example above puts the whole laravel app inside /app/ subfolder. Let's see how it works:

  1. /: Resolves to /public_html/index.php and loads the landing page at /.
  2. /about: Resolves to /public_html/index.php and loads the landing page at /about.
  3. /app/api/oauth: Resolves to /public_app/index.php and loads the Laravel app at /api/oauth.
  4. /app/web/login: Resolves to /public_app/index.php and loads the Laravel app at /web/login.
  5. /web/login: Resolves to /public_html/index.php and loads the landing page at /web/login (404 error).
  6. /uploads/image.png: Resolves to /public_app/storage/app/public/image.png and loads the image (if exist).

You can read more about putting Laravel in subfolder using NginX in this StackOverflow answer.

PHP environment setup

The default PHP version is 7.4, which is the default provided from the OS.

To change PHP version used in PHP-FPM to the latest one, put this in runner:

features:
- php latest

You can also use a fixed PHP version: php 7.4, php 8.0, php 8.1.

Unfortunately you can't use custom PHP version other than provided because it's tied to system daemon. We always update the list to the latest supported version or latest major version starting from PHP 7.4.

Support for PHP extensions is varies but you can request a ticket to be included, provided the extension is provided officially by PHP.

When PHP version is changed, it's also changing the php and composer version it used.

Alternatively, you can call the alternative php version using php81, php80, php56, etc.

You can also do this for composer, e.g. php81 `which composer` install.

info

While you also can change PHP version using webmin, it will not change the PHP version used by CLI/SSH. Always change PHP version using the runner like above.

Composer install

Composer is installed automatically. The good recommendation to install packages is with:

composer install --no-dev --no-progress --optimize-autoloader
  • --no-dev to skip development packages (things like PHPUnit), to save space.
  • --no-progress to skip the progress bar, because it's problematic when used inside runner.
  • --optimize-autoloader to speed up the package resolution in production.

Clearing composer cache

If your development is stable enough, you can clear the cache to save space by calling:

composer clear-cache

The composer cache locates in ~/.composer/cache/.

PHP INI configuration

The PHP INI configuration is useful to tweak the PHP behavior such at upload size limits. While you can't directly change the PHP INI located in system files, you can create .user.ini into PHP root folder (typically ~/public_html/.user.ini) and tweak the config there.

An example of PHP INI configuration is:

~/public_html/.user.ini
upload_max_filesize = 32M
post_max_size = 32M

See the list of available PHP INI configuration in official PHP documentation. To see default values or if your change has been in effect, use phpinfo().

caution

PHP INI doesn't work? You may need to wait until the PHP process is restarted, which typically about 5 minutes without traffic or after about 500 requests. Note that you can't change configs with PHP_INI_SYSTEM level because it's only changeable in the main system INI files.

PHP Error Logging

The error logs can be seen in Nginx Error Logs in Virtualmin web UI. You won't see error details in your website because we use production default settings.

If you want to see the error details in website (not recommended!), change this setting in .user.ini:

~/public_html/.user.ini
display_errors = On
display_startup_errors = On

Restarting PHP

PHP doesn't need restart. Changing PHP files instantly changes the running server code.

The PHP-FPM instance itself is running as ondemand and goes inactive after 5 minutes of no traffic.