Applications AWS

WBCE CMS 1.6.9 on AWS User Guide

| Product: WBCE CMS 1.6.9 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the WBCE CMS 1.6.9 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg BookStack, CMS, Blogs and Wikis listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

WBCE CMS (WebsiteBaker Community Edition) is a free and open source content management system written in PHP: a tree of pages built from content sections (WYSIWYG text with CKEditor, news with images, contact forms, menu links, wrappers), droplets (small snippets you place in a page), output filters, a media manager and an administration area at /admin.

The cloudimg image adds:

  • No reachable installer, no shared database. WBCE's browser installer creates the site and the first administrator for whoever opens it first. This image ships no database at all (the MariaDB data directory is empty), no configuration file and no installer in the web root. Your instance's first boot initialises its own database and runs WBCE's own installer before the web server starts: over a temporary server on a random local port that answers only requests carrying a token generated for that boot. First boot then stops that server and removes the installer.
  • Secrets of your instance's own, never on a command line. The database password, the administrator password, the secret WBCE signs its form tokens with and the TLS key are generated on your instance; nothing from the image build is reused. (Without its own secret, WBCE signs forms with a constant string that is the same in every copy.) The installer's form, which carries the passwords, is handed over in a file only root can read, never as a command line argument, and its replies are never logged. The administrator's password is stored with bcrypt.
  • HTTPS from the first boot. nginx serves WBCE on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
  • Fail closed. nginx does not start until first boot has finished; if first boot cannot complete, the site stays offline rather than open.
  • Code read only to PHP; page files read as data. The WBCE code belongs to root. PHP can write only media (your uploads), pages (the small page files WBCE writes for every page), temp and var. nginx never runs a PHP file from those folders: the page files are read by a root-owned page router that takes only the page number out of a file in exactly the form WBCE writes, and renders the page with WBCE's own index.php. media is static only: every file gets nosniff and a sandboxing content security policy, and only passive types (images, PDF, fonts, audio and video) open in the browser; anything else, SVG and HTML included, is sent as a download. temp, var, the framework and library code, language files and every text, SQL and template file are never served, and /x.php/<anything> is never a PHP request.
  • Forms and back-end links from other sites cannot act for you. Every request that changes something (every form submission, on any address of the site) and every link into the administration area and the modules only works when the request comes from your site itself (Step 9).
  • A rate-limited sign in. nginx allows 10 sign-in and password-reset attempts a minute from one address (after a burst of 10) on the administration and front-end forms, using the address of the connection, never a header a client sends. WBCE's own failed-login blocking and CAPTCHA stay on.
  • The real host and client address. nginx hands WBCE the host name you configure and nothing a request claims, and WB_URL in the configuration is pinned to it, so every link and e-mail uses your host name. nginx removes X-Forwarded-For, Client-IP and the other forwarding headers, so WBCE's sign-in blocking and its users list record the address of the connection.
  • A PHP that cannot reach the shell. PHP cannot run shell commands, fork, change its environment (so the putenv + mail() trick that loads a planted library does not work), load extensions or use FFI, in the web server and on the command line alike, and the web server is confined to the site folder.
  • No calls home. The dashboard's GitHub release check is off, and the online updater (which downloads releases and overwrites the code) is not shipped; update from the shell instead (Step 12).
  • Hardened defaults. MariaDB has no network listener at all (WBCE talks to it over a local socket) and its root account signs in only through the socket as the operating system's root. WBCE writes its files with mode 0640 and its folders with 0750. nginx, PHP, MariaDB and the first boot service are blocked from the instance metadata service, the AMI requires IMDSv2, and requests for a host name the instance does not serve are dropped.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: WBCE CMS 1.6.9 (the upstream release, checksum verified; without the online updater module), MariaDB 10.11, PHP 8.3 FPM, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 (SSH), TCP 443 (WBCE CMS) and TCP 80 (redirects to HTTPS) from the networks that should reach them. Keep 22 to your own networks. Open 443 to your visitors for a public site, or only to your team while you build it.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick WBCE CMS 1.6.9 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=wbce-cms}]'

