CMS Made Simple 2.2.24 on AWS User Guide
Overview
This guide covers launching and using the CMS Made Simple 2.2.24 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.
CMS Made Simple (CMSMS) is a mature open source content management system written in PHP: pages organised in a hierarchy, designs built from templates and stylesheets, an admin console for editors and administrators, a file manager for images and documents, a search module and an ecosystem of add-on modules. It suits company and organisation websites that a small team keeps up to date.
The cloudimg image adds:
- A security patch for the bundled template engine. CMS Made Simple renders every page through the Smarty template engine, which it bundles. Release 2.2.24 still bundles Smarty 4.5.5, which is affected by CVE-2026-82531 (a high severity flaw that can inject PHP into regenerated cache files), CVE-2026-62993 (server-side request forgery through
{fetch}and a redirect) and CVE-2026-62992 (reading files outside the trusted folders through a symbolic link). This image replaces the bundled copy with the upstream Smarty 4.5.8 release, which fixes all three, and changes nothing else in CMS Made Simple. The file list and checksums of that release ship with the image, so you can check it yourself (Step 3). - The current release. CMS Made Simple 2.2.24 is the newest stable release. It includes the 2.2.23 security fixes: front-end template names from a URL can no longer name files or other template resources (a template injection and file inclusion chain), a path traversal in the UserGuide module's import is closed, and session cookies are HttpOnly, Secure over HTTPS and SameSite. The download is verified against a pinned checksum.
- No web installer, no shared database. A plain CMS Made Simple installation starts with a web installer that whoever opens it first can complete. This image contains no installer at all and no database: the MariaDB data directory is empty in the image. Your instance's first boot creates its own database and database user, loads the stock sample site, and gives the instance its own site salt (
sitemask, part of every admin password hash), its own login cookie signing key and an admin password generated on your instance. Nothing from the image build is reused. - HTTPS from the first boot. nginx serves the site 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.
- Only CMS Made Simple's entry points run. PHP runs only through
index.php,moduleinterface.phpand the admin console's own pages under/admin/.config.php, the libraries, the module code, the documentation, templates and CMSMS's working folder (tmp/) are never served, and no other PHP file in the installation can be called. - Uploads never run. Files in
uploads/are never executed: PHP and CGI files there return 404. Every file fromuploads/is sent with a sandboxing content security policy andnosniff; images, PDFs, fonts, audio, video, stylesheets and scripts are served normally, and any other file (HTML, SHTML, SVG, XML or an unknown type) is only offered as a download, so an uploaded file can never act as a page on your site. - A rate-limited login. CMS Made Simple does not throttle sign in attempts. On this image nginx limits sign in and password reset requests to 6 a minute from one address after a burst of 10, and the address comes from the connection, never from a header a client sends.
- Hardened defaults. The code belongs to root and is read only to PHP; PHP cannot run shell commands and is confined to the site's folders. MariaDB has no network listener at all (CMSMS talks to it over a local socket) and its
rootaccount signs in only through the socket as the operating system's root. nginx, PHP and MariaDB are blocked from the instance metadata service, the AMI requires IMDSv2, requests for a host name the instance does not serve are dropped, and CMSMS always builds its links from the address you configure (root_url), never from one a request claims. - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: CMS Made Simple 2.2.24 (the official release, checksum verified, with Smarty 4.5.8 in place of the bundled 4.5.5), MariaDB 10.11, PHP 8.3 FPM, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. The database and the uploads are on a separate 30 GiB EBS volume mounted at /var/lib/cmsms.
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 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 editors while you prepare it.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick CMS Made Simple 2.2.24 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. Keep the second volume (the database and uploads) in the launch settings; you can make it larger there.
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=cms-made-simple}]'
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| CMS Made Simple 2.2.24 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 loads the site). Then nginx, PHP, MariaDB and the first boot unit are active:
systemctl is-active nginx php8.3-fpm mariadb cms-made-simple-firstboot
active
active
active
active
nginx serves ports 80 and 443; PHP and MariaDB listen on local sockets only, 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 CMS Made Simple release, the patched Smarty version (checked file by file against the checksums of the Smarty 4.5.8 release that ship with the image), PHP, MariaDB and the data volume:
sudo grep "^\$CMS_VERSION = " /var/www/cmsms/lib/version.php
sudo php -r 'require "/var/www/cmsms/lib/smarty/Smarty.class.php"; echo "Smarty ", Smarty::SMARTY_VERSION, "\n";'
sudo bash -c 'cd /var/www/cmsms/lib/smarty && sha256sum --quiet -c /usr/share/doc/cmsms/smarty/libs-4.5.8.sha256 && echo "lib/smarty matches the Smarty 4.5.8 manifest ($(wc -l < /usr/share/doc/cmsms/smarty/libs-4.5.8.sha256) files)"'
php -v | head -1
mariadb --version
findmnt -no TARGET,FSTYPE,SIZE /var/lib/cmsms
$CMS_VERSION = '2.2.24';
Smarty 4.5.8
lib/smarty matches the Smarty 4.5.8 manifest (212 files)
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
/var/lib/cmsms ext4 29.4G
Step 4: Get your sign-in details
The credentials file holds the address the site is served as (your instance's public DNS name by default), the admin console address, the admin login and password, and the database credentials, all generated on this instance. Show it with:
sudo cat /root/cms-made-simple-credentials.txt
CMSMS_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
CMSMS_ADMIN_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/admin/
CMSMS_ADMIN_USER=admin
CMSMS_ADMIN_PASSWORD=<24 letters and digits>
CMSMS_DB_NAME=cmsms
CMSMS_DB_USER=cmsms
CMSMS_DB_PASSWORD=<48 hexadecimal characters>
Step 5: Visit the site and sign in
Open CMSMS_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). The site shows CMS Made Simple's stock sample site, so a new instance renders real pages:

