Skip to content

Commit ce911e6

Browse files
retlehsclaude
andauthored
Document WordPress runtime hardening (#566)
Add documentation for the opt-in runtime hardening feature from roots/trellis#1649, covering PHP-FPM identity separation, writable path allowlisting, and per-site configuration. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent de6f061 commit ce911e6

2 files changed

Lines changed: 71 additions & 2 deletions

File tree

trellis/security.md

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
---
2-
date_modified: 2023-01-27 13:17
2+
date_modified: 2026-03-05 00:00
33
date_published: 2015-09-06 07:42
4-
description: Secure Trellis WordPress servers by disabling root SSH login, creating admin users with sudo access, and configuring secure password authentication.
4+
description: Secure Trellis WordPress servers by disabling root SSH login, creating admin users with sudo access, configuring secure password authentication, and hardening WordPress runtime file permissions.
55
title: WordPress Security Features in Trellis
66
authors:
77
- ben
@@ -24,3 +24,71 @@ The first provision via the `server.yml` playbook will create the `admin_user` a
2424
## Admin user sudoer password
2525

2626
If `root` login is disabled and the `server.yml` playbook connects as the `admin_user`, it will invoke `sudo` using the password in `vault_users` (`group_vars/<environment>/vault.yml`). If you run the playbook with `--ask-become-pass`, Trellis will use the password you enter via the CLI. You are strongly encouraged to protect the sensitive `vault_users` information by enabling Ansible [Vault](vault.md).
27+
28+
## WordPress runtime hardening
29+
30+
Trellis supports an opt-in hardening mode that separates the PHP-FPM runtime identity from the deploy user. When enabled, PHP runs as a dedicated user with write access limited to explicitly allowlisted paths. This reduces the impact of a compromised WordPress site by preventing PHP from modifying application code.
31+
32+
By default, hardening is disabled and Trellis behaves as it always has — PHP-FPM runs as the `web_user`.
33+
34+
### Enabling hardening
35+
36+
Add the following to `group_vars/all/main.yml` (or an environment-specific file like `group_vars/production/main.yml`):
37+
38+
```yaml
39+
wordpress_runtime_hardened: true
40+
```
41+
42+
### Configuration options
43+
44+
| Variable | Default | Description |
45+
| --- | --- | --- |
46+
| `wordpress_runtime_hardened` | `false` | Enable runtime hardening mode |
47+
| `wordpress_runtime_user` | `www-data` | OS user that PHP-FPM runs as when hardened |
48+
| `wordpress_runtime_group` | `www-data` | OS group that PHP-FPM runs as when hardened |
49+
| `wordpress_runtime_writable_paths` | `["shared/uploads"]` | Paths the runtime user can write to (relative to the site root) |
50+
| `wordpress_runtime_cron_as_runtime_user` | `false` | Run WP-CLI cron as the runtime user instead of `web_user` |
51+
52+
### Using a custom runtime user
53+
54+
For stronger isolation, use a dedicated user instead of `www-data`. The user and group must exist on the server before hardening is enabled — the playbook will fail fast if they don't.
55+
56+
Define the user in `group_vars/all/users.yml`:
57+
58+
```yaml
59+
users:
60+
- name: php-app
61+
groups:
62+
- php-app
63+
keys: []
64+
```
65+
66+
Then configure the runtime variables:
67+
68+
```yaml
69+
wordpress_runtime_hardened: true
70+
wordpress_runtime_user: php-app
71+
wordpress_runtime_group: php-app
72+
```
73+
74+
### Per-site writable paths
75+
76+
The global `wordpress_runtime_writable_paths` applies to all sites by default. You can override it for individual sites in your [WordPress Sites](/trellis/docs/wordpress-sites/) configuration:
77+
78+
```yaml
79+
wordpress_sites:
80+
example.com:
81+
runtime_writable_paths:
82+
- shared/uploads
83+
- current/web/app/cache
84+
```
85+
86+
### Cron user
87+
88+
By default, WP-CLI cron jobs continue to run as the `web_user` even when hardening is enabled. To run cron as the runtime user instead:
89+
90+
```yaml
91+
wordpress_runtime_cron_as_runtime_user: true
92+
```
93+
94+
This only takes effect when `wordpress_runtime_hardened` is also `true`.

trellis/wordpress-sites.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,5 @@ example.com:
145145
- `secure_auth_salt` - Generate (*required* in `vault.yml`)
146146
- `logged_in_salt` - Generate (*required* in `vault.yml`)
147147
- `nonce_salt` - Generate (*required* in `vault.yml`)
148+
- `runtime_writable_paths` - list of paths (relative to the site root) the PHP-FPM runtime user can write to when [runtime hardening](/trellis/docs/security/#wordpress-runtime-hardening) is enabled (default: global `wordpress_runtime_writable_paths`)
148149
- `deploy_keep_releases` - number of releases to keep for rollbacks (default: 5)

0 commit comments

Comments
 (0)