Step 2: Connect to your instance

Version SSH user
WBCE CMS 1.6.9 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot takes under a minute (it creates the database and runs WBCE's installer). Then nginx, PHP, MariaDB and the first boot unit are active:

systemctl is-active nginx php8.3-fpm mariadb wbce-firstboot
active
active
active
active

nginx serves ports 80 and 443; PHP and MariaDB listen on local sockets only, and the installer's temporary server is gone, so the only open ports are 22, 80 and 443:

sudo ss -tlnp | awk 'NR==1 || /nginx|sshd|php|maria/' | awk '{print $1, $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g'
State Local Peer
LISTEN 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
LISTEN 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
LISTEN 0.0.0.0:22 users:(("sshd"),("systemd"))
LISTEN [::]:443 users:(("nginx"),("nginx"),("nginx"))
LISTEN [::]:80 users:(("nginx"),("nginx"),("nginx"))
LISTEN [::]:22 users:(("sshd"),("systemd"))

The WBCE release, the editor and the PHP and MariaDB versions, and how the download was verified:

grep -E "define\('NEW_WBCE_(VERSION|TAG)'" /var/www/wbce/admin/interface/version.php
grep -o 'version:"[0-9.]*"' /var/www/wbce/modules/ckeditor/ckeditor/ckeditor.js | head -1
php -v | head -1
mariadb --version
cat /usr/share/doc/wbce/RELEASE
define('NEW_WBCE_VERSION', '1.6.9'); // NEW_WBCE_VERSION
define('NEW_WBCE_TAG', '1.6.9'); // NEW_WBCE_TAG
version:"4.22.1"
PHP 8.3.6 (cli) (built: Oct 19 2026 15:16:58) (NTS)
mariadb  Ver 15.1 Distrib 10.11.14-MariaDB, for debian-linux-gnu (x86_64) using  EditLine wrapper
WBCE CMS 1.6.9
source https://codeload.github.com/WBCE/WBCE_CMS/tar.gz/refs/tags/1.6.9
commit 4bd2a809d7a955f65e2907a1256cc3294216b9ea
sha256 920640d940573a120b0af30e45b0badb8d7e537817fbd84d82a35086104b53d6
not shipped: modules/wbce_updater (online self-updater; the code tree is root-owned)
miniform defaults moved to templates/ at build (its installer step)

Step 4: Get your sign-in details

The credentials file holds the address WBCE is served as (your instance's public DNS name by default), the administration address, the administrator's user name, e-mail and password, and the database credentials, all generated on this instance. Show it with:

sudo cat /root/wbce-credentials.txt
WBCE_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
WBCE_ADMIN_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/admin/
WBCE_ADMIN_USER=admin
WBCE_ADMIN_PASSWORD=<24 letters and digits>
WBCE_ADMIN_EMAIL=admin@example.com
WBCE_DB_NAME=wbce
WBCE_DB_USER=wbce
WBCE_DB_PASSWORD=<48 hexadecimal characters>

The administrator starts with the e-mail admin@example.com; change it to your own address under Preferences before you rely on WBCE's password e-mails.

Step 5: Visit the site and sign in

Open WBCE_URL in your browser. The certificate is self signed (generated on your instance), so your browser warns once: compare the certificate's SHA-256 fingerprint with the one on the instance (sudo openssl x509 -in /etc/nginx/ssl/wbce.crt -noout -fingerprint -sha256) and continue, or install a certificate from your own certificate authority (Step 11). A fresh site shows one Home page in WBCE's wbcezon template (WBCE's installer creates no page; first boot adds this one so the site answers at once):

The WBCE CMS home page of a fresh site in the wbcezon template

Open WBCE_ADMIN_URL and sign in with WBCE_ADMIN_USER and WBCE_ADMIN_PASSWORD:

The WBCE CMS administration sign in page with the user name and password fields

The administration area opens on its dashboard, with the menu on the left:

The WBCE CMS administration dashboard with tiles for pages, media, add-ons, preferences, settings, admin tools and access

Move around the administration area with its menu and links. Back-end pages only open from your site itself (Step 9): a back-end address typed into the address bar, bookmarked or opened from an e-mail is refused, except the dashboard (/admin/) and the sign-in page, so start there.

The administrator can do everything, including things that run code on the server: install add-ons, write droplets (PHP snippets) and output filters. Keep this account to yourself, and for editors create accounts under Access > Users in a group with only the page and media permissions they need.

Step 6: Pages, sections and the editor

Pages live in the page tree (Pages). A page is made of sections, each of one type (WYSIWYG text, news with images, a mini form, a menu link, a wrapper and more); add pages and sections from here:

The WBCE CMS page tree with the Home page

A WYSIWYG section is edited with CKEditor 4.22.1:

The CKEditor 4.22.1 editor in a WBCE CMS page section with bold and italic text and a list

Save publishes the section. Every page has an address under /pages/ (the Home page is /pages/home.php):

A page published from the editor, showing its bold and italic text and list

Media opens the elFinder file manager for your uploads in /var/www/wbce/media, also used by the editor's image and link dialogs:

The elFinder media manager in the WBCE CMS administration area

WBCE writes a small file in /var/www/wbce/pages for every page; it only names the page's number. nginx never runs those files: the page router reads the number out of a file in exactly the form WBCE writes and shows the page with WBCE's own index.php. Anything else in that folder, a planted script included, is answered with 404. The optional "intro page" (a free-form pages/intro.php under Settings) is therefore not served on this image.

Step 7: The closed-by-default settings

Setting Image default Where to change it
Front-end sign up and sign in off (as WBCE ships) Settings (Show advanced options)
Dashboard release check (SHOW_UPDATE_INFO) off: no call to GitHub config.php (see Step 12)
Online updater not shipped update from the shell (Step 12)
File and folder modes WBCE writes 0640 / 0750 Settings (Show advanced options)
Form-signing secret generated on your instance Admin-Tools > SecureForm Switcher
Site address (WB_URL) https://<your host> sudo wbce-set-hostname (Step 11)
sudo php /usr/local/lib/cloudimg/wbce/write-config.php parse /var/www/wbce/config.php | grep -E '^(WB_URL|SHOW_UPDATE_INFO|DB_NAME|DB_USERNAME|TABLE_PREFIX)=' | sed -E 's#https://[^/]+#https://<your host>#'
sudo mysql -N -B --raw wbce -e "SELECT name, value FROM wbce_settings WHERE name IN ('frontend_signup','frontend_login','string_file_mode','string_dir_mode') ORDER BY name"
DB_NAME=wbce
DB_USERNAME=wbce
TABLE_PREFIX=wbce_
WB_URL=https://<your host>
SHOW_UPDATE_INFO=false
frontend_login  false
frontend_signup false
string_dir_mode 0750
string_file_mode    0640

Every PHP page also carries a content security policy that lets it connect only to your own server:

H=$(sed -n 's/^set \$wbce_host "\(.*\)";$/\1/p' /etc/nginx/wbce-host.conf)
curl -sk -D - -o /dev/null --resolve "$H:443:127.0.0.1" "https://$H/admin/login/index.php" | tr -d '\r' | grep -i '^content-security-policy'
content-security-policy: connect-src 'self'; frame-ancestors 'self'; object-src 'none'; base-uri 'self'

Step 8: Your data

Everything that makes your site lives in the wbce database in MariaDB and the uploads under /var/www/wbce/media. The page files in /var/www/wbce/pages are rewritten by WBCE whenever a page is saved, and temp and var hold caches and logs. Keep config.php with your backup: it holds this site's database password.

Back up the database to a file only root can read (MariaDB's root signs in over the local socket without a password, so no password appears on the command line):

sudo sh -c 'umask 077; mysqldump --single-transaction wbce > /root/wbce-$(date +%F).sql'
sudo tar -C /var/www/wbce -czf /root/wbce-files-$(date +%F).tgz config.php media pages

For a whole-instance copy, snapshot the EBS volume.

Step 9: Security checks

Port 80 sends browsers to HTTPS, the health endpoint answers for load balancer checks, and the installer, the configuration, the framework code, temp, var, planted PHP in the writable folders, a path after a PHP file name and the removed updater are not reachable:

H=$(sed -n 's/^set \$wbce_host "\(.*\)";$/\1/p' /etc/nginx/wbce-host.conf)
curl -s -o /dev/null -w 'port 80: %{http_code} -> %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/admin/" | sed -E 's#https://[^/]+#https://<your host>#'
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/healthz"
for p in /install/ /config.php /framework/initialize.php /temp/ /var/ /media/x.php /pages/x.php /index.php/admin /modules/wbce_updater/info.php; do
  curl -sk -o /dev/null -w "$p: %{http_code}\n" -H 'Sec-Fetch-Site: same-origin' --resolve "$H:443:127.0.0.1" "https://$H$p"
done
curl -s -o /dev/null -w 'unknown host name: %{http_code}\n' -H 'Host: example.com' http://127.0.0.1/ || true
port 80: 301 -> https://<your host>/admin/
ok
/install/: 404
/config.php: 404
/framework/initialize.php: 404
/temp/: 404
/var/: 404
/media/x.php: 404
/pages/x.php: 404
/index.php/admin: 404
/modules/wbce_updater/info.php: 404
unknown host name: 000

(000 means nginx closed the connection without an answer.) The credentials file is readable by root only, the configuration file by root and the web server, the code belongs to root, and only media, pages, temp and var belong to the web server:

sudo stat -c '%a %U:%G %n' /root/wbce-credentials.txt /var/www/wbce/config.php /var/www/wbce /var/www/wbce/media /var/www/wbce/pages /var/www/wbce/temp /var/www/wbce/var /var/www/wbce/index.php /var/www/wbce/pages/home.php
600 root:root /root/wbce-credentials.txt
640 root:www-data /var/www/wbce/config.php
755 root:root /var/www/wbce
750 www-data:www-data /var/www/wbce/media
750 www-data:www-data /var/www/wbce/pages
750 www-data:www-data /var/www/wbce/temp
750 www-data:www-data /var/www/wbce/var
644 root:root /var/www/wbce/index.php
640 www-data:www-data /var/www/wbce/pages/home.php

The Home page's page file, as WBCE writes it (the router takes only $page_id from it):

sudo cat /var/www/wbce/pages/home.php
<?php
// *** This file is generated by WBCE v1.6.9
// *** Creation date: <first boot time>
// *** Do not modify this file manually
// *** WBCE will rebuild this file from time to time!!
// *************************************************
    $page_id    = 1;
    require('../index.php');
// *************************************************

An uploaded HTML file is sent as a download with nosniff and the sandboxing policy, never rendered as a page of your site:

H=$(sed -n 's/^set \$wbce_host "\(.*\)";$/\1/p' /etc/nginx/wbce-host.conf)
echo '<b>demo</b>' | sudo -u www-data tee /var/www/wbce/media/guide-demo.html >/dev/null
curl -sk -D - -o /dev/null --resolve "$H:443:127.0.0.1" "https://$H/media/guide-demo.html" | tr -d '\r' | grep -iE '^(HTTP|content-disposition|content-security-policy|x-content-type)'
sudo -u www-data rm -f /var/www/wbce/media/guide-demo.html
HTTP/2 200
x-content-type-options: nosniff
content-security-policy: default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'; sandbox
content-disposition: attachment

The certificate was generated on this instance, names its own addresses and can only vouch for itself (it is not a certificate authority):

H=$(sed -n 's/^set \$wbce_host "\(.*\)";$/\1/p' /etc/nginx/wbce-host.conf)
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -ext basicConstraints,extendedKeyUsage
X509v3 Basic Constraints: critical
    CA:FALSE
X509v3 Extended Key Usage:
    TLS Web Server Authentication

nginx, PHP, MariaDB and the first boot service (and the installer's temporary server, which ran inside it) cannot reach the instance metadata service; PHP cannot run shell commands, fork, change its environment or use FFI, and is confined to the site folder; nginx hands PHP the configured host name, removes forwarding headers and any Authorization header a client sends, never passes a path after a PHP file name, and rate-limits requests:

for u in nginx php8.3-fpm mariadb wbce-firstboot; do systemctl show $u -p IPAddressDeny; done
php-fpm8.3 -i 2>/dev/null | grep -E '^(open_basedir|disable_functions|expose_php|ffi.enable) ' | cut -d' ' -f1-3
grep -E 'fastcgi_param (HTTP_HOST|SERVER_NAME|HTTP_X_FORWARDED_FOR|HTTP_CLIENT_IP|HTTP_X_FORWARDED_HOST|HTTP_AUTHORIZATION|PATH_INFO) ' /etc/nginx/snippets/cloudimg-wbce-fastcgi-file.conf
grep -h 'limit_req_zone\|limit_req_status' /etc/nginx/conf.d/cloudimg-wbce-guard.conf
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
disable_functions => exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec,pcntl_fork,putenv,dl,imap_open
expose_php => Off
open_basedir => /var/www/wbce/:/usr/local/lib/cloudimg/wbce-web/:/var/lib/php/sessions/:/tmp/
ffi.enable => Off
fastcgi_param PATH_INFO "";
fastcgi_param HTTP_HOST $wbce_host;
fastcgi_param SERVER_NAME $wbce_host;
fastcgi_param HTTP_X_FORWARDED_HOST "";
fastcgi_param HTTP_X_FORWARDED_FOR "";
fastcgi_param HTTP_CLIENT_IP "";
fastcgi_param HTTP_AUTHORIZATION "";
limit_req_zone $wbce_login_key zone=wbce_login:10m rate=10r/m;
limit_req_zone $wbce_post_key zone=wbce_post:10m rate=60r/m;
limit_req_zone $binary_remote_addr zone=wbce_php:10m rate=600r/m;
limit_req_status 429;

wbce_login counts sign-in, password-reset and sign-up attempts from one address (10 a minute after a burst of 10), wbce_post every request that sends data (60 a minute after a burst of 60) and wbce_php every page (600 a minute). Raise them in /etc/nginx/conf.d/cloudimg-wbce-guard.conf and /etc/nginx/snippets/cloudimg-wbce-fastcgi-*.conf if your site needs more, then sudo nginx -t && sudo systemctl reload nginx. The same PHP settings apply on the command line (/etc/php/8.3/cli/conf.d/99-cloudimg-wbce.ini).

sudo wbce-cred-roundtrip checks the whole sign in path on the instance: the redirect to HTTPS, that no installer is offered and the configuration is refused, a sign in through the real sign-in form with the password from the credentials file, and that a wrong and a common password are refused:

sudo wbce-cred-roundtrip
OK https 301 + no installer + config.php 404 + administrator sign-in + wrong and default passwords refused

Requests from another site. Every request that sends data (every form, on any address), every back-end page under /admin/<area>/ and every module address under /modules/ only works when the request comes from your site itself. nginx decides this from the browser's Sec-Fetch-Site, Origin and Referer headers (a request with none of them is refused too), so another site cannot make you act through a link or a hidden form. The site's pages, the search, the sign-in pages, the dashboard at /admin/ and the news feed stay linkable. Scripts that call the back end must send Sec-Fetch-Site: same-origin:

H=$(sed -n 's/^set \$wbce_host "\(.*\)";$/\1/p' /etc/nginx/wbce-host.conf)
curl -sk -o /dev/null -w 'page tree link from another site: %{http_code}\n' -H 'Sec-Fetch-Site: cross-site' --resolve "$H:443:127.0.0.1" "https://$H/admin/pages/index.php"
curl -sk -o /dev/null -w 'form post from another site: %{http_code}\n' -H 'Sec-Fetch-Site: cross-site' -H 'Origin: https://example.com' --data 'x=1' --resolve "$H:443:127.0.0.1" "https://$H/modules/wysiwyg/save.php"
curl -sk -o /dev/null -w 'sign-in page from another site: %{http_code}\n' -H 'Sec-Fetch-Site: cross-site' --resolve "$H:443:127.0.0.1" "https://$H/admin/login/index.php"
curl -sk -o /dev/null -w 'home page from another site: %{http_code}\n' -H 'Sec-Fetch-Site: cross-site' --resolve "$H:443:127.0.0.1" "https://$H/pages/home.php"
page tree link from another site: 403
form post from another site: 403
sign-in page from another site: 200
home page from another site: 200

The rules are in /etc/nginx/conf.d/cloudimg-wbce-guard.conf and /etc/nginx/conf.d/cloudimg-xsite-guard.conf (the latter pinned to your host names by wbce-set-hostname).

Step 10: Behind a proxy or load balancer

nginx removes X-Forwarded-For, X-Real-IP, Client-IP, X-Forwarded, Forwarded-For and Forwarded from every request, so WBCE's failed-login blocking and the users' last sign-in address use the address of the connection. Behind an AWS load balancer that is the load balancer's address, so all visitors share one failed-login counter; nginx's own access log (/var/log/nginx/access.log) records it the same way. To record visitors' addresses, use nginx's real_ip module with set_real_ip_from set to your load balancer's subnet only.

Step 11: Your own DNS name and certificate

Point a DNS record at the instance, then set the name WBCE is served as. This updates WB_URL in config.php (WBCE builds every absolute link from it), mints a new self signed certificate for the name, updates nginx (which hands the name to WBCE) and the cross-site rules, clears WBCE's caches, updates the addresses in /root/wbce-credentials.txt and keeps the name across reboots:

sudo wbce-set-hostname <your-domain>

To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running wbce-set-hostname again replaces it with a self signed one):

sudo install -m 0644 <your-domain>.crt /etc/nginx/ssl/wbce.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/ssl/wbce.key
sudo nginx -t && sudo systemctl reload nginx

Once browsers trust the certificate you can also add an HSTS header (add_header Strict-Transport-Security "max-age=31536000" always;) to /etc/nginx/snippets/cloudimg-wbce-headers.conf. The image does not send one: browsers ignore HSTS from a site whose certificate they do not trust.

Step 12: Passwords, settings, e-mail, add-ons and upgrades

wbce-set-password gives the administrator a new random password with WBCE's own password hashing (bcrypt), clears WBCE's failed-login blocking, signs out every session and records the password in /root/wbce-credentials.txt. Run it without arguments, or pass your own password (24 to 30 letters and digits; WBCE's sign-in form accepts up to 30 characters) on stdin. Users change their own password under Preferences.

sudo wbce-set-password
printf '%s\n' '<your-new-password>' | sudo wbce-set-password --stdin

The configuration file is read only to PHP. config.php is executed on every request, so the image does not let the web server rewrite it (a compromised PHP process could otherwise change the site permanently), and the cloudimg tools never execute it: they read it as data and refuse it unless it belongs to root, is mode 0640 and holds plain define(...) values only. WBCE keeps its settings in the database and saves them normally. To turn the dashboard's release check back on, change define('SHOW_UPDATE_INFO', false); to true in /var/www/wbce/config.php as root.

E-mail. WBCE sends password and form e-mails with PHP's mail function by default; the image has no mail server, so set an SMTP server under Settings (Show advanced options, Mailer) before you rely on e-mail.

Add-ons, templates and module files. The code folders belong to root, so the administration area cannot install add-ons or templates, or edit module and template files. Install an add-on from the shell: unpack it into /var/www/wbce/modules/<name> (or templates/<name>) as root, then register it under Add-ons (Advanced, reload the add-on lists). Install only add-ons you trust: an add-on is PHP that runs with your site's rights.

Upgrades. WBCE releases are published at https://github.com/WBCE/WBCE_CMS/releases. After a snapshot, copy the new release's wbce folder over /var/www/wbce as root (keep config.php, media, pages, temp and var, and leave out install and modules/wbce_updater), run the release's upgrade script if it has one (copy it in, open it once from the site, then delete it), then check that the code belongs to root again and that config.php is 0640 root:www-data.

Step 13: Server components

Component Path
WBCE code (root owned) /var/www/wbce
Configuration (this instance's database password) /var/www/wbce/config.php
Uploads /var/www/wbce/media
Page files (read by the page router) /var/www/wbce/pages
Caches and logs /var/www/wbce/temp, /var/www/wbce/var
Page router /usr/local/lib/cloudimg/wbce-web/page-router.php
Database (MariaDB datadir) /var/lib/mysql
nginx site, request limits and cross-site rules /etc/nginx/sites-available/cloudimg-wbce, /etc/nginx/conf.d/cloudimg-wbce-guard.conf, /etc/nginx/conf.d/cloudimg-xsite-guard.conf, /etc/nginx/snippets/cloudimg-wbce-*.conf
PHP settings /etc/php/8.3/fpm/conf.d/99-cloudimg-wbce.ini, /etc/php/8.3/cli/conf.d/99-cloudimg-wbce.ini
MariaDB settings /etc/mysql/mariadb.conf.d/99-cloudimg-wbce.cnf
Certificate and key /etc/nginx/ssl/
First boot script, installer copy and helpers /usr/local/sbin/wbce-firstboot.sh, /usr/local/lib/cloudimg/wbce/
Helpers /usr/local/sbin/wbce-set-hostname, wbce-set-password, wbce-cred-roundtrip
Licence, release and image notes /usr/share/doc/wbce/
Credentials /root/wbce-credentials.txt (mode 0600)

Step 14: Security recommendations

  • Allow port 22 in the security group from your own networks only.
  • Change the administrator e-mail to your own (Step 4), keep its password secret and rotate it if it may have been shared (Step 12). The administrator can run code on the server (add-ons, droplets, output filters): give editors their own accounts with narrower groups.
  • Keep config.php and your database backups secret (Step 8).
  • Use your own DNS name and a certificate your browsers trust (Step 11).
  • Keep WBCE on the newest release (Step 12) and install only add-ons you trust.
  • Keep the operating system patched; unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else. MariaDB, PHP-FPM and PHP's zip extension come from Ubuntu's universe repository; sudo pro security-status --esm-apps shows whether Ubuntu Pro (ESM Apps) has security updates for them (none were pending when the image was built).

Support and licensing

WBCE CMS is free software under the GNU General Public License, version 2 (a copy is at /usr/share/doc/wbce/LICENSE); its bundled modules, templates and libraries (CKEditor 4.22.1, elFinder, jQuery and others) carry their own open source licences. There is no per user or per CPU licence fee and no licence key. This image installs the upstream release without its online updater module, plus the cloudimg first boot, settings and helpers described above. The AMI price covers cloudimg's hardening, first boot setup and support.

  • Email: support@cloudimg.co.uk
  • Website: https://www.cloudimg.co.uk
  • Support: 24/7, with a 24 hour response SLA