Concrete CMS
Overview
This guide covers deploying and using the Concrete CMS 9.5.7 on Ubuntu 24.04 LTS image from SecureAnyCloud on Microsoft Azure.
Concrete CMS (formerly concrete5) is a free, open-source content management system known for in-context editing. Signed-in editors change a page on the page itself, adding and arranging blocks of content. The Dashboard manages the sitemap, files, users, permissions, forms, calendars and themes.
The image starts with Concrete's own Elemental sample site (pages, a blog, a portfolio and sample images) for you to edit or replace.
What you get:
- The 9.5.7 security update. Concrete CMS 9.5.7 is the newest stable release. It includes the security fixes of 9.5.3 to 9.5.5 (more than sixty CVEs, several reachable without signing in, such as the user search autocomplete CVE-2026-18110) and updated bundled libraries, including Guzzle 7.16 (the HTTP client; CVE-2026-69246 is fixed in 7.15.2). The download is verified against a pinned checksum (Step 3 shows the bundled versions).
- No web installer, no shared database. Concrete's web installer creates the database connection and the first administrator for whoever opens it first. This image ships no database at all: the MariaDB data directory on the data disk is empty in the image, and there is no
database.php. Your VM's first boot initializes its own database and runs Concrete's own command-line installer (c5:install) before the web server starts, which creates the schema, the sample site, the super-useradminwith a password generated on your VM, and the configuration. - Secrets of your VM's own. The database password, the
adminpassword, Concrete's three security tokens (they protect scheduled jobs, encrypted values and validation links) and, when you switch the REST API on, its RSA key pair are all generated on your VM. Nothing from the image build is reused, so no two VMs share a secret. No secret is ever passed on a command line. - No calls home. Concrete normally connects a new site to its marketplace, checks concretecms.com for updates, and fetches news, tutorials and background images from concretecms.com servers. On this image the server makes none of those calls (Step 7 shows each switch; the image was tested by counting the network connections PHP opens while an administrator browses the Dashboard, and there were none), and the geoPlugin visitor geolocation lookup is off. The Elemental theme loads its web font from Google Fonts and the Dashboard loads Google Charts in your browser.
- Closed by default. Public registration is off, email is off until you configure a mail server, and error details are never shown to visitors.
- HTTPS from the first boot. nginx serves Concrete 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 the front controller runs PHP. Every page goes through Concrete's
index.php. Its configuration (application/config), the command-line tool, the bundled libraries (concrete/vendor), the update folder, add-on code and composer files are never served, and no other PHP file can be called. Uploaded files inapplication/filesare served as static files only: every one withnosniffand 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 sign in and registration requests to 6 a minute from one address after a burst of 10, and Concrete bans an address for ten minutes after five failed sign ins in five minutes. Both use the address of the connection, never a header a client sends.
- Hardened defaults. The Concrete code belongs to root and is read-only to PHP (only
application/filesandapplication/configare writable); PHP cannot run shell commands and is confined to Concrete's folders. MariaDB has no network listener at all (Concrete talks to it over a local socket) and itsrootaccount signs in only through the socket as the operating system's root. Sessions are kept in the database. nginx, PHP, MariaDB and the first-boot service are blocked from the Azure Instance Metadata Service and the WireServer (the Azure VM agent and cloud-init keep their access), requests for a host name the VM does not serve are dropped, and Concrete always uses the address you configure (its canonical URL), never one a request claims. - Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: Concrete CMS 9.5.7 (the official release, checksum verified, Elemental starting point), MariaDB 10.11, PHP 8.3 FPM, nginx, Ubuntu 24.04 LTS, cloud-init and the Azure Linux agent. The database, uploads and Concrete's configuration are on a separate 30 GiB Azure data disk mounted at /var/lib/concrete.
Default user: the administrator account you choose at deployment (azureuser in the examples). Architecture: x64 (Gen2). Recommended size: Standard_B2ms.
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 (Concrete 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.
Deploy from the Azure Marketplace
In the Azure Portal, choose Create a resource, search the Marketplace for Concrete CMS by SecureAnyCloud and select Create.
On Basics, pick your subscription, resource group, region and size (Standard_B2ms 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).
Keep the data disk on the Disks tab. It holds the database, uploads and configuration, and you can make it larger there. Then choose Review + create and Create.
With the Azure CLI (replace the values in angle brackets):
az vm image terms accept --urn sac1647283583153:concrete-cms-ubuntu-24-04:default:latest
az vm create \
--resource-group <your-rg> --name concrete-cms \
--image sac1647283583153:concrete-cms-ubuntu-24-04:default:latest \
--size Standard_B2ms \
--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 concrete-cms --port 80,443 --priority 1010Connect to your VM
Connect over SSH as the administrator account you chose at deployment:
ssh azureuser@<vm-public-ip>Check the services
First boot takes about a minute (it creates the database and runs Concrete's installer with the sample site). Then nginx, PHP, MariaDB, the VM address reader and the first-boot unit are active:
systemctl is-active nginx php8.3-fpm mariadb concrete-netinfo concrete-firstbootactive active 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:80 users:(("nginx"),("nginx"),("nginx")) LISTEN 0.0.0.0:22 users:(("sshd"),("systemd")) LISTEN 0.0.0.0:443 users:(("nginx"),("nginx"),("nginx")) LISTEN [::]:80 users:(("nginx"),("nginx"),("nginx")) LISTEN [::]:22 users:(("sshd"),("systemd")) LISTEN [::]:443 users:(("nginx"),("nginx"),("nginx"))The Concrete release, three of its bundled libraries (the Guzzle HTTP client, the Twig template engine and Concrete's own security patch set for them), the PHP and MariaDB versions and the data disk:
grep "'version' =>" /var/www/concrete/concrete/config/concrete.php | head -1 sudo jq -r '.packages[] | select(.name == "guzzlehttp/guzzle" or .name == "twig/twig" or .name == "concretecms/dependency-patches") | "\(.name) \(.version)"' /var/www/concrete/concrete/vendor/composer/installed.json php -v | head -1 mariadb --version findmnt -no TARGET,FSTYPE,SIZE /var/lib/concrete'version' => '9.5.7', concretecms/dependency-patches 1.9.0 guzzlehttp/guzzle 7.16.0 twig/twig v3.11.3 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/concrete ext4 29.4GTwig 3.11.3 is the version Concrete 9.5.7 bundles; Concrete's patch set 1.9.0 applies the fixes for its advisories in place (including CVE-2026-46633), and the remaining Twig advisories concern Twig's sandbox, which Concrete does not use.
Get your sign-in details
The credentials file holds the address Concrete is served as (your VM's public IP address by default), the sign-in page, the administrator login and password, and the database credentials, all generated on this VM. Show it with:
sudo cat /root/concrete-cms-credentials.txtCONCRETE_URL=https://<vm-public-ip>/ CONCRETE_LOGIN_URL=https://<vm-public-ip>/login CONCRETE_ADMIN_USER=admin CONCRETE_ADMIN_PASSWORD=<24 letters and digits> CONCRETE_DB_NAME=concrete CONCRETE_DB_USER=concrete CONCRETE_DB_PASSWORD=<48 hexadecimal characters>Visit the site and sign in
Open CONCRETE_URL in your browser. The certificate is self-signed (generated on your VM), so accept the browser warning once, or install your own certificate (Step 11). The Elemental sample site appears:
Open CONCRETE_LOGIN_URL and sign in with the user name admin and CONCRETE_ADMIN_PASSWORD:
admin is Concrete's super user, the one account that can always do everything. On the first sign in Concrete offers a short survey ("Tell us a little about your site") and a "learn the basics" panel; choose Skip and Got It! to close them. With the marketplace connection off, survey answers stay on your VM. Create a separate account for each editor under Dashboard > Members > Add User and keep admin for administration.
Edit pages in place
When you are signed in, every page shows the Concrete toolbar. Choose the pencil to put the page in edit mode. Each area and block is outlined.
Click a block to edit it, or drag a new one from the Add panel (the plus icon). Choose Publish Changes to make the new version live. Every page keeps its full version history.
The Dashboard (the sliders icon at the top right) manages the whole site:
Sitemap lists every page. Drag pages to move them, and add new pages from a page's menu:
Files > File Manager holds every uploaded image and document, with thumbnails, sets and folders:
Concrete's command-line tool runs from /var/www/concrete as the web server user. c5:info summarizes the installation:
cd /var/www/concrete && sudo -u www-data php concrete/bin/concrete c5:info | sed -n '/# Version/,/^$/p'# Version Installed - Yes Core Version - 9.5.7 Version Installed - 9.5.7 Database Version - 20261020120000The closed-by-default settings
The image's settings are written into Concrete's own configuration store (/var/lib/concrete/config/generated_overrides), the same place the Dashboard writes, so you can change each one in the Dashboard or from the shell. The settings in effect, as Concrete reads them:
cd /var/www/concrete && for k in concrete.security.production.mode concrete.user.registration.enabled concrete.marketplace.enabled concrete.updates.skip_core concrete.updates.services.get_available_updates concrete.urls.activity_slots concrete.white_label.background_image concrete.email.enabled concrete.session.handler; do printf '%s = %s\n' "$k" "$(sudo -u www-data php concrete/bin/concrete c5:config get "$k")"; doneconcrete.security.production.mode = production concrete.user.registration.enabled = false concrete.marketplace.enabled = false concrete.updates.skip_core = true concrete.updates.services.get_available_updates = concrete.urls.activity_slots = concrete.white_label.background_image = none concrete.email.enabled = false concrete.session.handler = databaseWhat each switch does, and how to turn it back on:
- Public registration is off (Dashboard > System & Settings > Login & Registration > Account Options). Create accounts under Dashboard > Members > Add User; if you open registration to the internet, require validation and a CAPTCHA first.
- Marketplace and updates. The marketplace connection, the add-on update checks and the core update check are off, and the update service address is empty, so the server never contacts concretecms.com. Upgrade from the shell instead (Step 12). To connect the site to the marketplace, set
concrete.marketplace.enabledtotrueand restoreconcrete.updates.services.get_available_updates(its default ishttps://www.concretecms.com/api/remote_update/update_core). - News, tutorials and backgrounds. The Dashboard's news and tutorial tiles (fetched by the server from concretecms.com) were removed from the Welcome page, the activity feed address is empty, and the sign-in page and Dashboard use no remote background image. Add an RSS Displayer block to the Welcome page (Customize) for a feed you choose.
- Geolocation. Concrete's geoPlugin geolocator, which sends visitors' addresses to geoplugin.net, is off (Dashboard > System & Settings > Environment > Geolocation).
- Email is off, because the image has no mail server; password reset links and form notifications are not sent. Configure your SMTP server under Dashboard > System & Settings > Email > SMTP Method and then turn email on. Until then, reset a forgotten password from the shell (Step 12).
For example, to set a value from the shell (-g writes it where the Dashboard does):
cd /var/www/concrete && sudo -u www-data php concrete/bin/concrete c5:config set -g concrete.email.enabled trueConcrete's own sign-in protection: the failed_login rule bans an address for 600 seconds after 5 failures in 300 seconds (Dashboard > System & Settings > Permissions & Access > IP Deny List), and the geolocator is inactive:
sudo mysql -N concrete -e "SELECT iaccHandle, iaccMaxEvents, iaccTimeWindow, iaccBanDuration, iaccEnabled FROM IpAccessControlCategories; SELECT glHandle, glActive FROM Geolocators"failed_login 5 300 600 1 forgot_password 2 30 600 1 geoplugin 0Add-ons. The packages folder belongs to root, so PHP cannot write code into the site. To install an add-on, copy it into /var/www/concrete/packages, give it to root (sudo chown -R root:root /var/www/concrete/packages) and install it with sudo -u www-data php concrete/bin/concrete c5:package:install <handle>; an add-on is PHP code that runs with Concrete's rights, so install only ones you trust.
Your data
Concrete's data is on its own Azure data disk at /var/lib/concrete: the MariaDB data directory (mysql, initialized on this VM's first boot), uploads (files, which the site serves as /application/files/) and Concrete's configuration (config, which it reads as application/config):
sudo ls -l /var/lib/concrete | sed -E 's/ +/ /g' | cut -d' ' -f1,3,4,9-total drwxr-x--- www-data www-data config drwxr-x--- www-data www-data files drwx------ root root lost+found drwxr-x--- mysql mysql mysql/var/lib/concrete/config/database.php was written by Concrete's installer on first boot and holds the database password. Keep it with your backups.
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 concrete, Concrete's own user with this VM'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"concrete@localhost mariadb.sys@localhost mysql@localhost root@localhost 1 /var/lib/concrete/mysql/Back the site up with Azure disk snapshots of the data disk and the OS disk, or as files:
sudo mysqldump --single-transaction concrete | gzip > concrete-db.sql.gz
sudo tar -czf concrete-files.tar.gz -C / var/lib/concrete/files var/lib/concrete/configSecurity checks
Port 80 sends browsers to HTTPS, the health endpoint answers for load balancer probes, and the installer, registration, the configuration, the command-line tool, the bundled libraries and the composer files are not reachable:
H=$(sed -n 's/^set \$cc_host "\(.*\)";$/\1/p' /etc/nginx/concrete-host.conf)
curl -s -o /dev/null -w 'port 80: %{http_code} -> %{redirect_url}\n' --resolve "$H:80:127.0.0.1" "http://$H/login"
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/healthz" for p in /install /register /application/config/database.php /concrete/bin/concrete /concrete/vendor/autoload.php /composer.json /updates/; 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>/login ok /install: 404 /register: 404 /application/config/database.php: 404 /concrete/bin/concrete: 404 /concrete/vendor/autoload.php: 404 /composer.json: 404 /updates/: 404 unknown host name: 000(000 means nginx closed the connection without an answer.) A file in application/files that is not a passive type, here the release's own placeholder page, is offered as a download with the sandbox policy, never shown as a page:
H=$(sed -n 's/^set \$cc_host "\(.*\)";$/\1/p' /etc/nginx/concrete-host.conf)
curl -sk -D - -o /dev/null --resolve "$H:443:127.0.0.1" "https://$H/application/files/index.html" | tr -d '\r' | grep -iE '^(HTTP|content-type|content-disposition|x-content-type|content-security)'HTTP/2 200 content-type: text/html 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, database.php by the web server only, the uploads and configuration folders by the web server, and the code (including packages) belongs to root:
sudo stat -c '%a %U:%G %n' /root/concrete-cms-credentials.txt /var/lib/concrete/config/database.php /var/lib/concrete/config /var/lib/concrete/files /var/www/concrete/packages /var/www/concrete/index.php600 root:root /root/concrete-cms-credentials.txt 600 www-data:www-data /var/lib/concrete/config/database.php 750 www-data:www-data /var/lib/concrete/config 750 www-data:www-data /var/lib/concrete/files 755 root:root /var/www/concrete/packages 644 root:root /var/www/concrete/index.phpThe certificate was generated on this VM and names its own addresses:
H=$(sed -n 's/^set \$cc_host "\(.*\)";$/\1/p' /etc/nginx/concrete-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), while the Azure VM agent and cloud-init keep their access; PHP cannot run shell commands and is confined to Concrete's folders; nginx runs only index.php, 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 and other requests:
for u in nginx php8.3-fpm mariadb concrete-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 (SCRIPT_FILENAME|HTTP_HOST|SERVER_NAME|HTTP_X_FORWARDED_FOR|HTTP_X_FORWARDED_HOST|HTTP_AUTHORIZATION) ' /etc/nginx/snippets/sac-concrete-php.conf grep -h 'limit_req_zone\|limit_req_status' /etc/nginx/conf.d/sac-concrete-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 concrete-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/concrete/:/var/lib/concrete/files/:/var/lib/concrete/config/:/var/lib/php/sessions/:/tmp/ fastcgi_param SCRIPT_FILENAME $document_root/index.php; fastcgi_param HTTP_HOST $cc_host; fastcgi_param SERVER_NAME $cc_host; fastcgi_param HTTP_X_FORWARDED_HOST ""; fastcgi_param HTTP_X_FORWARDED_FOR $remote_addr; fastcgi_param HTTP_AUTHORIZATION ""; limit_req_zone $cc_login_key zone=cc_login:10m rate=6r/m; limit_req_zone $cc_post_key zone=cc_post:10m rate=120r/m; limit_req_status 429;The cc_post limit allows 120 form submissions and editing requests a minute from one address (after a burst of the same size), which is plenty for editors; raise it in /etc/nginx/conf.d/sac-concrete-guard.conf if you need to. If a burst of wrong passwords locks you out (HTTP 429, or "your IP address has been banned"), wait ten minutes, or lift the ban under Dashboard > System & Settings > Permissions & Access > IP Deny List from another address.
sudo concrete-cred-roundtrip checks the whole sign-in path on the VM: the redirect to HTTPS, that no installer is offered and database.php is refused, a sign in through Concrete's login form with the password from the credentials file, and that wrong and common passwords are refused:
sudo concrete-cred-roundtripOK https 301 + no installer + database.php 404 + admin login + wrong and default passwords refusedCommand line tools
Concrete's command-line tool runs from /var/www/concrete as the web server user. A few useful commands:
cd /var/www/concrete
sudo -u www-data php concrete/bin/concrete list
sudo -u www-data php concrete/bin/concrete c5:clear-cache
sudo -u www-data php concrete/bin/concrete c5:jobNever put a password on a command line (it would be stored in your shell history and the system logs); use the helpers in Step 12.
Your own DNS name and certificate
Point a DNS record at the VM's public IP address, then set the name Concrete is served as. This mints a new self-signed certificate for the name, updates nginx, Concrete's canonical URL (used in links, redirects and password reset mails) and the addresses in /root/concrete-cms-credentials.txt, clears Concrete's cache and keeps the name across reboots:
sudo concrete-set-hostname <your-domain>To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running concrete-set-hostname again replaces it with a self-signed one):
sudo install -m 0644 <your-domain>.crt /etc/nginx/ssl/concrete.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/ssl/concrete.key
sudo nginx -t && sudo systemctl reload nginxOnce 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-concrete-headers.conf. The image does not send one: browsers ignore HSTS from a site whose certificate they do not trust.
Passwords and upgrades
concrete-set-password gives admin a new random password through Concrete's own user API, signs out every session and records the password in /root/concrete-cms-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 Account.
sudo concrete-set-password printf '%s\n' '<your-new-password>' | sudo concrete-set-password --stdinUpgrades. Concrete releases are announced on concretecms.org and on GitHub. Snapshot the OS disk and the data disk first. Then download the new release zip from https://github.com/concretecms/concretecms/releases and compare its SHA-256 with the release page.
Read the release notes, replace the concrete folder as root while keeping application and packages, and then run the database migrations:
python3 -m zipfile -e concrete-cms-9.5.<n>.zip /tmp
sudo systemctl stop nginx
sudo rsync -a --delete /tmp/concrete-cms-9.5.<n>/concrete/ /var/www/concrete/concrete/
sudo chown -R root:root /var/www/concrete/concrete cd /var/www/concrete && sudo -u www-data php concrete/bin/concrete c5:update
sudo -u www-data php concrete/bin/concrete c5:clear-cache
sudo systemctl start nginxServer components
The image keeps its components in these locations:
- Concrete code (root owned): /var/www/concrete
- Configuration (database connection, generated settings): /var/lib/concrete/config (application/config)
- Uploads, thumbnails and Concrete's cache: /var/lib/concrete/files (application/files)
- Database (MariaDB datadir): /var/lib/concrete/mysql (on the same Azure data disk, LUN 0)
- nginx site and request limits: /etc/nginx/sites-available/sac-concrete, /etc/nginx/conf.d/sac-concrete-guard.conf
- PHP settings: /etc/php/8.3/fpm/conf.d/99-sac-concrete.ini
- MariaDB settings: /etc/mysql/mariadb.conf.d/99-sac-concrete.cnf
- Certificate and key: /etc/nginx/ssl/
- First-boot script, VM address reader and SecureAnyCloud helpers: /usr/local/sbin/concrete-firstboot.sh, /usr/local/sbin/concrete-netinfo, /usr/local/lib/sac/concrete/
- Helpers: /usr/local/sbin/concrete-set-hostname, concrete-set-password, concrete-cred-roundtrip
- License, release and image notes: /usr/share/doc/concrete/
- Credentials: /root/concrete-cms-credentials.txt (mode 0600)
Security recommendations
- Allow port 22 in the network security group from your own networks only.
- Keep the
adminpassword 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
/var/lib/concrete/configsecret and backed up with the database (Step 8). - Think before opening public registration to the internet (Step 7).
- Use your own DNS name and a certificate your browsers trust (Step 11).
- Keep Concrete on the newest release (Step 12) and install only add-ons you trust (Step 7).
- 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 extension 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 disk regularly (Step 8).
Support and licensing
Concrete CMS is free software under the MIT license (a copy is at /usr/share/doc/concrete/LICENSE); the bundled libraries carry their own open source licenses, as listed in Concrete's composer metadata (concrete/vendor/composer/installed.json): mostly MIT and BSD, plus Apache 2.0, the SVG sanitiser under GPL 2.0 or later and htmLawed under LGPL 3.0 or GPL 2.0 or later. There is no per-user or per CPU license fee. This image installs the unmodified upstream release, without its vendored test suites and documentation, plus the SecureAnyCloud first boot, settings and helpers 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 Concrete CMS 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
Overview
This image runs Concrete CMS, the free, open-source content management system designed for teams that build and manage websites without writing code. Its standout feature is in-context editing. You edit pages directly on the live site, drag blocks of content onto the page and see changes instantly.
The dashboard adds a full sitemap, page versioning and approval, a file manager, reusable stacks, themes and a built-in marketplace of add-ons.
What you get:
- Concrete CMS installed under
/var/www/concreteas a standard LAMP application: PHP 8.3 with php-fpm, a MariaDB database, and nginx serving the website and the editing dashboard on port 80. - Systemd management of the database, the PHP FastCGI workers and the web server. Systemd starts them on boot and restarts them on failure.
- A content database at
/var/lib/mysql, on a dedicated, independently resizable EBS data volume. - An administrator password generated on the first boot of every deployed instance, so two instances launched from the same Amazon Machine Image never share a password. The password is written to
/root/concrete-cms-aws-credentials.txtwith mode 0600, so only the root user can read it. - A full sample site, seeded so you can explore the editing experience immediately.
Prerequisites
Before you deploy this image, make sure you have:
- An Amazon Web Services account where you can launch EC2 instances.
- IAM permissions to launch instances, create security groups, and subscribe to AWS Marketplace products.
- An EC2 key pair in the target Region for SSH access to the instance.
- A VPC and subnet in the target Region, with a security group that allows inbound port 22 from your management network and port 80 for the website and dashboard.
- The AWS CLI (version 2) installed locally, if you plan to deploy from the command line.
Launch the Instance from the AWS Marketplace
Sign in to the AWS Management Console, open the EC2 service, and select Launch instance. Under Application and OS Images, choose AWS Marketplace AMIs and search for Concrete CMS. Select the SecureAnyCloud listing and choose Select, then Continue on the subscription summary.
Pick an instance type of t3.medium or larger. Choose your EC2 key pair under Key pair (login). Under Network settings, select your VPC and subnet. Create or select a security group that opens port 22 from your management network and port 80 for the website and dashboard. Leave the root volume at the default size or larger.
Select Launch instance. First-boot initialization installs the per-instance database and administrator account. It takes a minute or two after the instance state becomes Running and the status checks pass.
Launch the Instance from the AWS CLI
The following command launches an instance from the SecureAnyCloud Concrete CMS Marketplace AMI into an existing subnet and security group. Replace <ami-id> with the AMI ID shown on the Marketplace listing and <key-name> with your EC2 key pair name. Replace <subnet-id> with your subnet ID and <security-group-id> with a security group that opens ports 22 and 80 as described above.
aws ec2 run-instances \
--image-id <ami-id> \
--instance-type t3.medium \
--key-name <key-name> \
--subnet-id <subnet-id> \
--security-group-ids <security-group-id> \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=concrete-cms}]'When the instance reaches the Running state and its status checks pass, note its public IP address or DNS name from the EC2 console or with aws ec2 describe-instances.
Connect to Your Instance
Connect over SSH using your key pair and the login user for your operating system variant. For Ubuntu 24.04, the login user is ubuntu.
ssh -i <key-name>.pem ubuntu@<public-ip>Retrieve the Administrator Password
The dashboard administrator password is unique to your instance and was generated on first boot. Read it as root:
sudo cat /root/concrete-cms-aws-credentials.txtThe file lists the website URL, the login URL, the administrator user name (admin) and the generated password. Keep this password somewhere safe.
Sign In to the Dashboard
The website and the editing dashboard are served on port 80 by nginx. In a browser, go to the login page:
http://<instance-public-ip>/index.php/loginSign in as admin with the password from the credentials file. After signing in you land on the dashboard, where the Welcome, Sitemap, Files, Members, Express, Boards, Reports and Pages & Themes sections give you everything you need to build and manage your site.
Edit a Page in Context
Concrete CMS lets you edit your website directly on the page. Browse to any page of your site, then open the page menu in the editing toolbar at the top of the screen and choose Edit This Page. The page reloads in edit mode with every content region outlined, so you can drag and drop blocks for text, images, galleries, forms and navigation, rearrange layouts, and publish with one click.
The seeded sample site demonstrates the standard building blocks: a sitewide header with the site title, navigation and search, an image slider, and a three-column Bootstrap layout. Click any block to edit its content, or use the Add menu in the toolbar to drop new blocks onto the page.
Organize Your Site with the Sitemap
Open Sitemap from the dashboard to see the full page tree. From here you add, move, copy and delete pages, manage page versions and approval, and configure page attributes and permissions. The seeded site ships with Home, Services, Portfolio, Team, Blog, Search and Contact pages so you can see how a real site is structured.
Confirm Concrete CMS Is Running
Over SSH, confirm the database, the PHP FastCGI workers and the nginx proxy are active and that port 80 is listening:
sudo systemctl is-active mariadb php8.3-fpm nginx
sudo ss -tlnp | grep -E ':80 'You should see all three services reported as active and nginx listening on port 80. Confirm the application answers on the loopback interface:
curl -s -o /dev/null -w 'login page: %{http_code}\n' http://127.0.0.1/index.php/loginA 200 confirms the login page is served. Check the installed Concrete CMS core version with the bundled console tool:
sudo -u www-data php /var/www/concrete/concrete/bin/concrete c5:info | grep -i 'Core Version'The Database Volume
The content database lives on a dedicated EBS volume mounted at /var/lib/mysql. This keeps the database off the operating system disk and lets you resize or snapshot it independently. Confirm the mount with:
df -h /var/lib/mysqlTo grow the database store, expand the EBS volume in the AWS console, then grow the filesystem on the instance with sudo resize2fs on the underlying device. The MariaDB server listens on the loopback interface only and is never exposed to the network.
Manage Files, Themes and Add-ons
From the dashboard, open Files to use the file manager for uploading and organizing images and documents, Pages & Themes to install and activate themes and switch the look of your site, and Extend Concrete to browse and install add-ons from the Concrete CMS marketplace. Reusable Stacks & Blocks let you define content once and place it across many pages.
Add a Custom Domain and Enable HTTPS
First boot pins the site canonical URL to your instance public IP, so links and assets resolve correctly out of the box. To serve the site on your own domain, point a DNS record at the instance, or at a load balancer in front of it. Then update the canonical URL in the dashboard under System & Settings, SEO & Statistics, URLs and Redirection.
The website is served over plain HTTP on port 80 by nginx. For production use, place it behind TLS. Obtain a certificate for your domain, for example a managed certificate on an Application Load Balancer in front of the instance, or Certbot installed on the instance.
Then configure nginx to listen on 443 with your certificate and proxy to the Concrete CMS front controller, exactly as the bundled site does for port 80. Restrict the security group so ports 80 and 443 are reachable only from the networks that need them.
Backup and Maintenance
Back up your site by snapshotting the /var/lib/mysql EBS volume, which captures the entire content database, together with the uploaded files under /var/www/concrete/application/files. Apply operating system security updates with sudo apt-get update && sudo apt-get upgrade, and reboot when a new kernel is installed. MariaDB, php-fpm and nginx start automatically on boot.
Update Concrete CMS itself from the dashboard, or by replacing the application code under /var/www/concrete as described in the Concrete CMS upgrade documentation.
Support
This image is published and supported by SecureAnyCloud. Support covers deployment, theme and add-on installation, page and block editing, user and permission management, the file manager, database tuning, TLS and scaling. Contact SecureAnyCloud through the support channel listed on the AWS Marketplace listing.
All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.
Deploy on AWS
Launch Concrete CMS on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com