Deployment guide

CMS Made Simple 2.2.24

Overview

CMS Made Simple (CMSMS) is a mature, open-source content management system written in PHP. It offers hierarchical pages, 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.

This SecureAnyCloud AMI runs CMS Made Simple 2.2.24 on Ubuntu 24.04 LTS on Amazon EC2. It is one version of the SecureAnyCloud BookStack, CMS, Blogs and Wikis listing on AWS Marketplace. Select it as the software version when you subscribe and launch.

What you get:

    • 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 verify the patch 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 and no database: the MariaDB data directory is empty in the image. On the first boot of your instance, the image creates its own database and database user, loads the stock sample site, and generates a site salt (sitemask, part of every admin password hash), a login cookie signing key and an admin password. 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.php and the admin console's own pages under /admin/. config.php, the libraries, the module code, the documentation, templates and the CMSMS 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 from uploads/ is sent with a sandboxing content security policy and nosniff. Images, PDFs, fonts, audio, video, stylesheets and scripts are served normally. Any other file (HTML, SHTML, SVG, XML or an unknown type) is offered only 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. 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 (CMSMS talks to it over a local socket), and its root account 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.

      Defaults: default user ubuntu, architecture x86_64, recommended instance type m5.large.

Prerequisites

Before you launch, make sure you have:

  • 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. Restrict port 22 to your own networks. Open port 443 to your visitors for a public site, or only to your editors while you prepare it.
1

Launch the instance

Open the AWS Marketplace listing and choose Continue to Subscribe, then Continue to Configuration. Pick CMS Made Simple 2.2.24 on Ubuntu 24.04 as the software version, select your Region, and choose Launch through EC2.

Choose m5.large, your key pair and a security group as described above. Keep the second volume (the database and uploads) in the launch settings. You can make it larger there.

Alternatively, launch 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}]'
2

Connect to your instance

Connect over SSH as the ubuntu user, the default user for CMS Made Simple 2.2.24 on Ubuntu 24.04.

ssh -i <your-key.pem> ubuntu@<public-ip>
3

Check the services

First boot takes under a minute while it creates the database and loads the site. When it finishes, nginx, PHP, MariaDB and the first-boot unit are all 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"))

Check the CMS Made Simple release, the patched Smarty version, PHP, MariaDB and the data volume. The Smarty check compares every file against the checksums of the Smarty 4.5.8 release that ship with the image:

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
4

Get your sign-in details

Read the credentials file. It 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:

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>
5

Visit the site and sign in

Open CMSMS_URL in your browser. The certificate is self-signed and was generated on your instance, so accept the browser warning once, or install your own certificate (Step 10). The site shows the CMS Made Simple 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.

Go to My Preferences > My Account and set your own email address. It starts as admin@localhost.localdomain. You can also set your own password there.

6

Pages, designs and files

Open Content > Content Manager to see 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 appears in the navigation once it is active and set to show in the menu.

Open Layout > Design Manager to manage designs, which are sets of templates and stylesheets. The sample site uses Simplex. Use Content > File Manager to upload images and documents to uploads/ on the data volume. Give the file manager only to people you trust. On this image, uploaded HTML, SVG and similar files are offered for download rather than shown as pages (Step 8).

Open User Management > Backend Users to add people. Give each person their own account in the narrowest group that works. The stock groups are Admin, Editor and Designer.

Email: By default, CMS Made Simple sends password reset links and notifications with PHP's mail function, and the image has no mail server. Before you rely on email, open Site Admin > Settings - Global Settings, select the Mail Settings tab and configure SMTP with your mail server. You can always reset the admin password from the shell (Step 11).

7

Your data

The site's data lives on its own EBS volume at /var/lib/cmsms. It holds the MariaDB data directory (mysql, initialised on this instance's first boot) and the uploads folder, 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

First boot wrote the database connection and the site address to /var/www/cmsms/config.php. The site salt (sitemask) and the login cookie key live in the database (cms_siteprefs), so a database backup carries them along 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. MariaDB 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 up the site 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
8

Security checks

Run these checks to confirm that port 80 redirects browsers to HTTPS, the health endpoint answers load balancer checks, and the installer, the configuration, the libraries, the compiled templates and the 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

The status 000 means nginx closed the connection without an answer. Next, confirm that 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

Check how uploads are served. 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 and 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

Check file ownership and permissions. The credentials file is readable by root only, config.php by root and the web server's group, and the uploads and the working folder by the web server only. 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

Confirm that CMSMS builds its links from the address in config.php (never from a request) and that 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';

Inspect the certificate. It was generated on this instance and names the instance's 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 = sac 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

Finally, verify the runtime restrictions. 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. nginx also 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/sac-cmsms-php.conf grep -h 'limit_req_zone\|limit_req_status' /etc/nginx/conf.d/sac-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 triggers the rate limit (HTTP 429), wait a minute and try again. The limit only slows repeated attempts from one address. Failed sign-ins are recorded with the client's address under Site Admin > Admin Log.

Run sudo cms-made-simple-cred-roundtrip to check the whole sign-in path on the instance. It verifies 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.

9

Modules

Open Extensions > Module Manager to see 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

Install only modules you trust. A module is PHP code that runs with the site's rights.

10

Your own DNS name and certificate

Point a DNS record at the instance, then set the name the site is served as. The command creates a new self-signed certificate for the name, updates nginx, the CMSMS root_url (used in links, redirects and password reset emails) and the addresses in /root/cms-made-simple-credentials.txt, clears the CMSMS 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 certificate:

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/sac-cmsms-headers.conf. The image does not send one, because browsers ignore HSTS from a site whose certificate they do not trust.

11

Passwords and upgrades

Run cmsms-set-password to give the admin account a new random password using the CMS Made Simple password scheme. The command records the password 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, which is 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 the CMSMS upgrade documentation, keeping config.php, uploads and the database. After any core upgrade, check the bundled Smarty again (Step 3), because an upgrade restores 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 as changed. This is the Smarty 4.5.8 security patch, not tampering. Use the Smarty manifest in Step 3 to check that folder instead.

12

Server components

Use this reference to find the main components on the instance:

  • 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, on their own EBS volume: /var/lib/cmsms/mysql, /var/lib/cmsms/uploads
  • Stock sample site loaded on first boot: /usr/local/lib/sac/cmsms/site.sql, /usr/local/lib/sac/cmsms/uploads-stock
  • nginx site, upload headers and login rate limit: /etc/nginx/sites-available/sac-cmsms, /etc/nginx/snippets/sac-cmsms-upload-headers.conf, /etc/nginx/conf.d/sac-cmsms-login-guard.conf
  • PHP settings: /etc/php/8.3/fpm/conf.d/99-sac-cmsms.ini
  • MariaDB settings: /etc/mysql/mariadb.conf.d/99-sac-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 (mode 0600): /root/cms-made-simple-credentials.txt
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 upgrade applies everything else. MariaDB, PHP-FPM and the PHP zip and intl extensions come from the Ubuntu universe repository. Run sudo pro security-status --esm-apps to see 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 of the licences 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 SecureAnyCloud hardening, first-boot setup and support.

  • Email: sales@secureanycloud.com
  • Website: https://secureanycloud.com
  • Support: 24/7, with a 24 hour response SLA

Deploy on AWS

Launch CMS Made Simple 2.2.24 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.

View on Marketplace

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on AWS