Applications AWS

Evolution CMS 3.5.9 on AWS User Guide

| Product: Evolution CMS 3.5.9 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Evolution CMS 3.5.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.

Evolution CMS is a free and open source content management system and PHP framework, the community continuation of MODX Evolution. Pages ("resources") live in a document tree; templates, chunks, snippets, plugins and template variables shape how they render; and the browser based Manager at /manager/ is where you edit content, files, users and settings.

The cloudimg image adds:

  • No web installer, no shared database. Evolution's browser installer creates the database connection and the first manager account for whoever opens it first. This image ships no database at all: the MariaDB data directory is empty in the image, there is no database configuration file, and the installer is not in the web root. Your instance's first boot initialises its own database and runs Evolution's own command line installer (install/cli-install.php) before the web server starts, which creates the schema, the manager user admin with a password generated on your instance and the database configuration, and then removes the installer again.
  • Secrets of your instance's own, never on a command line. The database password, the admin password, the site identifier and the TLS key are all generated on your instance; nothing from the image build is reused. Evolution's installer normally takes both passwords as command line arguments; here a small root-only wrapper reads them from files that only root can read and hands them to the installer in memory, so they never appear in the process list or a log.
  • The current release. Evolution CMS 3.5.9 is the newest stable release. It includes the security fixes of 3.5.8 and 3.5.9 (SQL injection and code injection fixes, a manager access check, stricter CSRF protection) and updated bundled libraries; the download is verified against a pinned checksum.
  • No calls home. The stock dashboard fetches news and extras feeds from evo.im, asks GitHub for the newest version and checks a list of outdated extras on GitHub. On this image the dashboard feeds are empty, and the Updater and OutdatedExtrasCheck plugins and the Extras module are disabled, so the server makes none of those calls while you use the Manager (Step 7 shows each switch).
  • HTTPS from the first boot. nginx serves Evolution 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 Evolution's entry points run PHP. The site goes through Evolution's index.php, and the Manager through its own index.php and the handful of scripts it calls (the same list as Evolution's own nginx template). The framework (core/), its configuration and caches, the bundled libraries, the installer and every other PHP file are never served. Uploads in assets/images, assets/files and assets/media are served as static files only: every one with 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.
  • A rate-limited login. nginx limits requests that post to the Manager (the sign in and everything else that carries a password) to 20 a minute from one address after a burst of 20, and Evolution blocks an account for 60 minutes after three wrong passwords. nginx uses the address of the connection, never a header a client sends.
  • The real host and client address. nginx hands Evolution the host name you configure and nothing a request claims (X-Forwarded-Host, Host values for other names), and removes X-Forwarded-For and Client-IP before PHP sees them, so Evolution builds every link from your host name and records the address of the connection in its logs.
  • Hardened defaults. The Evolution code belongs to root and is read only to PHP; only Evolution's runtime folders (core/storage and the assets cache, upload and backup folders) are writable. PHP cannot run shell commands and is confined to the site folder. MariaDB has no network listener at all (Evolution 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 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: Evolution CMS 3.5.9 (the upstream release, checksum verified), 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 (Evolution 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 Evolution CMS 3.5.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=evolution-cms}]'

Step 2: Connect to your instance

Version SSH user
Evolution CMS 3.5.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 Evolution's installer). Then nginx, PHP, MariaDB and the first boot unit are active:

systemctl is-active nginx php8.3-fpm mariadb evolution-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 Evolution release and the PHP and MariaDB versions:

grep "'version' =>" /var/www/evolution/core/factory/version.php | head -1
php -v | head -1
mariadb --version
    'version' => '3.5.9',
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

Step 4: Get your sign-in details

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

sudo cat /root/evolution-cms-credentials.txt
EVOLUTION_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
EVOLUTION_MANAGER_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/manager/
EVOLUTION_ADMIN_USER=admin
EVOLUTION_ADMIN_PASSWORD=<24 letters and digits>
EVOLUTION_DB_NAME=evolution
EVOLUTION_DB_USER=evolution
EVOLUTION_DB_PASSWORD=<48 hexadecimal characters>

Step 5: Visit the site and sign in

Open EVOLUTION_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 11). A fresh site shows Evolution's starter page:

The Evolution CMS starter page of a fresh site, saying the install was successful

Open EVOLUTION_MANAGER_URL and sign in with the user name admin and EVOLUTION_ADMIN_PASSWORD:

The Evolution CMS Manager sign in page with the Username and Password fields

The Manager opens on its dashboard, with the document tree on the left:

The Evolution CMS Manager dashboard with the welcome panel, the recent resources list and the document tree

admin is an Administrator. Create a separate account for each editor under Users > Users and give it a role (Users > Roles) with only the permissions it needs.

Step 6: Create and edit pages

Each page is a resource in the document tree. Select a resource (the starter page is Evolution CMS Install Success, id 1) to edit its title, content and settings, or choose New Resource on the dashboard or above the tree for a new page. Save, then open the site to see it; with plain links on (Step 7) a page is at /index.php?id=<id>.

Editing a resource in the Evolution CMS Manager: the title, content editor and document settings

Templates, template variables, chunks, snippets and plugins are under Elements; files and images you upload are under Elements > Manage Files (the file manager starts in assets/).

Step 7: The closed-by-default settings

cloudimg sets these with Evolution's own system settings, so you can change each one in the Manager. Configuration is in the System menu (the gear icon at the top right):

Setting Image default Where to change it
Dashboard news and extras feeds (fetched from evo.im) empty Configuration > Site, "EVO Releases RSS Feed" and "Extras Releases RSS Feed"
Updater plugin (asks GitHub for new versions) disabled Elements > Plugins > Updater
OutdatedExtrasCheck plugin (reads a list on GitHub) disabled Elements > Plugins > OutdatedExtrasCheck
Extras module (the online extras store) disabled Elements > Manage Modules > Extras
Links plain index.php?id=<id> links (friendly URLs off) Configuration > Friendly URLs
File manager start folder assets/ Configuration > File Manager
Failed sign ins before an account is blocked / block time 3 / 60 minutes Configuration > Security

The image's settings as stored in the database:

sudo mysql -N -B evolution -e "SELECT setting_name, setting_value FROM evo_system_settings WHERE setting_name IN ('rss_url_releases','rss_url_extras','server_protocol','friendly_urls','filemanager_path','failed_login_attempts','blocked_minutes') ORDER BY 1; SELECT name, disabled FROM evo_site_plugins ORDER BY name; SELECT name, disabled FROM evo_site_modules;"
blocked_minutes 60
failed_login_attempts   3
filemanager_path    [(base_path)]assets/
friendly_urls   0
rss_url_extras
rss_url_releases
server_protocol https
CodeMirror  0
OutdatedExtrasCheck 1
TransAlias  0
Updater 1
Extras  1

(1 in the second column means disabled.) If you switch any of the calls home back on, the server needs outbound HTTPS to evo.im, github.com and raw.githubusercontent.com. Installing extras: the code folder belongs to root, so add packages from the shell (Step 12) rather than the Extras store.

Step 8: Your data

Everything that makes your site lives in two places: the evolution database in MariaDB, and the uploads under /var/www/evolution/assets (images, files and media). Evolution's caches in core/storage and assets/cache are rebuilt automatically.

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 evolution > /root/evolution-$(date +%F).sql'
sudo tar -C /var/www/evolution -czf /root/evolution-assets-$(date +%F).tgz assets/images assets/files assets/media

Evolution's own backup page (Tools > Backup) also works; it writes into assets/backup, which is never served. 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 framework, its configuration and caches, the bundled libraries, the Manager's internal PHP files and the composer files are not reachable:

H=$(sed -n 's/^set \$evo_host "\(.*\)";$/\1/p' /etc/nginx/evolution-host.conf)
curl -s -o /dev/null -w 'port 80: %{http_code} -> %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/manager/"
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/healthz"
for p in /install/ /core/config/database/connections/default.php /core/vendor/autoload.php /core/storage/cache/env.php /manager/processors/save_content.processor.php /assets/cache/siteManager.php /composer.json; 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/manager/
ok
/install/: 404
/core/config/database/connections/default.php: 404
/core/vendor/autoload.php: 404
/core/storage/cache/env.php: 404
/manager/processors/save_content.processor.php: 404
/assets/cache/siteManager.php: 404
/composer.json: 404
unknown host name: 000

(000 means nginx closed the connection without an answer.) The credentials file is readable by root only, the database configuration by root and the web server, Evolution's runtime and upload folders belong to the web server, and the code belongs to root:

sudo stat -c '%a %U:%G %n' /root/evolution-cms-credentials.txt /var/www/evolution/core/config/database/connections/default.php /var/www/evolution/core/storage /var/www/evolution/assets/images /var/www/evolution/assets/files /var/www/evolution/index.php
600 root:root /root/evolution-cms-credentials.txt
640 root:www-data /var/www/evolution/core/config/database/connections/default.php
750 www-data:www-data /var/www/evolution/core/storage
750 www-data:www-data /var/www/evolution/assets/images
750 www-data:www-data /var/www/evolution/assets/files
644 root:root /var/www/evolution/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 \$evo_host "\(.*\)";$/\1/p' /etc/nginx/evolution-host.conf)
echo '<b>demo</b>' | sudo -u www-data tee /var/www/evolution/assets/files/guide-demo.html >/dev/null
curl -sk -D - -o /dev/null --resolve "$H:443:127.0.0.1" "https://$H/assets/files/guide-demo.html" | tr -d '\r' | grep -iE '^(HTTP|content-disposition|content-security-policy|x-content-type)'
sudo rm -f /var/www/evolution/assets/files/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 and names its own addresses:

H=$(sed -n 's/^set \$evo_host "\(.*\)";$/\1/p' /etc/nginx/evolution-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, MariaDB and the first boot service cannot reach the instance metadata service; PHP cannot run shell commands and is confined to the site folder; nginx hands PHP the configured host name (also as Evolution's own host allowlist, EVO_SITE_HOSTNAMES) and removes forwarding headers and any Authorization header a client sends, and rate-limits Manager requests:

for u in nginx php8.3-fpm mariadb evolution-firstboot; 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|EVO_SITE_HOSTNAMES|HTTP_X_FORWARDED_FOR|HTTP_CLIENT_IP|HTTP_X_FORWARDED_HOST|HTTP_AUTHORIZATION) ' /etc/nginx/snippets/cloudimg-evolution-fastcgi.conf
grep -h 'limit_req_zone\|limit_req_status' /etc/nginx/conf.d/cloudimg-evolution-guard.conf
IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32
IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32
IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32
IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32
disable_functions => exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec
expose_php => Off
open_basedir => /var/www/evolution/:/var/lib/php/sessions/:/tmp/
fastcgi_param HTTP_HOST $evo_host;
fastcgi_param SERVER_NAME $evo_host;
fastcgi_param EVO_SITE_HOSTNAMES $evo_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 $evo_login_key zone=evo_login:10m rate=20r/m;
limit_req_zone $binary_remote_addr zone=evo_mgr:10m rate=240r/m;
limit_req_status 429;

The evo_login limit counts every request that posts to the Manager (signing in and saving your work), 20 a minute from one address after a burst of 20; evo_mgr allows 240 Manager requests a minute. Raise them in /etc/nginx/conf.d/cloudimg-evolution-guard.conf and /etc/nginx/snippets/cloudimg-evolution-manager.conf if your editors need more. If three wrong passwords block the admin account ("You are blocked and cannot log in"), wait 60 minutes or run sudo evolution-set-password (Step 12), which clears the block.

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

sudo evolution-cred-roundtrip
OK https 301 + no installer + default.php 404 + admin login + wrong and default passwords refused

Step 10: The Manager behind a proxy or load balancer

nginx removes X-Forwarded-For, X-Real-IP, Client-IP and Forwarded from every request, so Evolution's logs show the address of the connection. Behind an AWS load balancer that is the load balancer's address; nginx's own access log (/var/log/nginx/access.log) records it the same way. To record visitors' addresses there, 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 Evolution is served as. This mints a new self signed certificate for the name, updates nginx (which hands the name to Evolution), clears Evolution's caches, updates the addresses in /root/evolution-cms-credentials.txt and keeps the name across reboots:

sudo evolution-set-hostname <your-domain>

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

sudo install -m 0644 <your-domain>.crt /etc/nginx/ssl/evolution.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/ssl/evolution.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-evolution-headers.conf. The image does not send one: browsers ignore HSTS from a site whose certificate they do not trust.

Step 12: Passwords, extras and upgrades

evolution-set-password gives admin a new random password with Evolution's own password hashing, clears a failed sign in block, signs out every session and records the password in /root/evolution-cms-credentials.txt. Run it without arguments, or pass your own password (24 to 96 letters and digits) on stdin. Manager users change their own password under their user menu at the top right (Change Password).

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

E-mail. Evolution sends password reminders and notifications with PHP's mail(); the image has no mail server, so set an SMTP server under Configuration > Email & Templates before you rely on them.

Extras and upgrades. The code folder belongs to root, so install extras and upgrades from the shell, as root, after a snapshot. Evolution releases are published at https://github.com/evolution-cms/evolution/releases with release notes. Evolution's own update path (documented in install/cli-install.php) is to lay the files of the new release over the site and then run the updater, php cli-install.php --typeInstall=2 --removeInstall=y --skipComposer=y, from the new release's install folder; afterwards hand core/storage and assets/cache back to www-data (sudo chown -R www-data:www-data /var/www/evolution/core/storage /var/www/evolution/assets/cache) and check that install is gone. Keep core/config, core/custom, core/storage and your assets uploads when you copy the new files.

Step 13: Server components

Component Path
Evolution code (root owned) /var/www/evolution
Database connection (this instance's password) /var/www/evolution/core/config/database/connections/default.php
Uploads /var/www/evolution/assets/images, assets/files, assets/media
Caches, logs, sessions (rebuilt automatically) /var/www/evolution/core/storage, assets/cache
Database (MariaDB datadir) /var/lib/mysql
nginx site and request limits /etc/nginx/sites-available/cloudimg-evolution, /etc/nginx/conf.d/cloudimg-evolution-guard.conf, /etc/nginx/snippets/cloudimg-evolution-*.conf
PHP settings /etc/php/8.3/fpm/conf.d/99-cloudimg-evolution.ini
MariaDB settings /etc/mysql/mariadb.conf.d/99-cloudimg-evolution.cnf
Certificate and key /etc/nginx/ssl/
First boot script, installer wrapper and helpers /usr/local/sbin/evolution-firstboot.sh, /usr/local/lib/cloudimg/evolution/
Helpers /usr/local/sbin/evolution-set-hostname, evolution-set-password, evolution-cred-roundtrip
Licence, release and image notes /usr/share/doc/evolution/
Credentials /root/evolution-cms-credentials.txt (mode 0600)

Step 14: 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 12). Give each editor their own account with only the permissions they need.
  • Keep default.php and your database backups secret (Step 8).
  • Use your own DNS name and a certificate your browsers trust (Step 11).
  • Keep Evolution on the newest release (Step 12) and install only extras 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

Evolution CMS is free software under the GNU General Public License, version 3 or later (a copy is at /usr/share/doc/evolution/LICENSE); the bundled libraries carry their own open source licences. There is no per user or per CPU licence fee. This image installs the unmodified upstream release, without its test suite, its unused jQuery copies and demo pages, and the vendored test suites and documentation, 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