iTranslated by AI

The content below is an AI-generated translation. This is an experimental feature, and may contain errors. View original article
🧟

Installing FrankenPHP on Debian 12 Bookworm

に公開

Screenshot of the FrankenPHP official website

In this article, it is assumed that the installation and configuration of Debian have been completed. The final target configuration is as follows.

  • OS: Debian 12 bookworm
  • web server + PHP: FrankenPHP (Caddy + PHP8.3) - This article
  • Database: MariaDB

Firewall Configuration

Open ports 80 and 443.

$ sudo ufw allow 'WWW Full' && sudo ufw reload && sudo ufw status
Rule added
Rule added (v6)
Status: active

To                         Action      From
--                         ------      ----
SSH                        LIMIT       Anywhere
WWW Full                   ALLOW       Anywhere
SSH (v6)                   LIMIT       Anywhere (v6)
WWW Full (v6)              ALLOW       Anywhere (v6)

If the status is active and "WWW Full" is in the list, you are done.

Deleting Rules Once Added

To delete a rule that was added, execute the following command.

$ sudo ufw delete allow 'WWW Full' && sudo ufw reload

To delete them individually:

$ sudo ufw status numbered
Status: active

     To                         Action      From
     --                         ------      ----
[ 1] WWW Full                        LIMIT IN    Anywhere
[ 2] WWW Full (v6)                   LIMIT IN    Anywhere (v6)

Specify the number displayed above.

$ sudo ufw delete 2 && sudo ufw reload

Installing haveged

Take measures to prevent the entropy pool from being depleted[1].

$ sudo apt update
$ sudo apt install haveged
$ sudo systemctl enable --now haveged

Installing FrankenPHP

Generally, when people think of web hosting environments, Nginx + PHP-FPM is the most common setup. However, because the hurdles for environment construction and maintenance are high, I have high expectations for FrankenPHP.

Add the installation path to your PATH

Add /usr/local/sbin to your PATH.

$ vi ~/.bashrc

Add the following:

+ export PATH=/usr/local/sbin:$PATH

Apply the changes.

$ source ~/.bashrc

Download and place the binary from GitHub

$ wget https://github.com/dunglas/frankenphp/releases/download/v1.5.0/frankenphp-linux-x86_64 && \
sudo mv ~/frankenphp-linux-x86_64 /usr/local/sbin/caddy && \
sudo chmod +x /usr/local/sbin/caddy && \
/usr/local/sbin/caddy -v

Configuring FrankenPHP

The following steps are typical tasks, similar to those for Nginx or Apache.

Changing User Settings

Create a user for FrankenPHP and add them to the www-data group.

  • In the example below, the working user debian is also added to the www-data group.
$ sudo useradd -mU -s /usr/sbin/nologin -d /opt/caddy caddy && \
sudo usermod -aG www-data caddy && \
sudo usermod -aG www-data debian

Creating the Document Root

Change the permissions of the web site's document root so that members of the www-data group can add, update, and delete files.

$ sudo mkdir /var/www && \
sudo chown -R caddy:www-data /var/www && \
sudo chmod -R 2775 /var/www && \
sudo find /var/www -type d -exec sudo chmod 2775 {} \; && \
sudo find /var/www -type f -exec sudo chmod 2664 {} \; && \
exit

You should have been logged out of the server, so please log in again.

Configuring the Caddyfile

FrankenPHP's web server uses Caddy, which is written in Go. The configuration file syntax is the same as Caddy's.

$ sudo vi /etc/Caddyfile

(encode) {
    encode zstd br gzip
    php_server
}

(header-cache-control) {
    @static {
        file
        path *.css *.js *.jpeg *.jpg *.png *.gif *.webp *.avif *.svg *.eot *.ttf *.woff *.woff2
    }

    header @static Cache-Control "public, max-age=691200"
}

(header-security) {
    header Referrer-Policy no-referrer-when-downgrade
    header X-Content-Type-Options nosniff
    header X-Download-Options noopen
    header X-Frame-Options SAMEORIGIN
}

(header-cors) {
    @origin header Origin {args[0]}
    header @origin Access-Control-Allow-Origin "{args[0]}"
    header @origin Access-Control-Allow-Methods "GET,POST,PUT,DELETE,OPTIONS"
}

(header-https) {
    header Content-Security-Policy "default-src https: data: 'unsafe-eval' 'unsafe-inline'"
    header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
    header X-SSL-Status https
}

(createlog) {
    log {
        hostnames {args[0]}
        output file /opt/caddy/log/access-{args[0]}.log {
            roll_size 1gb
            roll_keep 5
            roll_keep_for 7d
        }
    }
    log {
        hostnames {args[0]}
        output file /opt/caddy/log/error-{args[0]}.log {
            roll_size 1gb
            roll_keep 5
            roll_keep_for 7d
        }
        level ERROR
    }
}

{
    frankenphp {
        num_threads 16
    }
    order php_server before file_server
    http_port  80
    https_port 443
    auto_https disable_redirects
}

http:// {
    root * /var/www/exmaple.com/public
    import header-cache-control
    import header-security
    import header-cors 192.168.10.20
    import encode
}

exmaple.com:80,
exmaple.com:443 {
    redir https://www.{host}{uri} permanent
    import header-cache-control
    import header-security
    import header-cors {host}
    import encode
}