Open CMSMS_ADMIN_URL (the admin console, /admin/) and sign in with the user name admin and CMSMS_ADMIN_PASSWORD:

The admin console opens on its dashboard, with the menus CMS, Content, Layout, User Management, Extensions, Site Admin and My Preferences:

Under My Preferences > My Account set your own e-mail address (it starts as admin@localhost.localdomain) and, if you like, your own password.
Step 6: Pages, designs and files
Content > Content Manager lists the site's pages as a tree. Open a page to edit its title, menu text and content (the WYSIWYG editor is MicroTiny), or choose Add New Content to create one; a page is shown in the navigation once it is active and set to show in the menu.

Layout > Design Manager holds the designs (sets of templates and stylesheets); the sample site uses Simplex. Content > File Manager uploads images and documents to uploads/ on the data volume. Only give the file manager to people you trust, and remember that on this image uploaded HTML, SVG and similar files are offered for download rather than shown as pages (Step 8).
User Management > Backend Users adds people. Give each person their own account in the narrowest group that works (the stock groups are Admin, Editor and Designer).
Email: CMS Made Simple sends password reset links and notifications with PHP's mail function by default, and the image has no mail server. Set Site Admin > Settings - Global Settings, tab Mail Settings, to SMTP with your mail server before you rely on e-mail. The admin password can always be reset from the shell (Step 11).
Step 7: Your data
The site's data is on its own EBS volume at /var/lib/cmsms: the MariaDB data directory (mysql, initialised on this instance's first boot) and the uploads (uploads, which the site serves as /uploads/):
sudo ls -l /var/lib/cmsms | sed -E 's/ +/ /g' | cut -d' ' -f1,3,4,9-
total
drwx------ root root lost+found
drwxr-x--- mysql mysql mysql
drwxr-x--- www-data www-data uploads
The connection to the database and the site address are in /var/www/cmsms/config.php, which first boot wrote. The site salt (sitemask) and the login cookie key live in the database (cms_siteprefs), so a database backup carries them with the password hashes that depend on them.
MariaDB has four accounts: the system accounts root and mysql (which sign in only through the local socket as the matching operating system user), the locked mariadb.sys, and cmsms, the site's own user with this instance's password. It has no network listener:
sudo mysql -N -e "SELECT CONCAT(User,'@',Host) FROM mysql.global_priv ORDER BY 1; SELECT @@skip_networking, @@datadir"
cmsms@localhost
mariadb.sys@localhost
mysql@localhost
root@localhost
1 /var/lib/cmsms/mysql/
Back the site up with EBS snapshots of the data volume and the root volume, or as files:
sudo mysqldump --single-transaction cmsms | gzip > cmsms-db.sql.gz
sudo tar -czf cmsms-files.tar.gz -C / var/lib/cmsms/uploads var/www/cmsms/config.php
Step 8: Security checks
Port 80 sends browsers to HTTPS, the health endpoint answers for load balancer checks, and the installer, the configuration, the libraries, CMSMS's compiled templates and its documentation are not reachable:
H=$(sed -n 's/^set \$cmsms_host "\(.*\)";$/\1/p' /etc/nginx/cmsms-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/"
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/healthz"
for p in /cmsms-2.2.24-install.php /config.php /lib/include.php /lib/smarty/Smarty.class.php /tmp/templates_c/ /doc/CHANGELOG.txt; 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/admin/
ok
/cmsms-2.2.24-install.php: 404
/config.php: 404
/lib/include.php: 404
/lib/smarty/Smarty.class.php: 404
/tmp/templates_c/: 404
/doc/CHANGELOG.txt: 404
unknown host name: 000
(000 means nginx closed the connection without an answer.) The admin console is closed until you sign in:
H=$(sed -n 's/^set \$cmsms_host "\(.*\)";$/\1/p' /etc/nginx/cmsms-host.conf)
curl -sk -o /dev/null -w 'admin console: %{http_code} -> %{redirect_url}\n' --resolve "$H:443:127.0.0.1" "https://$H/admin/index.php"
admin console: 302 -> https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/admin/login.php
A file in uploads/ that is not a passive type, here a stock HTML file of the sample design, is offered as a download with the sandbox policy, never shown as a page:
H=$(sed -n 's/^set \$cmsms_host "\(.*\)";$/\1/p' /etc/nginx/cmsms-host.conf)
curl -sk -D - -o /dev/null --resolve "$H:443:127.0.0.1" "https://$H/uploads/simplex/index.html" | tr -d '\r' | grep -iE '^(HTTP|content-type|content-disposition|x-content-type|content-security)'
HTTP/2 200
content-type: application/octet-stream
x-content-type-options: nosniff
content-security-policy: default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'; sandbox
content-disposition: attachment
The credentials file is readable by root only, config.php by root and the web server's group, the uploads and the working folder by the web server only, and the code belongs to root:
sudo stat -c '%a %U:%G %n' /root/cms-made-simple-credentials.txt /var/www/cmsms/config.php /var/www/cmsms /var/www/cmsms/tmp /var/lib/cmsms/uploads /var/www/cmsms/index.php
600 root:root /root/cms-made-simple-credentials.txt
640 root:www-data /var/www/cmsms/config.php
750 root:www-data /var/www/cmsms
750 www-data:www-data /var/www/cmsms/tmp
750 www-data:www-data /var/lib/cmsms/uploads
644 root:root /var/www/cmsms/index.php
CMSMS builds its links from the address in config.php (never from a request), and its permissive template mode is off:
sudo grep -E "^\\\$config\['(root_url|permissive_smarty)'\]" /var/www/cmsms/config.php
$config['permissive_smarty'] = false;
$config['root_url'] = 'https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com';
The certificate was generated on this instance and names its own addresses:
H=$(sed -n 's/^set \$cmsms_host "\(.*\)";$/\1/p' /etc/nginx/cmsms-host.conf)
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, PHP and MariaDB cannot reach the instance metadata service; PHP cannot run shell commands and is confined to the site's folders; nginx hands PHP the configured host name and the real client address from the connection (never values a client sends, and never an Authorization header), and rate-limits sign in requests:
for u in nginx php8.3-fpm mariadb; do systemctl show $u -p IPAddressDeny; done
php-fpm8.3 -i 2>/dev/null | grep -E '^(open_basedir|disable_functions|expose_php) ' | cut -d' ' -f1-3
grep -E 'fastcgi_param (HTTP_HOST|SERVER_NAME|HTTP_X_FORWARDED_FOR|HTTP_X_FORWARDED_HOST|HTTP_AUTHORIZATION) ' /etc/nginx/snippets/cloudimg-cmsms-php.conf
grep -h 'limit_req_zone\|limit_req_status' /etc/nginx/conf.d/cloudimg-cmsms-login-guard.conf
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
IPAddressDeny=169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128
disable_functions => exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec
expose_php => Off
open_basedir => /var/www/cmsms/:/var/lib/cmsms/uploads/:/var/lib/php/sessions/:/tmp/:/usr/share/php/
fastcgi_param HTTP_HOST $cmsms_host;
fastcgi_param SERVER_NAME $cmsms_host;
fastcgi_param HTTP_X_FORWARDED_HOST "";
fastcgi_param HTTP_X_FORWARDED_FOR $remote_addr;
fastcgi_param HTTP_AUTHORIZATION "";
limit_req_zone $cmsms_auth_key zone=cmsms_auth:10m rate=6r/m;
limit_req_status 429;
If a burst of wrong passwords rate-limits you (HTTP 429), wait a minute and try again: the limit only slows down repeated attempts from one address. Failed sign ins are recorded with the client's address under Site Admin > Admin Log.
sudo cms-made-simple-cred-roundtrip checks the whole sign in path on the instance: the redirect to HTTPS, the refused installer and configuration, a sign in with the password from the credentials file, and that wrong and common passwords are refused.
Step 9: Modules
Extensions > Module Manager lists the installed modules and the modules available from the CMS Made Simple Forge. The image keeps the code, modules/ included, owned by root and read only to PHP, so the Module Manager shows Cannot Download for new modules. To install or upgrade modules, open the folder to the web server for the duration, then close it again:
sudo chown -R www-data:www-data /var/www/cmsms/modules
# install or upgrade the module in Extensions > Module Manager
sudo chown -R root:root /var/www/cmsms/modules
Only install modules you trust: a module is PHP code that runs with the site's rights.
Step 10: Your own DNS name and certificate
Point a DNS record at the instance, then set the name the site is served as. This mints a new self signed certificate for the name, updates nginx, CMSMS's root_url (used in links, redirects and password reset mails) and the addresses in /root/cms-made-simple-credentials.txt, clears CMSMS's caches and keeps the name across reboots:
sudo cmsms-set-hostname <your-domain>
To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running cmsms-set-hostname again replaces it with a self signed one):
sudo install -m 0644 <your-domain>.crt /etc/nginx/ssl/cmsms.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/ssl/cmsms.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-cmsms-headers.conf. The image does not send one: browsers ignore HSTS from a site whose certificate they do not trust.
Step 11: Passwords and upgrades
cmsms-set-password gives the admin account a new random password with CMS Made Simple's own password scheme and records it in /root/cms-made-simple-credentials.txt. Run it without arguments, or pass your own password (24 to 96 letters and digits) on stdin. Users change their own password under My Preferences > My Account, and an administrator can change anyone's under User Management.
sudo cmsms-set-password
printf '%s\n' '<your-new-password>' | sudo cmsms-set-password --stdin
CMS Made Simple stores admin passwords as an MD5 hash salted with the site's sitemask (its upstream scheme). The image generates a long random password and a per instance salt; if you choose your own passwords, make them long and unique.
Upgrades. Snapshot the instance and the data volume first. CMS Made Simple upgrades through its own installer, which this image does not contain: download the new release's -install.zip from https://www.cmsmadesimple.org/downloads/cmsms, read its release notes, and follow CMSMS's upgrade documentation, keeping config.php, uploads and the database. After any core upgrade, check the bundled Smarty again (Step 3): an upgrade puts back whatever Smarty version that release bundles. If it is older than 4.5.8, replace /var/www/cmsms/lib/smarty with the libs/ folder of the current Smarty 4 release from https://github.com/smarty-php/smarty/releases.
System Verification. Site Admin > System Verification compares the installation with the checksum file of a release. Against the 2.2.24 checksum file it lists the files under lib/smarty among the changed files: that is the Smarty 4.5.8 security patch, not tampering (use the Smarty manifest in Step 3 to check that folder instead).
Step 12: Server components
| Component | Path |
|---|---|
| CMS Made Simple code (root owned) | /var/www/cmsms |
| Smarty 4.5.8 (security patch) and its file manifest | /var/www/cmsms/lib/smarty, /usr/share/doc/cmsms/smarty/libs-4.5.8.sha256 |
| Configuration: database connection, site address | /var/www/cmsms/config.php |
| Working folder: caches, compiled templates | /var/www/cmsms/tmp |
| Database (MariaDB datadir) and uploads | /var/lib/cmsms/mysql, /var/lib/cmsms/uploads (their own EBS volume) |
| Stock sample site loaded on first boot | /usr/local/lib/cloudimg/cmsms/site.sql, /usr/local/lib/cloudimg/cmsms/uploads-stock |
| nginx site, upload headers and login rate limit | /etc/nginx/sites-available/cloudimg-cmsms, /etc/nginx/snippets/cloudimg-cmsms-upload-headers.conf, /etc/nginx/conf.d/cloudimg-cmsms-login-guard.conf |
| PHP settings | /etc/php/8.3/fpm/conf.d/99-cloudimg-cmsms.ini |
| MariaDB settings | /etc/mysql/mariadb.conf.d/99-cloudimg-cmsms.cnf |
| Certificate and key | /etc/nginx/ssl/ |
| First boot script | /usr/local/sbin/cms-made-simple-firstboot.sh |
| Helpers | /usr/local/sbin/cmsms-set-hostname, cmsms-set-password, cms-made-simple-cred-roundtrip |
| Release, licences and image notes | /usr/share/doc/cmsms/ |
| Credentials | /root/cms-made-simple-credentials.txt (mode 0600) |
Step 13: Security recommendations
- Allow port 22 in the security group from your own networks only.
- Keep the admin password secret and rotate it if it may have been shared (Step 11). Give each person their own account in the narrowest group that works.
- Use your own DNS name and a certificate your browsers trust (Step 10).
- Keep CMS Made Simple current, and check the bundled Smarty after every core upgrade (Step 11). Install only modules you trust (Step 9).
- Keep the operating system patched; unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else. MariaDB, PHP-FPM and PHP's zip and intl extensions come from Ubuntu's universe repository;sudo pro security-status --esm-appsshows whether Ubuntu Pro (ESM Apps) has security updates for them (none were pending when the image was built). - Snapshot the data volume regularly (Step 7).
Support and licensing
CMS Made Simple is free software under the GNU General Public License, version 2 or (at your option) any later version; Smarty is free software under the GNU Lesser General Public License version 3 (copies and the release record are in /usr/share/doc/cmsms/). There is no per user or per CPU licence fee. This image installs the upstream CMS Made Simple release unchanged except for the Smarty 4.5.8 security patch 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