CMS Made Simple
Overview
CMS Made Simple (CMSMS) is a mature, open source content management system written in PHP. It provides a page hierarchy, designs built from templates and stylesheets, an admin console, a file manager, a search module and a wide range of add-on modules. It suits company and organisation websites that a small team keeps up to date.
This SecureAnyCloud image runs CMS Made Simple 2.2.24 on Ubuntu 24.04 LTS on Microsoft Azure. It ships with a security patch for the bundled template engine and sets up its own database, certificate and admin password on first boot.
What you get:
- A security patch for the bundled template engine. CMS Made Simple renders every page through the Smarty template engine. 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 it yourself (Step 4). - The current release. CMS Made Simple 2.2.24 is the newest stable release, and its download is verified against a pinned checksum. It includes the 2.2.23 security fixes. Front-end template names from a URL can no longer name files or other template resources (an unauthenticated template injection and file inclusion chain). A path traversal in the UserGuide module's import is closed. Session cookies are HttpOnly, Secure over HTTPS and SameSite.
- 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 on the data disk is empty in the image. The first boot of your VM 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, so no two VMs share a secret. - HTTPS from the first boot. nginx serves the site on port 443 with a certificate generated on your VM'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 the CMSMS working folder (tmp/) are never served, and no other PHP file in the installation can be called. - Uploads never run. PHP and CGI files in
uploads/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. 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
rootand 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, MariaDB and the first-boot service are blocked from the Azure Instance Metadata Service and the WireServer, while the Azure VM agent and cloud-init keep their access. Requests for a host name the VM does not serve are dropped. CMSMS always builds its links from the address you configure (root_url), never from one a request claims. - A fully patched base. Ubuntu 24.04 LTS is fully patched 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 Azure Linux agent. The database and the uploads live on a separate 30 GiB Azure data disk mounted at
/var/lib/cmsms. - Defaults: the default user is the administrator account you choose at deployment (
azureuserin the examples). The architecture is x64 (Gen2) and the recommended size is Standard_B2s.
Prerequisites
- An Azure subscription, an SSH key pair, and a virtual network and subnet in the target region.
- A network 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.
Deploy from the Azure Marketplace
In the Azure Portal, choose Create a resource, search the Marketplace for CMS Made Simple by SecureAnyCloud, and select Create.
On the Basics tab, pick your subscription, resource group, region and size (Standard_B2s to start). Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22), HTTP (80) and HTTPS (443).
On the Disks tab, keep the data disk. It holds the database and uploads, and you can make it larger here. Then select Review + create and Create.
To deploy with the Azure CLI instead, replace the values in angle brackets:
az vm image terms accept --urn sac1647283583153:cms-made-simple-ubuntu-24-04:default:latest
az vm create \
--resource-group <your-rg> --name cms-made-simple \
--image sac1647283583153:cms-made-simple-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name cms-made-simple --port 80,443 --priority 1010Connect to your VM
Connect over SSH as the administrator user you created:
ssh azureuser@<vm-public-ip>Check the services
First boot takes under a minute while it creates the database and loads the site. Afterwards, nginx, PHP, MariaDB and the first-boot unit are all active:
systemctl is-active nginx php8.3-fpm mariadb cms-made-simple-firstbootactive active active activenginx 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:22 users:(("sshd"),("systemd")) LISTEN 0.0.0.0:80 users:(("nginx"),("nginx"),("nginx")) LISTEN [::]:443 users:(("nginx"),("nginx"),("nginx")) LISTEN [::]:22 users:(("sshd"),("systemd")) LISTEN [::]:80 users:(("nginx"),("nginx"),("nginx"))Check the release and the Smarty patch
Run the commands below to check the CMS Made Simple release, the patched Smarty version, PHP, MariaDB and the data disk. Smarty is verified file by 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.4GGet your sign-in details
First boot writes a credentials file on your VM. It holds the address the site is served as (your VM's public IP address by default), the admin console address, the admin login and password, and the database credentials. Display it with:
sudo cat /root/cms-made-simple-credentials.txtCMSMS_URL=https://<vm-public-ip>/ CMSMS_ADMIN_URL=https://<vm-public-ip>/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>Visit the site and sign in
Open CMSMS_URL in your browser. The certificate is self-signed and generated on your VM, so accept the browser warning once, or install your own certificate (Step 11). The site shows CMS Made Simple's stock sample site, so a new VM 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 email address (it starts as admin@localhost.localdomain) and, if you like, your own password.
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 disk.
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 9).
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, go to the Mail Settings tab and switch to SMTP with your mail server. You can always reset the admin password from the shell (Step 12).
Your data
The site's data lives on its own Azure data disk at /var/lib/cmsms. It holds the MariaDB data directory (mysql, initialised on this VM'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 uploadsFirst boot writes 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 sign in only through the local socket as the matching operating system user. mariadb.sys is locked. cmsms is the site's own user, with a password unique to this VM. 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 Azure disk snapshots of the data disk and the OS disk, or export it 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.phpSecurity checks
Run these checks to confirm the hardening. Port 80 sends browsers to HTTPS, and the health endpoint answers for load balancer probes. The installer, the configuration, the libraries, the CMSMS 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/ || trueport 80: 301 -> https://<vm-public-ip>/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: 000The value 000 means nginx closed the connection without an answer. The admin console stays 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://<vm-public-ip>/admin/login.phpA file in uploads/ that is not a passive type is offered as a download with the sandbox policy, never shown as a page. This example requests a stock HTML file of the sample design:
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: attachmentThe credentials file is readable by root only. config.php is readable by root and the web server's group. The uploads and the working folder are accessible to 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.php600 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.phpCMSMS 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://<vm-public-ip>';The certificate is generated on your VM 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 subjectAltNamesubject=CN = <vm-public-ip>, O = sac X509v3 Subject Alternative Name: IP Address:<vm-public-ip>, IP Address:<vm-private-ip>, IP Address:127.0.0.1, DNS:localhostnginx, PHP, MariaDB and the first-boot service cannot reach the Azure Instance Metadata Service (169.254.169.254) or the WireServer (168.63.129.16). The Azure VM agent and cloud-init keep their access. 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 it rate-limits sign-in requests:
for u in nginx php8.3-fpm mariadb cms-made-simple-firstboot walinuxagent; do d=$(systemctl show $u -p IPAddressDeny --value | tr ' ' '\n' | sort | xargs); echo "$u: ${d:-none}"; 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.confnginx: 168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128 php8.3-fpm: 168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128 mariadb: 168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128 cms-made-simple-firstboot: 168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128 walinuxagent: none 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 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 VM. It covers the redirect to HTTPS, the refused installer and configuration, a sign-in with the password from the credentials file, and the refusal of wrong and common passwords:
sudo cms-made-simple-cred-roundtripOK https 301 + installer 404 + config 404 + admin login + wrong and default passwords refusedModules
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/modulesInstall only modules you trust. A module is PHP code that runs with the site's rights.
Your own DNS name and certificate
Point a DNS record at the VM's public IP address, then set the name the site is served as. The command creates a new self-signed certificate for the name and updates nginx. It also updates the CMSMS root_url (used in links, redirects and password reset emails) and the addresses in /root/cms-made-simple-credentials.txt. It 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 nginxOnce browsers trust the certificate, you can 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.
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. An administrator can change anyone's under User Management.
sudo cmsms-set-password printf '%s\n' '<your-new-password>' | sudo cmsms-set-password --stdinCMS 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-VM salt. If you choose your own passwords, make them long and unique.
Upgrades. Snapshot the OS disk and the data disk 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. Keep config.php, uploads and the database.
After any core upgrade, check the bundled Smarty again (Step 4). 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 as changed. That is the Smarty 4.5.8 security patch, not tampering. Use the Smarty manifest in Step 4 to check that folder instead.
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 (the Azure data disk, LUN 0)
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 and the VM address reader /usr/local/sbin/cms-made-simple-firstboot.sh, /usr/local/sbin/cmsms-netinfo
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)
Security recommendations
- Allow port 22 in the network security group from your own networks only.
- Keep the admin password secret, and rotate it if it may have been shared (Step 12). Give each person their own account in the narrowest group that works.
- Use your own DNS name and a certificate your browsers trust (Step 11).
- Keep CMS Made Simple current, and check the bundled Smarty after every core upgrade (Step 12). Install only modules you trust (Step 10).
- 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 the PHP zip and intl extensions come from the Ubuntu universe repository. Runsudo pro security-status --esm-appsto see whether Ubuntu Pro (ESM Apps) has security updates for them. None were pending when the image was built. - Snapshot the data disk regularly (Step 8).
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 licence fee or key. This image installs the upstream CMS Made Simple release unchanged, apart from the Smarty 4.5.8 security patch described above. The SecureAnyCloud charge covers the hardening, first-boot setup, image maintenance and 24/7 support.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24 hour response SLA
Deploy on Azure
Launch CMS Made Simple on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com