www.exmaple.com:80,
www.exmaple.com:443 {
    root * /var/www/{host}/public
    import header-cache-control
    import header-security
    import header-cors {host}
    import header-https
    import encode
    import createlog {host}
}

Registering FrankenPHP as a Service

$ sudo vi /etc/systemd/system/caddy.service

Paste the following:

# caddy.service
#
# For using Caddy with a config file.
#
# Make sure the ExecStart and ExecReload commands are correct
# for your installation.
#
# See https://caddyserver.com/docs/install for instructions.
#
# WARNING: This service does not use the --resume flag, so if you
# use the API to make changes, they will be overwritten by the
# Caddyfile next time the service is restarted. If you intend to
# use Caddy's API to configure it, add the --resume flag to the
# `caddy run` command or use the caddy-api.service file instead.

[Unit]
Description=Caddy
Documentation=https://caddyserver.com/docs/
After=network.target network-online.target
Requires=network-online.target

[Service]
Type=notify
User=caddy
Group=www-data
ExecStart=/usr/local/sbin/caddy run --environ --config /etc/Caddyfile
ExecReload=/bin/kill -USR1 $MAINPID
ExecStop=/usr/local/sbin/caddy stop
Restart=on-failure
RestartSec=5s
TimeoutStopSec=5s
LimitNOFILE=65536
LimitNPROC=512
PrivateTmp=true
PrivateDevices=true
ProtectSystem=full
ReadWriteDirectories=/opt/caddy
Environment=CADDYPATH=/opt/caddy
AmbientCapabilities=CAP_NET_ADMIN CAP_NET_BIND_SERVICE

[Install]
WantedBy=multi-user.target

Register FrankenPHP as a service.

$ sudo systemctl enable caddy && \
sudo systemctl start caddy && \
sudo systemctl status caddy

The website should now be live.

Creating PHP.ini

Unless you built the FrankenPHP binary yourself, the location for php.ini is under /lib. Since FrankenPHP does not provide a php.ini file, you need to prepare it yourself.

$ sudo vi /lib/php.ini

Change the /run/mysqld/mysqld.sock part according to your environment.
For MariaDB, you can check it in /etc/mysql/mariadb.cnf.

[PHP]
engine = On
short_open_tag = Off
precision = 14
output_buffering = 4096
implicit_flush = Off
serialize_precision = -1
zend.enable_gc = On
expose_php = Off
max_execution_time = 30
max_input_time = 60
memory_limit = 128M
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT
error_log = /var/log/php_errors.log
display_errors = Off
display_startup_errors = Off
log_errors = On
log_errors_max_len = 1024
ignore_repeated_errors = On
ignore_repeated_source = Off
report_memleaks = On
track_errors = Off
html_errors = Off
request_order = "GP"
auto_globals_jit = On
post_max_size = 4M
upload_max_filesize = 4M
max_file_uploads = 20
default_mimetype = "text/html"
file_uploads = On
allow_url_fopen = Off
default_socket_timeout = 60
open_basedir = "/var/www/:/tmp/"

[Date]
date.timezone = "Asia/Tokyo"

[mbstring]
mbstring.language = Japanese
mbstring.detect_order = ASCII,ISO-2022-JP,UTF-8,eucJP-win,SJIS-win
mbstring.encoding_translation = Off
mbstring.substitute_character = none
mbstring.func_overload = 0

[mail function]
SMTP = localhost
smtp_port = 25
sendmail_path = /usr/sbin/sendmail -t -i
mail.add_x_header = On

[Session]
session.save_handler = files
session.save_path = "/tmp/php/session"
session.use_cookies = 1
session.use_only_cookies = 1
session.name = PHPSESSID
session.cookie_lifetime = 0
session.cookie_path = /
session.cookie_domain =
session.cookie_httponly =
session.serialize_handler = php
session.gc_probability = 1
session.gc_divisor = 1000
session.gc_maxlifetime = 1440
session.cache_limiter = nocache
session.cache_expire = 180
session.use_trans_sid = 0
session.hash_function = 0
session.hash_bits_per_character = 5

[Pdo_mysql]
pdo_mysql.default_socket = /run/mysqld/mysqld.sock

[MySQLi]
mysqli.default_socket = /run/mysqld/mysqld.sock

[OPcache]
opcache.enable=1
opcache.enable_cli=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8M
opcache.max_accelerated_files=4000
opcache.revalidate_freq=0
opcache.fast_shutdown=1

[apcu]
apc.enabled=1
apc.enable_cli=1
apc.shm_size=64M
apc.ttl=3600
apc.gc_ttl=3600
apc.user_ttl=7200
apc.cache_by_default=1
apc.filters=.*\.php
apc.stat=1
apc.serializer=php
apc.use_request_time=1
apc.include_once_override=0

[Memcached]
;extension=memcached.so

脚注
  1. This can occur when there is a lack of randomness (entropy) while establishing an SSL/TLS connection, preventing cURL or OpenSSL from establishing secure communication (cURL error 35: Insufficient randomness).
    In SSL communication, secure encryption is not possible if "truly random values" cannot be generated.
    General-purpose PCs accumulate "entropy" (randomness) from keyboard/mouse activity or disk I/O. However, on servers, these sources are extremely limited, which can lead to /dev/random failing to provide enough random numbers and causing processes to hang.
    If Intel/AMD RDRAND instructions are available, rng-tools is also effective. ↩︎

Discussion