# WordPress

URL: https://scalws.com/docs/howto/wordpress/
Source: `docs/howto/wordpress.md` in the ScalWS repository

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](https://scalws.com/docs/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](https://scalws.com/docs/howto/lets-encrypt/)
  set up, or a certificate file on the TLS listener.

How PHP runs depends on the server unit (see
[the three services](https://scalws.com/docs/guide/installation/#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](https://scalws.com/docs/adr/0036-multi-instance-state/)). |
| `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`:

```sh
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`:

```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`](https://scalws.com/docs/reference/site-file/#php.fastcgi) is the existing pool.
* [`php.front_controller`](https://scalws.com/docs/reference/site-file/#php.front_controller) handles every
  path that is not a file: WordPress's pretty permalinks.
* [`profile: wordpress`](https://scalws.com/docs/reference/site-file/#profile) 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`](https://scalws.com/docs/reference/site-file/#htaccess) honours the rules plugins write
  into `.htaccess` (see [.htaccess support](https://scalws.com/docs/howto/htaccess/)).
* [`https_redirect: true`](https://scalws.com/docs/reference/site-file/#https_redirect) 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:

```sh
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`.

```yaml
# /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:

```yaml
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:

```ini
# /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`](https://scalws.com/docs/reference/site-file/#cache.default_ttl):

```yaml
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](https://scalws.com/docs/guide/caching/).

## Apply

```sh
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

```sh
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](https://scalws.com/docs/howto/htaccess/).

See also: [applications](https://scalws.com/docs/guide/applications/#wordpress),
[quick start](https://scalws.com/docs/guide/quick-start/#3-a-php-site-wordpress-style),
[troubleshooting](https://scalws.com/docs/guide/troubleshooting/#php-fpm).
