Applications AWS

phpMyFAQ 4.1.11 on AWS User Guide

| Product: phpMyFAQ 4.1.11 Knowledge Base on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the phpMyFAQ 4.1.11 Knowledge Base on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg CMS, Blogs and Wikis listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

phpMyFAQ is a free and open source FAQ and knowledge base system written in PHP: categories, FAQ entries with revisions and a WYSIWYG editor, open questions from visitors, a glossary, news, comments and ratings, full text search, PDF export of every entry, user and group permissions, and a REST API. The public site is at / and the admin panel at /admin/.

The cloudimg image adds:

  • No reachable setup wizard, no shared database. phpMyFAQ's browser setup creates the database and the first administrator for whoever opens it first. This image ships no database at all (the MariaDB data directory is empty) and no database configuration. Your instance's first boot initialises its own database and runs phpMyFAQ's own setup wizard before the web server starts: over a temporary server on a random local port that answers only requests carrying a token generated for that boot. First boot then stops that server; the setup and update pages are never served on ports 80 and 443.
  • Secrets of your instance's own, never on a command line. The database password, the administrator password, phpMyFAQ's password reset key and API token and the TLS key are generated on your instance; nothing from the image build is reused. The setup form, which carries the passwords, is handed over in a file only root can read, never as a command line argument, and its reply is never logged.
  • The newest stable release. phpMyFAQ 4.1.11, downloaded from the project's own download site and checked against a pinned SHA-256 checksum. It contains every fix published for the 4.1 line up to 4.1.11, including the passkey account takeover, glossary SQL injection and password reset token flaws fixed in 4.1.7 to 4.1.11.
  • Closed by default. Public registration is off (Step 7 shows how to open it), and phpMyFAQ makes no calls home: the update hint and the online updater, which contact phpmyfaq.de, are switched off, and the version, update check and package download routes are not available (Step 7). Microsoft Entra ID sign in is not available (Step 7 explains why).
  • HTTPS from the first boot. nginx serves phpMyFAQ on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS, and the instance's other addresses (its IP addresses) redirect to the one host name phpMyFAQ is configured with, so links, the admin sign in form and cookies always match the address in your browser.
  • Fail closed. nginx does not start until first boot has finished; if first boot cannot complete, the site stays offline rather than open.
  • Only phpMyFAQ's own entry points run PHP. Pages go through index.php (the friendly page addresses are rewritten onto it), the admin panel through admin/index.php and the APIs through api/index.php; every other PHP file is never run, and the setup, update, src, translations and multisite folders, the configuration and the attachment store are never served. The image folder PHP writes uploaded images to is static only: every file gets 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.
  • Rate-limited sign in and password reset. On top of phpMyFAQ's own lockout (five failed sign ins lock an account for ten minutes), nginx allows 10 sign-in, two factor, passkey, password reset, registration and guest submission attempts a minute from one address (after a burst of 10), whatever form of the address is used, and 120 public REST API requests a minute, using the address of the connection, never a header a client sends.
  • Links from other sites cannot act for you. The admin panel's background requests (/admin/api/...) and the backup export only work when they come from your site itself (Step 9).
  • The real host and client address. nginx hands phpMyFAQ the host name you configure and nothing a request claims, and phpMyFAQ's reference URL is pinned to it, so every link, password reset e-mails included, uses your host name. nginx removes X-Forwarded-For, Client-IP and the other forwarding headers, and phpMyFAQ records the address of the connection.
  • Hardened defaults. The phpMyFAQ code belongs to root and is read only to PHP, the database configuration file is read only to PHP (and the cloudimg tools read it as data, never as code), and only the data, log, image and attachment folders are writable. PHP cannot run shell commands and is confined to the site folder. MariaDB has no network listener at all (phpMyFAQ 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: phpMyFAQ 4.1.11 (the upstream release, checksum verified), MariaDB 10.11, PHP 8.3 FPM, nginx 1.24, 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 (phpMyFAQ) and TCP 80 (redirects to HTTPS) from the networks that should reach them. Keep 22 to your own networks. Open 443 to your readers for a public knowledge base, or only to your team for an internal one.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick phpMyFAQ 4.1.11 Knowledge Base 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=phpmyfaq}]'

