Applications AWS

FlatPress Blog and CMS 1.5.1 on AWS User Guide

| Product: FlatPress Blog and CMS 1.5.1 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the FlatPress Blog and CMS 1.5.1 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.

FlatPress is an open source blog engine and small CMS that needs no database. Posts, static pages, comments, settings and the administrator account are plain files, so the whole blog is one folder you can back up and move with ordinary file tools. You write posts with a BBCode editor, add static pages such as About or Contact, moderate comments, arrange sidebar widgets and switch themes from a browser based administration area.

The cloudimg image adds:

  • No setup wizard, no default login. Out of the box FlatPress offers a web setup wizard that creates the administrator on the first visit to a new site. This image removes the wizard. Your instance's first boot sets FlatPress up before the web server starts, with an administrator password, a blog id and a hash salt generated on your instance; the password is recorded in /root/flatpress-credentials.txt. Nothing from the image build ships in the image.
  • Security fixes released after 1.5.1, already applied. FlatPress 1.5.1 is the newest release. The FlatPress project has since fixed, for its next release, a comment and contact form script injection that an anonymous visitor could use, a way to read unpublished drafts through a crafted page address, and two BBCode script injection advisories (GHSA-26cp-8p3v-5f66 and GHSA-q7fj-xg8x-4qxx). This image applies those upstream fixes to 1.5.1, unchanged, and documents them in /usr/share/doc/flatpress/MODIFICATIONS.
  • HTTPS from the first boot. nginx serves FlatPress on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
  • Fail closed. nginx and PHP refuse to start until first boot has finished.
  • The blog data folder is never served. FlatPress relies on Apache .htaccess rules that nginx ignores; this image replaces them. The fp-content folder (settings, the administrator's password hash, the hash salt, entries, comments and caches) is never served, except uploaded images and files, which are served as static files with a policy that stops an uploaded HTML or SVG file from running script. PHP runs only through FlatPress's own entry points; library, theme, plugin and uploaded PHP files are never run directly.
  • Failed-login lockout. FlatPress itself only slows down repeated failures within one browser session. On this image ten failed sign ins from one address within 15 minutes block that address for five minutes. The address comes from the connection, never from a header a client sends, and FlatPress records commenter addresses the same way.
  • Hardened defaults. The FlatPress code is owned by root and read only to PHP; PHP can write only the fp-content folder, cannot run shell commands and is confined to FlatPress's folders. nginx and PHP are blocked from the instance metadata service, the AMI requires IMDSv2, requests for a host name the instance does not serve are dropped, and FlatPress's links always use the host name you configure, never the one a request claims.
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: FlatPress 1.5.1 (the upstream release, checksum verified, with the security fixes above) with its Leggero theme and standard plugins, PHP 8.3 FPM, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent. First boot creates a welcome post and the Menu, About and Privacy Policy static pages.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 (SSH), TCP 443 (the blog and its administration area) and TCP 80 (redirects to HTTPS) from the networks that should reach them. A public blog needs 443 and 80 open to everyone; keep 22 to your own networks.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick FlatPress Blog and CMS 1.5.1 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (FlatPress is very light; a smaller instance is fine for a small blog), 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=flatpress}]'

Step 2: Connect to your instance

Version SSH user
FlatPress Blog and CMS 1.5.1 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot takes a few seconds. Then nginx, PHP and the first boot unit are active:

systemctl is-active nginx php8.3-fpm flatpress-firstboot
active
active
active

nginx serves ports 80 and 443; PHP listens on a local socket only, and there is no database, so the only open ports are 22, 80 and 443:

sudo ss -tlnp | awk 'NR==1 || /nginx|sshd|php/' | 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"))
grep -oE "define\('SYSTEM_VER', '[0-9.]+'\)" /var/www/flatpress/fp-includes/core/core.system.php
php -v | head -1
define('SYSTEM_VER', '1.5.1')
PHP 8.3.6 (cli) (built: Oct 19 2026 15:16:58) (NTS)

Step 4: Get your sign-in details

