Deployment guide

FrankenPHP

Overview

FrankenPHP is a modern PHP application server built on top of the Caddy web server. It embeds the PHP interpreter and Caddy into a single self-contained binary, so you run high-performance PHP without wiring together a separate web server and a PHP-FPM pool.

FrankenPHP adds worker mode for long-running application workers, automatic HTTPS, early hints, and real-time features such as server-sent events and Mercure. It remains fully compatible with your existing PHP applications.

This guide covers deploying and configuring FrankenPHP on Ubuntu 24.04 on Azure using SecureAnyCloud Azure Marketplace images. The image installs FrankenPHP 1.12.4 from the official prebuilt release binary. It runs under systemd as an unprivileged frankenphp user with just the CAP_NET_BIND_SERVICE capability, so it can bind the standard privileged web ports.

FrankenPHP serves your application over HTTPS on port 443 and permanently redirects plain HTTP on port 80 to HTTPS. A small demo PHP application ships in the document root, so a freshly launched instance immediately demonstrates end-to-end PHP execution.

Secure by default. FrankenPHP embeds Caddy, whose administration interface on port 2019 can rewrite the running configuration. This image disables that interface entirely (admin off), so it listens on no interface at all.

On the first boot of every VM, a one-shot service generates a fresh per-instance self-signed TLS certificate bound to that instance address, so the server answers over HTTPS immediately. FrankenPHP has no login, so no shared or default credential of any kind ships in the image.

What you get:

  • FrankenPHP 1.12.4 from the official prebuilt release binary, run under systemd as the unprivileged frankenphp user (frankenphp.service)
  • An HTTPS endpoint on port 443 and an HTTP to HTTPS permanent redirect on port 80
  • A demo PHP application at /var/www/html/index.php that reports the runtime PHP version, proving the application server executes PHP (replace it with your own application)
  • The embedded PHP interpreter and Caddy web server as a single static binary with no runtime library dependencies and no separate PHP-FPM socket to manage
  • The Caddy admin API disabled (admin off), so the config mutation endpoint listens on no interface
  • A per-instance self-signed TLS certificate generated on first boot, with a one-line path to automatic trusted HTTPS for your own domain
  • The whole configuration in one readable file, /etc/frankenphp/Caddyfile, that you can keep under version control

Prerequisites

  • An active Azure subscription, an SSH public key, and a VNet with a subnet in the target region
  • A subscription to the FrankenPHP listing on Azure Marketplace
  • Network Security Group rules that allow TCP 22 (admin) from your management network, and TCP 443 (HTTPS) plus TCP 80 (HTTP redirect) from the clients that reach your application

Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is ample for a demo or a small application. Choose a larger size for production PHP workloads and worker mode concurrency.

1

Deploy from the Azure Portal

  • Open the FrankenPHP offer on the Azure Marketplace and select Get It Now, then Create.
  • Choose your subscription, resource group and region.
  • Set the VM size (Standard_B2s or larger), your SSH public key and the admin username azureuser.
  • On the Networking tab, attach a Network Security Group that allows inbound TCP 22 from your management network and TCP 443 and TCP 80 from your users.
  • Review and create. When the deployment completes, note the VM public IP address.
2

Deploy from the Azure CLI

az vm create \
  --resource-group my-rg \
  --name my-frankenphp \
  --image <marketplace-image-urn> \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_rsa.pub \
  --public-ip-sku Standard
# open HTTPS (443), the HTTP redirect (80) and SSH (22)
az vm open-port --resource-group my-rg --name my-frankenphp --port 443 --priority 1001
az vm open-port --resource-group my-rg --name my-frankenphp --port 80 --priority 1002
3

First boot