Step 2: Connect to your instance

Version SSH user
phpMyFAQ 4.1.11 Knowledge Base 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 phpMyFAQ's setup wizard). Then nginx, PHP, MariaDB and the first boot unit are active:

systemctl is-active nginx php8.3-fpm mariadb phpmyfaq-firstboot
active
active
active
active

nginx serves ports 80 and 443; PHP and MariaDB listen on local sockets only, and the setup wizard's temporary server is gone, 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 phpMyFAQ release and the PHP, MariaDB and nginx versions:

grep -E 'VERSION_(MAJOR|MINOR|PATCH_LEVEL) =' /var/www/phpmyfaq/src/phpMyFAQ/System.php | sed 's/^ *//'
php -v | head -1
mariadb --version
nginx -v
private const int VERSION_MAJOR = 4;
private const int VERSION_MINOR = 1;
private const int VERSION_PATCH_LEVEL = 11;
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
nginx version: nginx/1.24.0 (Ubuntu)

How the download was verified:

cat /usr/share/doc/phpmyfaq/RELEASE
phpMyFAQ 4.1.11
source https://download.phpmyfaq.de/phpMyFAQ-4.1.11.tar.gz
sha256 874342f7098aeb6e14af5ba3bd919a377f851ecdadeb90e7cb573f0a9330a658
license MPL-2.0 (https://raw.githubusercontent.com/thorsten/phpMyFAQ/4.1.11/LICENSE, sha256 66a3107d5ad6a058aab753eaac2047ccb2ed0e39465dd0fe5844da3e300d5172)
appledouble APPLEDOUBLE_SKIPPED=7982 KEPT=7982

The last line records that the release archive's macOS metadata files (._<name>, one beside every file) were left out when it was unpacked.

Step 4: Get your sign-in details

The credentials file holds the address phpMyFAQ is served as (your instance's public DNS name by default), the admin panel address, the administrator's user name, password and e-mail, and the database credentials, all generated on this instance. Show it with:

sudo cat /root/phpmyfaq-credentials.txt
PHPMYFAQ_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
PHPMYFAQ_ADMIN_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/admin/
PHPMYFAQ_ADMIN_USER=admin
PHPMYFAQ_ADMIN_PASSWORD=<24 letters and digits>
PHPMYFAQ_ADMIN_EMAIL=admin@example.com
PHPMYFAQ_DB_NAME=phpmyfaq
PHPMYFAQ_DB_USER=phpmyfaq
PHPMYFAQ_DB_PASSWORD=<48 hexadecimal characters>

The administrator's e-mail starts as admin@example.com; change it to your own address in the admin panel (your user menu at the top right, then your profile) before you rely on password reset e-mails.

Step 5: Visit the site and sign in

Open PHPMYFAQ_URL in your browser. The certificate is self signed (generated on your instance), so your browser warns once: compare the certificate's SHA-256 fingerprint with the one on the instance (sudo openssl x509 -in /etc/nginx/ssl/phpmyfaq.crt -noout -fingerprint -sha256) and continue, or install a certificate from your own certificate authority (Step 11). A fresh site shows phpMyFAQ's default theme with the search box:

The phpMyFAQ public start page of a fresh knowledge base with the search box

Open PHPMYFAQ_ADMIN_URL and sign in with PHPMYFAQ_ADMIN_USER and PHPMYFAQ_ADMIN_PASSWORD:

The phpMyFAQ admin sign in page with the Username and Password fields

The admin panel opens on its dashboard, with the menu on the left:

The phpMyFAQ admin dashboard with visit, article and user statistics and the sidebar menu

The administrator can do everything. For editors, create accounts under Users and give them, or a group, only the rights they need.

Step 6: Categories, FAQs and the editor

FAQ entries live in categories: create the first one under Content > Categories > Add new category. Then write an entry under Content > Add new FAQ: the question, the answer in the built-in Jodit editor, the category and the status (published or not published), and Save:

The phpMyFAQ FAQ editor with a question, an answer with bold text and a list, and the published status

A published entry appears in its category on the public site, with a PDF version, sharing, bookmarks and ratings:

A published phpMyFAQ entry showing its formatting, breadcrumb and the entry details

Content > FAQ Administration lists every entry, by category, with its status:

The phpMyFAQ FAQ administration list

Visitors can suggest new entries (Add new FAQ on the public site) and ask open questions; suggestions are stored not published until an editor reviews them, and phpMyFAQ removes scripts, event handler attributes and javascript: links from the suggested text on the server. Switch guest suggestions and questions off under Configuration > Edit configuration > Records if you do not want them.

Step 7: The closed-by-default settings

Setting Image default Where to change it
Public registration (security.enableRegistration) off Configuration > Edit configuration > Security
Update hint (main.enableAutoUpdateHint) and online updater (upgrade.onlineUpdateEnabled) off; the version, update check and package routes are not available (they contact phpmyfaq.de and would write the code folder) upgrade from the shell (Step 12)
Reference URL (main.referenceURL) https://<your host>/ sudo phpmyfaq-set-hostname (Step 11)
Guest FAQ suggestions and questions on, stored unpublished for review Configuration > Edit configuration > Records
REST API (api.enableAccess) on, rate limited Configuration > Edit configuration > API
Microsoft Entra ID sign in not available (/services/azure/ answers 404) see below
sudo mysql -N -B --raw phpmyfaq -e "SELECT config_name, config_value FROM faqconfig WHERE config_name IN ('main.referenceURL','security.enableRegistration','main.enableAutoUpdateHint','upgrade.onlineUpdateEnabled','api.enableAccess','records.allowNewFaqsForGuests','records.allowQuestionsForGuests') ORDER BY config_name" | sed -E 's#https://[^/]+/#https://<your host>/#'
api.enableAccess    true
main.enableAutoUpdateHint   false
main.referenceURL   https://<your host>/
records.allowNewFaqsForGuests   true
records.allowQuestionsForGuests true
security.enableRegistration false
upgrade.onlineUpdateEnabled false

Microsoft Entra ID. phpMyFAQ 4.1 links an Entra ID sign in to a local account by a name the identity provider lets users change (advisory GHSA-6hhv-gc4g-f4r7), which could let a colleague's Entra identity take over a local account. The fix is in phpMyFAQ 4.2, which is not yet a stable release, so the image serves no Entra ID sign in. Do not enable it on 4.1; use phpMyFAQ's own accounts (with two factor authentication) or LDAP.

The admin panel also sends a content security policy that lets its pages connect only to your own server:

H=$(sed -n 's/^set \$pmf_host "\(.*\)";$/\1/p' /etc/nginx/phpmyfaq-host.conf)
curl -sk -D - -o /dev/null --resolve "$H:443:127.0.0.1" "https://$H/admin/" | tr -d '\r' | grep -i '^content-security-policy'
content-security-policy: connect-src 'self'; frame-ancestors 'self'

Step 8: Your data

Everything that makes your knowledge base lives in the phpmyfaq database in MariaDB, the uploaded images under /var/www/phpmyfaq/content/user/images and the attachments under /var/www/phpmyfaq/content/user/attachments. Keep content/core/config/database.php with your backup: it holds this site's database password.

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 phpmyfaq > /root/phpmyfaq-$(date +%F).sql'
sudo tar -C /var/www/phpmyfaq -czf /root/phpmyfaq-files-$(date +%F).tgz content/core/config content/user

phpMyFAQ's own Backup page in the admin panel exports the data and the logs too. For a whole-instance copy, snapshot the EBS volume.

Step 9: Security checks

Port 80 and the instance's IP address send browsers to the configured HTTPS host, the health endpoint answers for load balancer checks, and the setup wizard, the updater, the setup API, the configuration, the attachment store, Entra ID sign in, the update check, the source code, the page include files and stray PHP files in the image folder are not reachable:

H=$(sed -n 's/^set \$pmf_host "\(.*\)";$/\1/p' /etc/nginx/phpmyfaq-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/" | sed -E 's#https://[^/]+#https://<your host>#'
IP=$(hostname -I | awk '{print $1}'); curl -sk -o /dev/null -w 'by IP address: %{http_code} -> %{redirect_url}\n' "https://$IP/admin/" | sed -E 's#https://[^/]+#https://<your host>#'
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/healthz"
for p in /setup/ /update/ /api/setup/backup /content/core/config/database.php /content/user/attachments/ /services/azure/ /admin/api/update-check /src/Bootstrap.php /login.php /content/user/images/x.php; do
  curl -sk -o /dev/null -w "$p: %{http_code}\n" -H 'Sec-Fetch-Site: same-origin' --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://<your host>/admin/
by IP address: 301 -> https://<your host>/admin/
ok
/setup/: 404
/update/: 404
/api/setup/backup: 404
/content/core/config/database.php: 404
/content/user/attachments/: 404
/services/azure/: 404
/admin/api/update-check: 404
/src/Bootstrap.php: 404
/login.php: 404
/content/user/images/x.php: 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, the image, attachment and data folders belong to the web server, and the code belongs to root:

sudo stat -c '%a %U:%G %n' /root/phpmyfaq-credentials.txt /var/www/phpmyfaq/content/core/config/database.php /var/www/phpmyfaq/content/core/config /var/www/phpmyfaq/content/user/images /var/www/phpmyfaq/content/user/attachments /var/www/phpmyfaq/content/core/data /var/www/phpmyfaq/index.php
600 root:root /root/phpmyfaq-credentials.txt
640 root:www-data /var/www/phpmyfaq/content/core/config/database.php
750 root:www-data /var/www/phpmyfaq/content/core/config
750 www-data:www-data /var/www/phpmyfaq/content/user/images
750 www-data:www-data /var/www/phpmyfaq/content/user/attachments
750 www-data:www-data /var/www/phpmyfaq/content/core/data
755 root:root /var/www/phpmyfaq/index.php

An HTML file in the image folder is sent as a download with nosniff and the sandboxing policy, never rendered as a page of your site:

H=$(sed -n 's/^set \$pmf_host "\(.*\)";$/\1/p' /etc/nginx/phpmyfaq-host.conf)
echo '<b>demo</b>' | sudo -u www-data tee /var/www/phpmyfaq/content/user/images/guide-demo.html >/dev/null
curl -sk -D - -o /dev/null --resolve "$H:443:127.0.0.1" "https://$H/content/user/images/guide-demo.html" | tr -d '\r' | grep -iE '^(HTTP|content-disposition|content-security-policy|x-content-type)'
sudo -u www-data rm -f /var/www/phpmyfaq/content/user/images/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, names its own addresses and can only vouch for itself (it is not a certificate authority):

H=$(sed -n 's/^set \$pmf_host "\(.*\)";$/\1/p' /etc/nginx/phpmyfaq-host.conf)
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -ext basicConstraints,extendedKeyUsage
X509v3 Basic Constraints: critical
    CA:FALSE
X509v3 Extended Key Usage:
    TLS Web Server Authentication

nginx, PHP, MariaDB and the first boot service (and the setup wizard's temporary server, which ran inside it) 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, removes forwarding headers and any Authorization header a client sends, and rate-limits requests:

for u in nginx php8.3-fpm mariadb phpmyfaq-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|HTTP_X_FORWARDED_FOR|HTTP_CLIENT_IP|HTTP_X_FORWARDED_HOST|HTTP_AUTHORIZATION) ' /etc/nginx/snippets/cloudimg-phpmyfaq-fastcgi.conf
grep -h 'limit_req_zone\|limit_req_status' /etc/nginx/conf.d/cloudimg-phpmyfaq-guard.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
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
disable_functions => exec,passthru,shell_exec,system,proc_open,popen,pcntl_exec
expose_php => Off
open_basedir => /var/www/phpmyfaq/:/var/lib/php/sessions/:/tmp/
fastcgi_param HTTP_HOST $pmf_host;
fastcgi_param SERVER_NAME $pmf_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 $pmf_login_key zone=pmf_login:10m rate=10r/m;
limit_req_zone $pmf_post_key zone=pmf_post:10m rate=60r/m;
limit_req_zone $pmf_api_key zone=pmf_api:10m rate=120r/m;
limit_req_zone $binary_remote_addr zone=pmf_php:10m rate=600r/m;
limit_req_status 429;

pmf_login counts sign in (admin and public forms), two factor, passkey, password reset, registration and guest submission attempts from one address (10 a minute after a burst of 10), pmf_post every request that sends data (60 a minute after a burst of 60), pmf_api the public REST API under /api/v3.1/ (120 a minute) and pmf_php every page (600 a minute). Raise them in /etc/nginx/conf.d/cloudimg-phpmyfaq-guard.conf and /etc/nginx/snippets/cloudimg-phpmyfaq-fastcgi.conf if your readers need more, then sudo nginx -t && sudo systemctl reload nginx.

sudo phpmyfaq-cred-roundtrip checks the whole sign in path on the instance: the redirect to HTTPS, that no setup or update page is offered and the configuration is refused, a sign in through the real admin sign-in form with the password from the credentials file, and that a wrong and a common password are refused:

sudo phpmyfaq-cred-roundtrip
OK https 301 + no setup/updater + database.php 404 + administrator sign-in + wrong and default passwords refused

Links opened from another site. The admin panel's background requests under /admin/api/ and the backup export only work when the request comes from your site itself. nginx decides this from the browser's Sec-Fetch-Site, Origin and Referer headers (a request with none of them is refused too), so another site cannot make you export or read admin data through a link. The admin sign in page, the dashboard and every public page stay linkable. Scripts that call those routes must send Sec-Fetch-Site: same-origin:

H=$(sed -n 's/^set \$pmf_host "\(.*\)";$/\1/p' /etc/nginx/phpmyfaq-host.conf)
curl -sk -o /dev/null -w 'admin API link from another site: %{http_code}\n' -H 'Sec-Fetch-Site: cross-site' --resolve "$H:443:127.0.0.1" "https://$H/admin/api/user/users"
curl -sk -o /dev/null -w 'backup export link from another site: %{http_code}\n' -H 'Sec-Fetch-Site: cross-site' --resolve "$H:443:127.0.0.1" "https://$H/admin/backup/export/content"
curl -sk -o /dev/null -w 'admin sign in page from another site: %{http_code}\n' -H 'Sec-Fetch-Site: cross-site' --resolve "$H:443:127.0.0.1" "https://$H/admin/login"
admin API link from another site: 403
backup export link from another site: 403
admin sign in page from another site: 200

Passwords. phpMyFAQ 4.1 stores passwords as a salted SHA-256 hash (its only working scheme; the bcrypt option in constants.php does not sign in on 4.1, so leave PMF_ENCRYPTION_TYPE at hash). The administrator's generated 24 character password is out of reach of guessing; ask your users to choose long passphrases, and switch on two factor authentication for every editor (their profile, Two-factor authentication).

Step 10: Behind a proxy or load balancer

nginx removes X-Forwarded-For, X-Real-IP, Client-IP and Forwarded from every request, and phpMyFAQ reads the client address from the connection, so sessions, the admin log and the statistics use 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, use nginx's real_ip module with set_real_ip_from set to your load balancer's subnet only.

Put the load balancer's DNS name (or your own name for it) in place with sudo phpmyfaq-set-hostname (Step 11): phpMyFAQ builds its links, the admin sign in form and its cookie domain from that name.

Step 11: Your own DNS name and certificate

Point a DNS record at the instance, then set the name phpMyFAQ is served as. This mints a new self signed certificate for the name, updates nginx (which hands the name to phpMyFAQ and redirects the instance's other addresses to it), the cross-site rules and phpMyFAQ's reference URL, updates the addresses in /root/phpmyfaq-credentials.txt and keeps the name across reboots:

sudo phpmyfaq-set-hostname <your-domain>

Do not change Reference URL under Configuration by hand: phpmyfaq-set-hostname keeps it and nginx in step.

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

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

Step 12: Passwords, e-mail, plugins and upgrades

phpmyfaq-set-password gives the administrator admin a new random password through phpMyFAQ's own password code (run as the web server's user, never as root), clears its "remember me" and session state, signs out every session and records the password in /root/phpmyfaq-credentials.txt. Run it without arguments, or pass your own password (24 to 72 letters and digits) on stdin. Users change their own password in their profile.

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

The database configuration is read only to PHP. database.php is executed on every request, so the image does not let the web server rewrite it, and the cloudimg tools never execute it: they read it as data and refuse it unless it belongs to root, is mode 0640 and holds plain values only. Every other phpMyFAQ setting lives in the database and saves normally from Configuration.

E-mail. phpMyFAQ sends password reset mail and notifications with PHP's mail function by default; the image has no mail server, so set an SMTP server under Configuration > Edit configuration > Mail before you rely on e-mail.

Plugins and templates. The code folders belong to root, so plugins and templates are installed from the shell: copy a plugin into /var/www/phpmyfaq/content/plugins/<Name>/ as root, then enable it under Configuration > Plugins.

Upgrades. phpMyFAQ releases are published at https://www.phpmyfaq.de/download/. After a snapshot:

  1. Download the new release's tarball, check its checksum, and copy its files over /var/www/phpmyfaq as root, keeping content/core/config, content/core/data, content/core/logs and content/user; leave out the ._* macOS metadata files (tar --exclude='._*').
  2. For the database update only, open the updater: comment out the location ^~ /update line in /etc/nginx/sites-available/cloudimg-phpmyfaq and the location ~* ^/api/setup line inside its location ^~ /api/ block, let phpMyFAQ write its update token (sudo chmod 0770 /var/www/phpmyfaq/content/core/config), and sudo nginx -t && sudo systemctl reload nginx.
  3. Open https://<your host>/update/ signed in as the administrator and run the update.
  4. Put both nginx lines back, sudo chmod 0750 /var/www/phpmyfaq/content/core/config, remove any update token file phpMyFAQ left there, reload nginx, and check that the code belongs to root again and that database.php is 0640 root:www-data.

Step 13: Server components

Component Path
phpMyFAQ code (root owned) /var/www/phpmyfaq
Database configuration (this instance's database password) /var/www/phpmyfaq/content/core/config/database.php
Uploaded images, attachments /var/www/phpmyfaq/content/user/images, /var/www/phpmyfaq/content/user/attachments
phpMyFAQ data and log /var/www/phpmyfaq/content/core/data, /var/www/phpmyfaq/content/core/logs
Database (MariaDB datadir) /var/lib/mysql
nginx site, request limits and cross-site link rules /etc/nginx/sites-available/cloudimg-phpmyfaq, /etc/nginx/conf.d/cloudimg-phpmyfaq-guard.conf, /etc/nginx/conf.d/cloudimg-xsite-guard.conf, /etc/nginx/snippets/cloudimg-phpmyfaq-*.conf
PHP settings /etc/php/8.3/fpm/conf.d/99-cloudimg-phpmyfaq.ini
MariaDB settings /etc/mysql/mariadb.conf.d/99-cloudimg-phpmyfaq.cnf
Certificate and key /etc/nginx/ssl/
First boot script and helpers /usr/local/sbin/phpmyfaq-firstboot.sh, /usr/local/lib/cloudimg/phpmyfaq/
Helpers /usr/local/sbin/phpmyfaq-set-hostname, phpmyfaq-set-password, phpmyfaq-cred-roundtrip
Licence, release and image notes /usr/share/doc/phpmyfaq/
Credentials /root/phpmyfaq-credentials.txt (mode 0600)

Step 14: Security recommendations

  • Allow port 22 in the security group from your own networks only.
  • Change the administrator e-mail to your own (Step 4), keep its password secret, switch on two factor authentication for it and rotate the password if it may have been shared (Step 12).
  • Open registration only when you are ready to moderate accounts; keep the captcha on for guest suggestions and questions.
  • Keep database.php and your database backups secret (Step 8).
  • Use your own DNS name and a certificate your browsers trust (Step 11).
  • Do not enable Microsoft Entra ID sign in on phpMyFAQ 4.1 (Step 7).
  • Keep phpMyFAQ on the newest release (Step 12) and install only plugins 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 and PHP's FPM, intl and zip packages 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

phpMyFAQ is free software under the Mozilla Public License, version 2.0 (a copy is at /usr/share/doc/phpmyfaq/LICENSE), and the bundled libraries carry their own open source licences. There is no per user or per CPU licence fee and no licence key. This image installs the unmodified upstream release, 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