How-to

WordPress

Serve a WordPress site with PHP-FPM, pretty permalinks, the WordPress profile and HTTPS.

Prerequisites

  • ScalWS installed, with include: sites.d/*.yaml in /etc/scalws/scalws.yaml (see Sites).
  • WordPress unpacked in a directory such as /srv/www/blog. The server units make /srv and /home read-only for ScalWS and hide /root; /srv is the usual place.
  • The document root readable by group scalws, for example sudo chown -R www-data:scalws /srv/www/blog && sudo chmod -R g+rX /srv/www/blog.
  • A database for WordPress (not part of ScalWS).
  • For HTTPS: DNS of the domain pointing at the server and Let’s Encrypt set up, or a certificate file on the TLS listener.

How PHP runs depends on the server unit (see the three services):

UnitPHP
scalws-controller (default)An existing PHP-FPM pool, for example the distribution’s php8.3-fpm. Managed pools are refused (ADR-0036).
scalwsdEither an existing pool, or a pool that ScalWS starts and supervises itself.

Under scalws-controller: an existing PHP-FPM pool

Install PHP-FPM and let user scalws connect to its socket. Debian’s and Ubuntu’s pool creates /run/php/php8.3-fpm.sock for group www-data:

sudo apt install php8.3-fpm php8.3-mysql
sudo usermod -aG www-data scalws
sudo systemctl restart scalws-controller
sudo scalws-controller reload

The site file, /etc/scalws/sites.d/blog.example.com.yaml:

# /etc/scalws/sites.d/blog.example.com.yaml
domain: blog.example.com
root: /srv/www/blog
profile: wordpress
php:
  fastcgi: unix:/run/php/php8.3-fpm.sock
  front_controller: /index.php
htaccess: true
https_redirect: true
  • php.fastcgi is the existing pool.
  • php.front_controller handles every path that is not a file: WordPress’s pretty permalinks.
  • profile: wordpress adds deny rules (/wp-config.php, /wp-content/debug.log, /wp-content/uploads/*.php, /readme.html, /license.txt) and cache-bypass rules for /wp-admin, /wp-login.php, /wp-json, /xmlrpc.php, /wp-cron.php and the login and comment cookies. With an explicit php: the profile does not replace the runtime; its rules still apply.
  • htaccess: true honours the rules plugins write into .htaccess (see .htaccess support).
  • https_redirect: true sends plain-HTTP visitors to HTTPS. Leave it out until the certificate works.

Instead of writing the file by hand you can start with site add and add the other keys:

sudo scalwsctl site add blog.example.com --root /srv/www/blog \
    --php-fastcgi unix:/run/php/php8.3-fpm.sock --htaccess

PHP-FPM runs your PHP code as its own pool user (www-data by default). That user needs write access where WordPress writes, at least wp-content/.

Under scalwsd.service: a managed PHP-FPM pool

ScalWS starts PHP-FPM itself (php-fpm, else the newest versioned one it finds) and supervises it. Without php: the profile supplies the pool: 2 to 16 workers, front_controller: /index.php, memory_limit: 256M, uploads up to 64M and max_execution_time: 120.

# /etc/scalws/sites.d/blog.example.com.yaml
domain: blog.example.com
root: /srv/www/blog
profile: wordpress
htaccess: true
https_redirect: true

To size the pool yourself, write the runtime and keep the profile:

domain: blog.example.com
root: /srv/www/blog
profile: wordpress
php:
  front_controller: /index.php
  fpm:
    workers: {min: 4, max: 24}
    ini: {memory_limit: 512M, upload_max_filesize: 128M, post_max_size: 128M}
htaccess: true

The PHP processes run as scalws (or as the tenant’s user) inside the unit’s sandbox, where /srv is read-only. Allow writes to wp-content with a drop-in:

# /etc/systemd/system/scalwsd.service.d/writable.conf
[Service]
ReadWritePaths=/srv/www/blog/wp-content

Then sudo systemctl daemon-reload && sudo systemctl restart scalwsd.

Response cache (optional)

Add cache: to cache pages for anonymous visitors. WordPress sends no caching headers, so nothing is stored until you set a cache.default_ttl:

domain: blog.example.com
root: /srv/www/blog
profile: wordpress
php: {fastcgi: "unix:/run/php/php8.3-fpm.sock", front_controller: /index.php}
cache:
  default_ttl: 5m
  stale_while_revalidate: 1m

The profile’s bypass rules keep logged-in users and the admin area out of the cache. Pages that set cookies are never stored. Purge after publishing: scalwsctl purge --host blog.example.com --prefix /. See caching.

Apply

sudo scalwsctl check
sudo scalws-controller reload        # under scalws-controller
sudo scalwsctl reload                # under scalwsd.service

scalwsctl site add reloads by itself; after editing a file by hand, check and reload.

Check it works

curl -sI -H 'Host: blog.example.com' http://127.0.0.1/wp-login.php    # 200
curl -sI -H 'Host: blog.example.com' http://127.0.0.1/wp-config.php   # 404 (profile deny rule)
curl -sI -H 'Host: blog.example.com' http://127.0.0.1/hello-world/    # answered by index.php
sudo scalwsctl site list

Under scalwsd.service, scalwsctl apps shows the pool’s workers. With cache:, the X-Cache response header shows HIT, MISS or BYPASS.

Common problems

  • managed php runtimes are not supported with several instances (ADR-0036): the site has php: true, or profile: wordpress without php:, under scalws-controller. Give the pool with php: {fastcgi: ...} as above, or switch to scalwsd.service.
  • 502 with Permission denied: user scalws cannot connect to the PHP-FPM socket. Set listen.group = scalws and listen.mode = 0660 in the pool file, or add scalws to the socket’s group, and restart PHP-FPM.
  • 502 with No such file or directory: the socket path differs from the pool’s listen =, or PHP-FPM is not running.
  • Permalinks give 404: front_controller is missing (only when you wrote php: yourself without it).
  • Uploads or plugin installs fail: the PHP user cannot write wp-content/; see the write access notes above.
  • A rule from .htaccess has no effect: php_value, SetHandler and [P] lines are ignored and logged; see .htaccess support.

See also: applications, quick start, troubleshooting.