Applications AWS

WonderCMS 3.6.0 on AWS User Guide

| Product: WonderCMS 3.6.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the WonderCMS 3.6.0 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.

WonderCMS is a tiny open source content management system for simple websites. There is no database and no separate admin area: you sign in with one password, click any text on a page to edit it and click outside it to save. Pages, menus, settings and the password hash live in a single JSON file, so the whole site is one folder you can back up and move with ordinary file tools. Themes and plugins (a WYSIWYG editor, a blog, a contact form, statistics and more) install from the settings panel.

The cloudimg image adds:

  • No first-visit setup page, no default login. Out of the box WonderCMS creates its data file on the first visit to the site, keeps the login page at the well known address /loginURL and prints the generated password on the public home page. On this image your instance's first boot writes the data file before the web server starts, with an admin password and a private login address (/login- followed by 16 random hex characters) generated on your instance and recorded in /root/wondercms-credentials.txt. The home page prints no password and /loginURL is an ordinary "page not found". Nothing from the image build ships in the image.
  • HTTPS from the first boot. nginx serves WonderCMS on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
  • Fail closed. nginx and PHP refuse to start until first boot has finished.
  • The data file is never served. WonderCMS relies on Apache .htaccess rules that nginx ignores; this image replaces them. The data folder (the JSON file with the password hash, caches, theme and plugin sources) is never served, except uploaded files under /data/files/, which are served as static files with a policy that stops an uploaded HTML or SVG file from running script. PHP runs only through index.php; theme, plugin and uploaded PHP files are never run directly.
  • Failed-login lockout. WonderCMS itself does not limit login attempts. On this image ten failed sign ins from one address within 15 minutes block that address for five minutes. The address comes from the connection, never from a header a client sends.
  • Hardened defaults. The WonderCMS core (index.php) is owned by root and read only to PHP; PHP can write only the data folder (themes and plugins install there), cannot run shell commands and is confined to WonderCMS's folders. nginx and PHP are blocked from the instance metadata service, the AMI requires IMDSv2, requests for a host name the instance does not serve are dropped, and WonderCMS's links always use the host name you configure, never the one a request claims.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: WonderCMS 3.6.0 (the official release, checksum verified) with its default Sky theme, PHP 8.3 FPM, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. First boot creates WonderCMS's standard starter site: a Home page and a How to page.

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 (the site and its editor) and TCP 80 (redirects to HTTPS) from the networks that should reach them. A public website needs 443 and 80 open to everyone; keep 22 to your own networks.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick WonderCMS 3.6.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (WonderCMS is very light; a smaller instance is fine for a small site), 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=wondercms}]'

Step 2: Connect to your instance

Version SSH user
WonderCMS 3.6.0 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot takes a few seconds. Then nginx, PHP and the first boot unit are active:

systemctl is-active nginx php8.3-fpm wondercms-firstboot
active
active
active

nginx serves ports 80 and 443; PHP listens on a local socket only, so the only open ports are 22, 80 and 443:

sudo ss -tlnp | awk 'NR==1 || /nginx|sshd|php/' | awk '{print $1, $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g'
State Local Peer
LISTEN 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
LISTEN 0.0.0.0:22 users:(("sshd"),("systemd"))
LISTEN 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
LISTEN [::]:80 users:(("nginx"),("nginx"),("nginx"))
LISTEN [::]:22 users:(("sshd"),("systemd"))
LISTEN [::]:443 users:(("nginx"),("nginx"),("nginx"))
grep -oE "define\('VERSION', '[0-9.]+'\)" /var/www/wondercms/index.php
php -v | head -1
define('VERSION', '3.6.0')
PHP 8.3.6 (cli) (built: Oct 19 2026 15:16:58) (NTS)

Step 4: Get your login address and password