The credentials file holds the address the blog is served as (your instance's public DNS name by default), the sign-in page, the administrator user name and the password generated on this instance. Show it with:

sudo cat /root/flatpress-credentials.txt
FLATPRESS_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
FLATPRESS_LOGIN_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/login.php
FLATPRESS_ADMIN_USER=admin
FLATPRESS_ADMIN_PASSWORD=<24 letters and digits>

Step 5: Visit your blog and sign in

Open FLATPRESS_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). Visitors see the welcome post in the Leggero theme:

The FlatPress blog in the Leggero theme with the Welcome to your FlatPress blog post and the Admin, Menu, Categories, Archives, Last entries, Search and Subscribe widgets

Open FLATPRESS_LOGIN_URL (or Log in in the sidebar) and sign in as admin with FLATPRESS_ADMIN_PASSWORD:

The FlatPress login form with the Username and Password fields and the Login button

Step 6: The administration area

Signed in, you land on the administration area. Its tabs are Entries (write and manage posts, categories, the spam word filter and the Comment Center), Statics (static pages), Uploader (images and files), Widgets, Plugins, Themes, Configuration (blog title, subtitle, footer, author, language and date format) and Maintain.

The FlatPress administration area with the Main, Entries, Statics, Uploader, Widgets, Plugins, Themes, Configuration and Maintain tabs and the New Entry, Entries, Comment Center and Uploader actions

Step 7: Write your first post

Open Entries > Write Entry, give the post a subject, write the content with the BBCode toolbar (links, headings, lists, quotes, code, emoticons, files and images from the Uploader), choose categories and Publish. The post appears on the blog at once, stored as a plain file.

The FlatPress Write Entry form with the publication date, the subject My first post, the BBCode toolbar and the File, Image and Gallery pickers

Configuration sets the blog title and subtitle (the image starts with "My FlatPress Blog"), the author name and the email address FlatPress shows; change them to your own.

Step 8: Static pages, comments, widgets and plugins

Statics lists the static pages. The Menu page is the sidebar menu (edit it to add links) and About and Privacy Policy are ordinary pages you can rewrite or delete.

The FlatPress Statics tab listing the About, Menu and Privacy Policy static pages

Readers can comment on posts. Anonymous comments answer a small arithmetic question, comments that contain links are refused by the quick spam filter (Entries > QuickSpamFilter, where you can change its word list), and new comments wait in the Comment Center until you approve them. Plugins enables and disables FlatPress's standard plugins:

The FlatPress Plugins tab listing the standard plugins with their Disable and Enable actions

The notice about .htaccess on that page is expected: this image serves FlatPress through nginx, which does not use .htaccess files, and the PrettyURLs plugin is set to its query string mode (addresses such as /?u=/2026/10/25/my-first-post/), which needs no rewrite rules.

Email: FlatPress sends comment notifications and contact form messages with PHP's mail(). The image has no mail server, so these are not delivered until you install and configure a mail transfer agent that relays through your mail provider.

Step 9: Your blog data

Everything FlatPress stores lives in one folder, fp-content, which only the web server can read; the code around it belongs to root:

sudo ls -l /var/www/flatpress /var/www/flatpress/fp-content | sed -E 's/ +/ /g' | cut -d' ' -f1,3,4,9-
/var/www/flatpress:
total
drwxr-xr-x root root admin
-rw-r--r-- root root admin.php
-rw-r--r-- root root blog.php
-rw-r--r-- root root comments.php
-rw-r--r-- root root contact.php
-rw-r--r-- root root defaults.php
drwxr-x--- www-data www-data fp-content
drwxr-xr-x root root fp-defaults
drwxr-xr-x root root fp-includes
drwxr-xr-x root root fp-interface
drwxr-xr-x root root fp-plugins
-rw-r--r-- root root get.php
-rw-r--r-- root root index.php
-rw-r--r-- root root login.php
-rw-r--r-- root root rss.php
-rw-r--r-- root root search.php
-rw-r--r-- root root sitemap.php
-rw-r--r-- root root static.php