On the first boot, the frankenphp-firstboot.service one-shot unit generates a fresh per-instance self-signed TLS certificate (bound to this VM's IP address). It writes an informational note to /root/frankenphp-info.txt and then starts frankenphp.service. This takes a few seconds.

SSH in as azureuser:

ssh azureuser@<vm-public-ip>
4

Confirm the service is running

Confirm that frankenphp.service is active, check the FrankenPHP, PHP and Caddy versions, and confirm that the server is listening on port 443 (HTTPS) and port 80 (the redirect):

sudo systemctl is-active frankenphp HOME=/var/lib/frankenphp /opt/frankenphp/frankenphp version | head -1 sudo ss -tlnp | grep -E ':443 |:80 '

The output shows active, a version line reporting FrankenPHP v1.12.4 PHP 8.5.8 Caddy v2.11.4, and the server listening on *:443 and *:80.

5

Verify PHP execution over HTTPS

PHP processes the demo application at /var/www/html/index.php. The served page reports the exact runtime PHP version and a computed value, so you can verify that the FrankenPHP application server is executing PHP rather than serving a static file. Fetch it over HTTPS. The -k flag accepts the per-instance self-signed certificate:

curl -ks https://127.0.0.1/ | grep -iE 'rendered by PHP|execution check'

The response reports that the page was rendered by PHP 8.5.8 and shows the PHP execution check (6 x 7) evaluating to 42 on the server.

Open https://<vm-public-ip>/ in a browser. Your browser warns about the self-signed certificate until you switch to a trusted certificate in Step 8. The demo landing page confirms the runtime PHP version and provides quickstart notes.

6

HTTP to HTTPS redirect and the disabled admin API

Plain HTTP on port 80 permanently redirects (301) to HTTPS. Confirm the redirect, then confirm that the Caddy admin API on port 2019 is not listening on any interface (secure by default):

curl -ksI http://127.0.0.1/ | grep -iE 'HTTP/|location'
sudo ss -tln | grep ':2019' || echo 'admin API disabled - nothing listening on :2019'

The output shows HTTP/1.1 301 Moved Permanently with a Location: header pointing at the HTTPS URL, and confirmation that nothing is listening on port 2019.

7

Deploy your own PHP application

The document root is /var/www/html. Copy your PHP application there (or point the Caddyfile root at another directory), then restart the service. Because the admin API is disabled, you apply config and code changes with a brief restart:

# copy your application into the document root
sudo rsync -a ./my-php-app/ /var/www/html/
# apply the change
sudo systemctl restart frankenphp

The php_server directive serves index.php as the front controller, so modern PHP frameworks such as Laravel and Symfony work out of the box once their public directory is the document root.

8

Enable trusted automatic HTTPS for your own domain

FrankenPHP inherits Caddy's automatic HTTPS. If you own a domain and point its DNS A record at this instance, you can switch from the per-instance self-signed certificate to a trusted, automatically renewed certificate with a single Caddyfile edit. Replace the :443 block in /etc/frankenphp/Caddyfile with your domain name:

your-domain.com { root * /var/www/html php_server }

Then restart the service. On the first request to your domain, FrankenPHP provisions a certificate automatically and renews it before expiry. There is no Certbot dependency:

sudo systemctl restart frankenphp
9

Review the configuration and info note

The whole server configuration is one readable file, and the first-boot note records this instance's endpoint details:

sudo cat /etc/frankenphp/Caddyfile
sudo cat /root/frankenphp-info.txt

The Caddyfile shows the admin off global option, the :80 redirect block and the :443 block serving php_server from /var/www/html.

The info note records the HTTPS and HTTP URLs for this instance and confirms that FrankenPHP has no login and no shared credential.

10

Security recommendations

  • Restrict the management port. Allow inbound TCP 22 only from your management network, never 0.0.0.0/0.
  • Serve your users over HTTPS only. Expose TCP 443 to your users, and expose TCP 80 only so that the redirect works. Switch to a trusted certificate for your own domain (Step 8) so that browsers do not warn.
  • Keep the admin API disabled. This image ships with the Caddy admin API disabled (admin off). Leave it disabled unless you have a specific need. If you enable it, bind it to loopback only, never to a network interface.
  • Keep the OS patched. The image is patched at build time and ships with unattended-upgrades enabled, so security updates continue automatically. Reboot after kernel updates.
  • Run your application with least privilege. frankenphp.service already runs as the unprivileged frankenphp user with a minimal capability set and systemd sandboxing.
11

Support and licensing

FrankenPHP is open source software distributed under the MIT License. This SecureAnyCloud image bundles FrankenPHP 1.12.4 with its embedded PHP 8.5 interpreter and the Caddy 2 web server on Ubuntu 24.04 LTS, patched at build time and hardened.

SecureAnyCloud provides the packaging, configuration, security hardening and support. It does not modify the upstream FrankenPHP licence.

Deploy on Azure

Launch FrankenPHP on Ubuntu 24.04 from the SecureAnyCloud Azure Marketplace listing and follow this guide to deploy your PHP application.

Need Help?

SecureAnyCloud provides 24/7 technical support for this FrankenPHP image. Contact sales@secureanycloud.com for help with worker mode tuning, Caddyfile configuration, HTTPS and certificate options, and deploying PHP frameworks.

All product and company names are trademarks or registered trademarks of their respective holders. Their use does not imply any affiliation with or endorsement by them.

Deploy on Azure

Launch FrankenPHP 1.12.4 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure