FrankenPHP + Caddy + Ubuntu 24.04 + CakePHP Worker

by May 24, 2025IT Tips0 comments

This is getting FrankenPHP running with Caddy on Ubuntu 24.04 LTS without Docker

Note: CakePHP doesn't currently come with a drop-in frankenphp worker script. The script sample works if you set $maxRequests to 1 and let frankenphp restart it for every request. Development needs to be done to create a proper worker script.

Note: Frankenphp works fine in php_server mode with the default webroot/index.php

Install some pre-reqs

sudo apt install zip unzip curl -y

Install PHP8.4 and Caddy

sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
sudo apt install caddy php8.4 php8.4-cli php8.4-{bz2,curl,mbstring,intl,xml} -y
php -v

Install frankenphp

curl https://frankenphp.dev/install.sh | sh
sudo mv frankenphp /usr/local/bin/

Create a php.ini

# create the /etc/frankenphp dir if you need to
cp /etc/php/8.4/fpm/php.ini /etc/frankenphp/

# uncomment and edit the error_log value
error_log=/var/log/php_errors.log

sudo touch /var/log/php_errors.log
sudo chown www-data:www-data /var/log/php_errors.log

Create a config file for Caddy

/etc/caddy/Caddyfile

# The Caddyfile is an easy way to configure your Caddy web server.
#
# Unless the file starts with a global options block, the first
# uncommented line is always the address of your site.
#
# To use your own domain name (with automatic HTTPS), first make
# sure your domain's A/AAAA DNS records are properly pointed to
# this machine's public IP, then replace ":80" below with your
# domain name.
#
{
        metrics
        frankenphp {
                worker {
                        env DEBUG false
                        file /var/www/html/webroot/index.php
                        watch /var/www/html/webroot
                        watch /var/www/html/vendor
                        num 6
                }
        }
}

tgnyt.australiaeast.cloudapp.azure.com,
fphp.toggen.com.au {
        # Set this path to your site's directory.
        root * /var/www/html/webroot

        encode zstd br gzip
        file_server

        # Or serve a PHP site through php-fpm:
        # php_fastcgi localhost:9000
        php_server {
                env PATH /usr/local/bin:/usr/bin
                env DEBUG true
        }

        log {
                format console
                output file /var/log/caddy.log
        }
}

# Refer to the Caddy docs for more information:
# https://caddyserver.com/docs/caddyfile

To format the Caddyfile use

sudo caddy fmt --overwrite

Create a systemd .service file

/etc/systemd/system/frankenphp.service

Don't think CAP_NET_ADMIN is needed to remove this.

[Unit]
Description=FrankenPHP Server
After=network.target network-online.target
Requires=network-online.target

[Service]
Restart=on-failure
Type=notify
User=www-data
Group=www-data
ExecStartPre=/usr/local/bin/frankenphp validate --config /etc/caddy/Caddyfile
ExecStart=/usr/local/bin/frankenphp run --config /etc/caddy/Caddyfile --adapter caddyfile
ExecReload=/usr/local/bin/frankenphp reload --config /etc/caddy/Caddyfile --force
TimeoutStopSec=5s
LimitNOFILE=1048576
PrivateTmp=true
ProtectSystem=full
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

Add writeable dirs for the user running frankenphp

sudo mkdir /var/www/{.config,.local}
sudo chown www-data:www-data /var/www/{.config,.local} 

The contents of the above directories once frankenphp starts

# autosave
/var/www/.config/caddy/autosave.json

# certs letsencrypt config
/var/www/.local/share/caddy/certificates
/var/www/.local/share/caddy/acme 
/var/www/.local/share/caddy/acme/acme-v02.api.letsencrypt.org-directory

Enable and start the service

systemctl enable frankenphp.service
systemctl start frankenphp.service

Install CakePHP

cd /var/www/html
composer create-project --prefer-dist cakephp/app:~5.0 .
# optional upgrade
composer require cakephp/cakephp:~5.2

Deploy the worker script

<?php

use App\Application;
use App\Http\Server;
use Cake\Core\PluginApplicationInterface;
$workerServer = $_SERVER;

ignore_user_abort(true);

require  dirname(__DIR__) . '/vendor/autoload.php';
$myApp = new Application( dirname(__DIR__)  . '/config');
$myApp->bootstrap();

$server = new Server($myApp);

error_log('Worker started');

$handler = static function () use ($server, $myApp)  {
        error_log('Worker handler');
        # DebugKit won't show up and will throw an error without this 
        if ($myApp instanceof PluginApplicationInterface) {
                $myApp->pluginBootstrap();
        }
        $server->emit($server->run());
};

$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 1);

for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
    error_log('Worker handling request');
    $keepRunning = \frankenphp_handle_request($handler);

    gc_collect_cycles();

    if (!$keepRunning) break;
}

Modify Cake\Http\Server.php

mkdir src/Http
cp vendor/cakephp/cakephp/src/Http/Server.php src/Http/

Change the following

# change namespace
# namespace Cake\Http;
# to
namespace App\Http;


# add references to the Cake\Http classes
use Cake\Http\Runner;
use Cake\Http\MiddlewareQueue;
use Cake\Http\ResponseEmitter;
use Cake\Http\ServerRequestFactory;

# comment out the call to bootstrap the app
 public function run(
        ?ServerRequestInterface $request = null,
        ?MiddlewareQueue $middlewareQueue = null,
    ): ResponseInterface {
#        $this->bootstrap();

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

The reCAPTCHA verification period has expired. Please reload the page.