/var/www/flatpress/fp-content:
total
-rw-r--r-- www-data www-data %%setup.lock
drwxr-xr-x www-data www-data attachs
drwxr-xr-x www-data www-data cache
drwxr-xr-x www-data www-data compile
drwxr-xr-x www-data www-data config
drwxr-xr-x www-data www-data content
drwxr-xr-x www-data www-data images
drwxr-xr-x www-data www-data index
drwxr-xr-x www-data www-data plugin_commentcenter
drwxr-xr-x www-data www-data users

config holds the settings and the hash salt, users the administrator record with its password hash, content the posts, comments and static pages, images and attachs your uploads, and the login lockout keeps its counts in cloudimg-login-guard.json. Back the blog up by copying the folder (sudo tar -C /var/www/flatpress -czf flatpress-data.tar.gz fp-content) or with an EBS snapshot of the instance's volume. To restore onto a new instance, let its first boot finish, stop nginx and PHP, replace its fp-content with your copy (owned by www-data) and start them again.

Step 10: Security checks

Port 80 sends browsers to HTTPS, the health endpoint answers for load balancer checks, and the setup wizard, the account and settings files, FlatPress's library code and a crafted page address are not reachable:

H=$(sed -n 's/^set \$fp_host "\(.*\)";$/\1/p' /etc/nginx/flatpress-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.php"
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/healthz"
for p in /setup.php /fp-content/users/admin.php /fp-content/config/settings.conf.php /fp-content/config/hashsalt.conf.php /fp-includes/core/core.users.php "/?page=../config/settings.conf"; 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/login.php
ok
/setup.php: 404
/fp-content/users/admin.php: 404
/fp-content/config/settings.conf.php: 404
/fp-content/config/hashsalt.conf.php: 404
/fp-includes/core/core.users.php: 404
/?page=../config/settings.conf: 404
unknown host name: 000

(000 means nginx closed the connection without an answer.) FlatPress has one account, its password is stored only as a bcrypt hash, the blog id is your instance's own and FlatPress builds its links from the configured HTTPS address:

sudo -u www-data php /usr/local/lib/cloudimg/flatpress/flatpress-admin.php summary
www=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
users=admin
hashAlgo=bcrypt
lock=yes
theme=leggero
bbcodeComments=on
blogidDefault=no

The credentials file is readable by root only, the blog data folder by the web server only, and the code is owned by root:

sudo stat -c '%a %U:%G %n' /root/flatpress-credentials.txt /var/www/flatpress/fp-content /var/www/flatpress/index.php
600 root:root /root/flatpress-credentials.txt
750 www-data:www-data /var/www/flatpress/fp-content
644 root:root /var/www/flatpress/index.php

The certificate was generated on this instance and names its own addresses:

H=$(sed -n 's/^set \$fp_host "\(.*\)";$/\1/p' /etc/nginx/flatpress-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 and PHP cannot reach the instance metadata service, PHP cannot run shell commands, is confined to FlatPress's folders and runs the login guard before every request, and nginx hands PHP the configured host name and the real client address from the connection, never values a client sends:

systemctl show nginx -p IPAddressDeny
systemctl show php8.3-fpm -p IPAddressDeny
php-fpm8.3 -i 2>/dev/null | grep -E '^(open_basedir|disable_functions|expose_php|auto_prepend_file) ' | cut -d' ' -f1-3
grep -E 'fastcgi_param (HTTP_HOST|SERVER_NAME|HTTP_X_FORWARDED_FOR|HTTP_CLIENT_IP) ' /etc/nginx/snippets/cloudimg-flatpress-php.conf
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
IPAddressDeny=169.254.169.254/32 fd00:ec2::254/128 168.63.129.16/32
auto_prepend_file => /usr/local/lib/cloudimg/flatpress/login-guard.php
disable_functions => exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec
expose_php => Off
open_basedir => /var/www/flatpress/:/usr/local/lib/cloudimg/flatpress/:/var/lib/php/sessions/:/tmp/
fastcgi_param HTTP_HOST $fp_host;
fastcgi_param SERVER_NAME $fp_host;
fastcgi_param HTTP_X_FORWARDED_FOR $remote_addr;
fastcgi_param HTTP_CLIENT_IP $remote_addr;

The upstream security fixes applied to 1.5.1 are listed with their FlatPress commit ids, and each changed file carries a dated notice (the full diff is /usr/share/doc/flatpress/cloudimg-security-backport.diff):

sed -n '1,9p' /usr/share/doc/flatpress/MODIFICATIONS
grep -rlF --exclude-dir=fp-content 'cloudimg: modified 2026-10-24' /var/www/flatpress | wc -l
FlatPress 1.5.1 as shipped in this cloudimg image is MODIFIED (GPL-2.0-only, section 2(a)).

Date of change: 2026-10-24. Changed by: cloudimg.
What: the upstream FlatPress security fixes merged to master after the 1.5.1 release were backported verbatim:
  b6318487a15a3f3eaa886e98451c21df0081471e (PR #866) comment/contact form URL and name escaping (stored XSS)
  1db538c549f51dda9e7c0fb594b8312a6f84d0ac (PR #936) static page id validation (drafts disclosed by path traversal)
  350077232355369438f8813673b5c1cc6365c102 (PR #941, GHSA-26cp-8p3v-5f66) BBCode [video] attribute escaping
  ff5ab4ffe0473e14024d158cb9eadc749b8fb49d (PR #945, GHSA-q7fj-xg8x-4qxx) BBCode [color]/[size]/[align] escaping
The combined diff is cloudimg-security-backport.diff in this directory. Files changed:
11

If you lock yourself out with ten wrong passwords, wait five minutes or clear the lockout:

sudo flatpress-reset-lockout

Step 11: Your own DNS name and certificate

Point a DNS record at the instance, then set the name FlatPress is served as. This mints a new self signed certificate for the name, updates nginx and FlatPress's blog address (every link FlatPress builds) and the URLs in /root/flatpress-credentials.txt, and keeps the name across reboots:

sudo flatpress-set-hostname <your-domain>

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

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

Step 12: Password and upgrades

flatpress-set-password gives the administrator a new random password, signs out every open session and records the password in /root/flatpress-credentials.txt. Run it without arguments, or pass your own password (24 to 64 letters and digits) on stdin:

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

The FlatPress code is read only to PHP, so plugins and themes cannot be added from the browser on this image; FlatPress installs them by copying folders into fp-plugins and fp-interface/themes, which you can do over SSH (sudo cp -r <plugin> /var/www/flatpress/fp-plugins/ keeps them owned by root). To upgrade an existing instance to a later FlatPress release, snapshot it, follow FlatPress's upgrade notes and copy the new code over everything except fp-content. Your blog is untouched, because it lives in fp-content.

Step 13: Server components

Component Path
FlatPress code (root owned) /var/www/flatpress
Blog data (settings, account, posts, comments, uploads) /var/www/flatpress/fp-content
nginx site /etc/nginx/sites-available/cloudimg-flatpress
PHP settings /etc/php/8.3/fpm/conf.d/99-cloudimg-flatpress.ini
Login guard /usr/local/lib/cloudimg/flatpress/login-guard.php
Certificate and key /etc/nginx/ssl/
First boot script /usr/local/sbin/flatpress-firstboot.sh
Helpers /usr/local/sbin/flatpress-set-hostname, /usr/local/sbin/flatpress-set-password, /usr/local/sbin/flatpress-reset-lockout
Licence, release notes and the security diff /usr/share/doc/flatpress/
Credentials /root/flatpress-credentials.txt (mode 0600)

Step 14: Security recommendations

  • Allow port 22 in the security group from your own networks only.
  • Keep the administrator password secret, and rotate it if it may have been shared (Step 12).
  • Use your own DNS name and a certificate your browsers trust (Step 11).
  • Keep the quick spam filter and comment moderation on unless you trust every commenter.
  • Keep the operating system patched; unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else. PHP-FPM and the PHP intl 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).
  • Back up the fp-content folder regularly (Step 9).

Support and licensing

FlatPress is open source under the GNU General Public License version 2 (a copy is at /usr/share/doc/flatpress/LICENSE.md); there is no per user or per CPU licence fee. The FlatPress code in this image is modified as described in /usr/share/doc/flatpress/MODIFICATIONS: the upstream security fixes listed there are applied and the setup wizard is removed. 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