The credentials file holds the address the site is served as (your instance's public DNS name by default), the private login address and the admin password generated on this instance. Show it with:

sudo cat /root/wondercms-credentials.txt
WONDERCMS_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
WONDERCMS_LOGIN_SLUG=login-<16 hex characters>
WONDERCMS_LOGIN_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/login-<16 hex characters>
WONDERCMS_ADMIN_PASSWORD=<24 letters and digits>

The address lines alone (no password) come from:

sudo grep -E '^WONDERCMS_(URL|LOGIN_URL)=' /root/wondercms-credentials.txt
WONDERCMS_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
WONDERCMS_LOGIN_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/login-<16 hex characters>

Treat the login address as private: it is not linked from the site, and anyone who knows it still needs the password.

Step 5: Visit your site and sign in

Open WONDERCMS_URL in your browser. The certificate is self signed (generated on your instance), so accept the browser warning once, or install your own certificate (Step 10). Visitors see the starter site in the Sky theme, with no login link and no password:

The WonderCMS starter site in the Sky theme with the Home and How to menu and the Welcome to your website page

Open WONDERCMS_LOGIN_URL and sign in with WONDERCMS_ADMIN_PASSWORD (WonderCMS has one admin and no user name):

The WonderCMS login form with a single Password field and the Login button

Step 6: Edit your pages

Signed in, every editable area of the page has a dashed outline. Click any text to edit it in place and click outside it to save; changes are live immediately. Settings opens the settings panel and the red button signs you out.

Signed in to WonderCMS: the Settings and logout buttons and the dashed, click to edit outline around the page content

To add a page, open Settings > Menu and add a menu item, or browse to a new address such as /about while signed in and start typing: WonderCMS creates the page when you save.

Step 7: Settings, themes and plugins

The settings panel has tabs for the Current page (title, keywords and description for search engines), the Menu, Files (upload images and documents, served from /data/files/), Themes, Plugins and Security (change the login address and the password).

The WonderCMS settings panel on the Current page tab with the page title, keywords and description fields

Themes and Plugins list the modules from WonderCMS's online catalogue. Install downloads the module from its GitHub repository into the data folder; the Summernote editor plugin, for example, adds a WYSIWYG toolbar to every editable area.

The WonderCMS Themes tab with the active Sky theme and the Essence and GregCustom themes ready to install

The WonderCMS Plugins tab with the Summernote Editor, Simple Blog and Simple Statistics plugins and their Install buttons

A theme or plugin is PHP code that runs inside your site, so install only modules you trust; only the signed in admin can install them. The instance needs outbound HTTPS to GitHub for the catalogue and for installs. PHP cannot reach the instance metadata service, so a module cannot read the instance's IAM role credentials.

Step 8: Your site data

Everything WonderCMS stores lives in one folder: the JSON data file (pages, menu, settings and the password hash), uploaded files, and the installed themes and plugins.

sudo ls -l /var/www/wondercms /var/www/wondercms/data | sed -E 's/ +/ /g' | cut -d' ' -f1,3,4,9-
/var/www/wondercms:
total
drwxr-x--- www-data www-data data
-rw-r--r-- root root index.php
lrwxrwxrwx root root plugins -> data/plugins
lrwxrwxrwx root root themes -> data/themes

/var/www/wondercms/data:
total
-rw-r----- www-data www-data database.js
drwxr-xr-x www-data www-data files
drwxr-xr-x www-data www-data plugins
-rw-r----- www-data www-data security.json
drwxr-xr-x www-data www-data themes

After your first sign in WonderCMS also keeps cache.json there (the module catalogue), and the login lockout keeps its counts in cloudimg-login-guard.json. Back it up by copying the folder (sudo tar -C /var/www/wondercms -czf wondercms-data.tar.gz data) or with an EBS snapshot of the instance's volume. WonderCMS's own Settings > Security > Backup website button writes a zip of the whole site into /data/files/ and downloads it; delete the zip from Files afterwards, because anything in /data/files/ can be downloaded by anyone who knows its name.

Step 9: Security checks

Port 80 sends browsers to HTTPS, the health endpoint answers for load balancer checks, and the data file, the default login address and theme PHP files are not reachable:

H=$(sudo grep '^WONDERCMS_URL=' /root/wondercms-credentials.txt | sed -E 's#^WONDERCMS_URL=https://([^/]+)/.*#\1#')
curl -s -o /dev/null -w 'port 80: %{http_code} -> %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/how-to"
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/healthz"
for p in /data/database.js /data/security.json /loginURL /themes/sky/theme.php; do
  curl -sk -o /dev/null -w "$p: %{http_code}\n" --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://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/how-to
ok
/data/database.js: 404
/data/security.json: 404
/loginURL: 404
/themes/sky/theme.php: 404
unknown host name: 000

(000 means nginx closed the connection without an answer.) The login address is your instance's own (not the default), the password is stored only as a PHP password hash, and WonderCMS builds HTTPS links:

sudo -u www-data php /usr/local/lib/cloudimg/wondercms/wondercms-admin.php summary
theme=sky
forceHttps=true
loginIsDefault=no
hashAlgo=bcrypt
lastLogins=0
guard=0

The credentials file is readable by root only, the data file by the web server only, and the code is owned by root:

sudo stat -c '%a %U:%G %n' /root/wondercms-credentials.txt /var/www/wondercms/data/database.js /var/www/wondercms/index.php
600 root:root /root/wondercms-credentials.txt
640 www-data:www-data /var/www/wondercms/data/database.js
644 root:root /var/www/wondercms/index.php

The certificate was generated on this instance and names its own addresses:

H=$(sudo grep '^WONDERCMS_URL=' /root/wondercms-credentials.txt | sed -E 's#^WONDERCMS_URL=https://([^/]+)/.*#\1#')
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name:
    DNS:ec2-<public-ip-dashed>.<region>.compute.amazonaws.com, IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost

nginx and PHP cannot reach the instance metadata service, PHP cannot run shell commands, is confined to WonderCMS's folders and runs the login guard before every request, and nginx hands PHP the configured host name and the real client address from the connection, never values a client sends:

systemctl show nginx -p IPAddressDeny
systemctl show php8.3-fpm -p IPAddressDeny
php-fpm8.3 -i 2>/dev/null | grep -E '^(open_basedir|disable_functions|expose_php|auto_prepend_file) ' | cut -d' ' -f1-3
grep -E 'fastcgi_param (HTTP_HOST|HTTP_X_FORWARDED_FOR|HTTP_CLIENT_IP)' /etc/nginx/sites-available/cloudimg-wondercms
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
auto_prepend_file => /usr/local/lib/cloudimg/wondercms/login-guard.php
disable_functions => exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec
expose_php => Off
open_basedir => /var/www/wondercms/:/usr/local/lib/cloudimg/wondercms/:/var/lib/php/sessions/:/tmp/
        fastcgi_param HTTP_HOST $wcms_host;
        fastcgi_param HTTP_X_FORWARDED_FOR $remote_addr;
        fastcgi_param HTTP_CLIENT_IP $remote_addr;

If you lock yourself out with ten wrong passwords, wait five minutes or clear the lockout:

sudo wondercms-reset-lockout

Step 10: Your own DNS name and certificate

Point a DNS record at the instance, then set the name WonderCMS is served as. This mints a new self signed certificate for the name, updates nginx (which hands the name to WonderCMS for every link) and the URLs in /root/wondercms-credentials.txt, and keeps the name across reboots:

sudo wondercms-set-hostname <your-domain>

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

sudo install -m 0644 <your-domain>.crt /etc/nginx/ssl/wondercms.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/ssl/wondercms.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 the HTTPS server in /etc/nginx/sites-available/cloudimg-wondercms. The image does not send one: browsers ignore HSTS from a site whose certificate they do not trust, so it only takes effect with a trusted certificate.

Step 11: Password, login address and upgrades

wondercms-set-password gives the admin a new random password, signs out every open admin session and records the password in /root/wondercms-credentials.txt. Run it without arguments, or pass your own password (24 to 64 letters and digits) on stdin. You can also change the password in Settings > Security.

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

To move the login page, change the login URL in Settings > Security; WonderCMS uses the new address at once (note it down: the credentials file keeps the first-boot address).

The WonderCMS core is read only to PHP, so WonderCMS's built-in core Update button cannot replace index.php on this image; themes and plugins still update from the settings panel. To move to a newer WonderCMS release later, snapshot the instance, download the release zip from wondercms.com or GitHub, check its published SHA-256, and replace /var/www/wondercms/index.php with the one from the zip (sudo install -o root -g root -m 0644 index.php /var/www/wondercms/index.php). Your site is untouched, because it lives in the data folder. The image ships the version named in the listing.

Step 12: Server components

Component Path
WonderCMS core (root owned) /var/www/wondercms/index.php
Site data (data file, uploads, themes, plugins) /var/www/wondercms/data (themes and plugins point into it)
nginx site /etc/nginx/sites-available/cloudimg-wondercms
PHP settings /etc/php/8.3/fpm/conf.d/99-cloudimg-wondercms.ini
Login guard /usr/local/lib/cloudimg/wondercms/login-guard.php
Certificate and key /etc/nginx/ssl/
First boot script /usr/local/sbin/wondercms-firstboot.sh
Helpers /usr/local/sbin/wondercms-set-hostname, /usr/local/sbin/wondercms-set-password, /usr/local/sbin/wondercms-reset-lockout
Credentials /root/wondercms-credentials.txt (mode 0600)

Step 13: Security recommendations

  • Allow port 22 in the security group from your own networks only.
  • Keep the admin password and the login address secret, and rotate the password if it may have been shared (Step 11).
  • Use your own DNS name and a certificate your browsers trust (Step 10).
  • Install only themes and plugins you trust, and delete backup zips from Files once downloaded.
  • Keep the operating system patched; unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else.
  • Back up the data folder regularly (Step 8).

Support and licensing

WonderCMS is open source under the MIT licence (a copy is at /usr/share/doc/wondercms/LICENSE); there is no per user or per CPU licence fee. 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