How-to
WordPress
Serve a WordPress site with PHP-FPM, pretty permalinks, the WordPress profile and HTTPS.
Prerequisites
- ScalWS installed, with
include: sites.d/*.yamlin/etc/scalws/scalws.yaml(see Sites). - WordPress unpacked in a directory such as
/srv/www/blog. The server units make/srvand/homeread-only for ScalWS and hide/root;/srvis the usual place. - The document root readable by group
scalws, for examplesudo 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):
| Unit | PHP |
|---|---|
scalws-controller (default) | An existing PHP-FPM pool, for example the distribution’s php8.3-fpm. Managed pools are refused (ADR-0036). |
scalwsd | Either 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 reloadThe 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: truephp.fastcgiis the existing pool.php.front_controllerhandles every path that is not a file: WordPress’s pretty permalinks.profile: wordpressadds 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.phpand the login and comment cookies. With an explicitphp:the profile does not replace the runtime; its rules still apply.htaccess: truehonours the rules plugins write into.htaccess(see .htaccess support).https_redirect: truesends 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 --htaccessPHP-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: trueTo 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: trueThe 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-contentThen 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: 1mThe 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.servicescalwsctl 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 listUnder 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 hasphp: true, orprofile: wordpresswithoutphp:, under scalws-controller. Give the pool withphp: {fastcgi: ...}as above, or switch toscalwsd.service.- 502 with
Permission denied: userscalwscannot connect to the PHP-FPM socket. Setlisten.group = scalwsandlisten.mode = 0660in the pool file, or addscalwsto the socket’s group, and restart PHP-FPM. - 502 with
No such file or directory: the socket path differs from the pool’slisten =, or PHP-FPM is not running. - Permalinks give 404:
front_controlleris missing (only when you wrotephp:yourself without it). - Uploads or plugin installs fail: the PHP user cannot write
wp-content/; see the write access notes above. - A rule from
.htaccesshas no effect:php_value,SetHandlerand[P]lines are ignored and logged; see .htaccess support.
See also: applications, quick start, troubleshooting.