Fw
Applications AWS

Frappe Wiki 3.4.0 on AWS User Guide

| Product: Frappe Wiki 3.4.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using Frappe Wiki 3.4.0 on Ubuntu 24.04 from cloudimg on Amazon EC2. It is one version of the cloudimg Hardened BookStack & CMS, Blogs and Wikis AMIs listing on AWS Marketplace; you pick it as the software version when you subscribe and launch. The other products in the listing are described on the listing's guide hub.

Frappe Wiki is an open source wiki and knowledge base built on the Frappe framework. It suits text-heavy content such as product documentation, internal handbooks, runbooks and policies. Authors get a block editor, a drag-and-drop page tree, wiki spaces, revision history and a change-request approval workflow; readers get a published site with search and an automatic table of contents. Frappe Wiki and the Frappe framework are licensed under the MIT License.

What is on the image:

  • Frappe Wiki 3.4.0 (pinned v3.4.0 release tag) on the Frappe framework v16.54.0 (pinned v16.54.0 release tag); both apps are checked against their release commits at build time
  • Python 3.14 and Node.js 24, both upstream requirements of Frappe v16, with the front-end assets (Frappe and the Wiki app) pre-compiled
  • HTTPS from first boot: nginx serves the site on port 443 with a certificate created on your instance's first boot (an end-entity certificate for the site name, never a certificate authority), and port 80 redirects to HTTPS. The application servers listen on localhost only
  • MariaDB and Redis on localhost only. The site database user authenticates with a per-instance password, and MariaDB's root stays on unix-socket authentication (no root password exists)
  • One Frappe site with the setup wizard completed and one private wiki space, Wiki, published at the route docs
  • frappe-wiki-firstboot.service, which runs before the application may start and creates your instance's Administrator password, site database password and Frappe encryption key
  • frappe-wiki-set-hostname, which moves the site to your own DNS name
  • wkhtmltopdf for print-to-PDF, and a functional self-test (frappe-wiki-selftest.sh)
  • Dependency security fixes applied on top of the upstream release (see Security notes)

Default user: ubuntu · Architecture: x86_64 · Root volume: 30 GiB gp3 (sized for the Frappe bench, its Python environment and the MariaDB data directory, with room to grow)

Security posture

  • No default login. A stock Frappe site signs in as Administrator with a password set at install time. On this image that password is discarded: the Administrator password, the site database password and Frappe's encryption_key (which encrypts stored secrets such as email passwords) are all generated on each instance's first boot, before the application starts. None of them is in the image and no two instances share one. The image ships exactly two Frappe users, Administrator and Guest, and Guest cannot sign in.
  • No build-time database. The MariaDB data directory in the image was destroyed and re-initialised from a credential-free dump of the site at build time, so it holds no account, password hash or session from the build. First boot creates the database accounts with this instance's own passwords.
  • Fails closed. The application servers (supervisor) and nginx start only after first boot has finished and written the per-instance secrets; supervisor additionally refuses to start without a database password, an encryption key and an HTTPS site address. If first boot cannot finish, the site stays down rather than open.
  • Private by default. Wiki spaces are readable only by signed-in users with access to the space; an anonymous visitor gets 404. Self sign-up is off, and anonymous users cannot upload files or read records through the REST API.
  • No anonymous OAuth client registration. Frappe v16 turns on OAuth dynamic client registration by default, which lets anyone register an OAuth client without signing in. This image turns it off on every instance's first boot, so an anonymous registration request gets 404. An administrator can still create OAuth clients in the desk.
  • Unprivileged application. gunicorn, the background workers, the scheduler and the realtime (Socket.IO) server all run as the unprivileged frappe user, never as root.
  • The application cannot reach root through supervisor. supervisor runs as root, so its program file /etc/supervisor/conf.d/frappe-bench.conf is a root-owned file generated at build time (not a link to the frappe user's bench configuration), every program in it runs as frappe, its logs are in the root-owned /var/log/frappe-wiki/, and its control socket is usable by root only. Code running as frappe can neither add a program nor start one.
  • Only nginx faces the network. The listening set is exactly 22, 80 and 443. gunicorn, MariaDB and Redis are bound to localhost, and the realtime server listens on a UNIX socket that only nginx can reach. 3306 and 6379 are never exposed.
  • The site address comes from configuration, never from the request. First boot reads it from /etc/cloudimg/frappe-wiki-hostname or the EC2 instance metadata service (IMDSv2) and freezes it as the site's host_name, the nginx server_name and the certificate name. nginx answers only that name and drops any other Host or SNI, so a request cannot rewrite the links in emails and reset messages. nginx overwrites X-Forwarded-For with the real client address, so a client cannot spoof its IP.
  • Cross-origin requests refused. A state-changing request or a WebSocket upgrade that carries another site's Origin header is refused by nginx before it reaches the application. Frappe also runs many API methods on a plain GET and checks its CSRF token only on POST, PUT, DELETE and PATCH, so a link on another site could otherwise call them as you. When a signed-in browser sends a GET to the Frappe API (/api/method/... or a ?cmd= request) that came from another site, or was opened from outside the browser, nginx refuses it with 403. The wiki itself and API clients using API keys are unaffected.
  • Uploads cannot run in your browser. Every response under /files/ and /private/files/ carries X-Content-Type-Options: nosniff and a sandboxing Content-Security-Policy, and anything that is not an image, PDF, font, audio or video file is served as a download.
  • No instance metadata access for the application. Frappe can be asked to fetch URLs (print-to-PDF, file URLs, the Wiki's broken-link report, background jobs), so the mariadb, redis-server, nginx and supervisor services are blocked from reaching the instance metadata endpoints (169.254.169.254, its IPv4-mapped IPv6 form and fd00:ec2::254). Only the first-boot unit reads metadata, for the site address.
  • IMDSv2 required. The AMI is registered with ImdsSupport v2.0, so instances launched from it refuse token-less (IMDSv1) metadata requests.
  • Key-only SSH. Password and keyboard-interactive logins, root login and X11 forwarding are turned off in /etc/ssh/sshd_config.d/00-cloudimg.conf; you sign in as ubuntu with your EC2 key pair.
  • Per-instance identity. SSH host keys, the machine ID, the site certificate and the site database password, Administrator password and Frappe encryption key are all generated fresh on every instance.

Prerequisites

  • An AWS account where you can subscribe to AWS Marketplace products and launch EC2 instances
  • An EC2 key pair in the target Region
  • A VPC and subnet, with a public IP address on the instance if your users reach it over the internet
  • A security group that allows 22/tcp from your management network, and 80/tcp and 443/tcp from your users' networks. No other port is needed; do not open 3306 or 6379

Recommended instance type: m5.large (2 vCPU, 8 GiB RAM) suits a team wiki. Use m5.xlarge (4 vCPU, 16 GiB RAM) for larger teams or heavy editing traffic.

Step 1: Launch from AWS Marketplace

  1. Open the Hardened BookStack & CMS, Blogs and Wikis AMIs listing by cloudimg and choose View purchase options, then subscribe.
  2. Choose Continue to Configuration. Under Software version choose Frappe Wiki 3.4.0 on Ubuntu 24.04, pick your Region, and choose Continue to Launch.
  3. Under Choose Action select Launch through EC2.
  4. Set the instance type to m5.large, choose your key pair, and use the seller's security group settings (ports 22, 80 and 443), narrowing port 22 to your management network. Keep the 30 GiB root volume.
  5. Launch. First boot completes within two to three minutes of the instance reaching the running state.

Step 2: Launch from the AWS CLI

Workstation commands. Replace <ami-id> with the AMI ID shown for the Frappe Wiki 3.4.0 on Ubuntu 24.04 software version on the listing's launch page, and use your own VPC, subnet, key pair and address ranges.

aws ec2 create-security-group --group-name frappe-wiki-sg \
  --description "Frappe Wiki" --vpc-id <vpc-id>
aws ec2 authorize-security-group-ingress --group-id <security-group-id> \
  --protocol tcp --port 22 --cidr <your-ip>/32
aws ec2 authorize-security-group-ingress --group-id <security-group-id> \
  --protocol tcp --port 80 --cidr <users-cidr>
aws ec2 authorize-security-group-ingress --group-id <security-group-id> \
  --protocol tcp --port 443 --cidr <users-cidr>

aws ec2 run-instances --image-id <ami-id> --instance-type m5.large \
  --key-name <key-name> --subnet-id <subnet-id> \
  --security-group-ids <security-group-id> --associate-public-ip-address \
  --metadata-options HttpTokens=required \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=frappe-wiki}]'

aws ec2 describe-instances --instance-ids <instance-id> \
  --query "Reservations[].Instances[].[PublicDnsName,PublicIpAddress]" --output text

Step 3: Connect over SSH

ssh -i <key.pem> ubuntu@<public-dns>

The login user is ubuntu.

Step 4: Retrieve your credentials

First boot writes your instance's credentials to a root-only file:

sudo cat /root/frappe-wiki-credentials.txt

It looks like this (your values differ):

wiki.url=https://ec2-203-0-113-10.compute-1.amazonaws.com/wiki-app
wiki.docs.url=https://ec2-203-0-113-10.compute-1.amazonaws.com/docs
wiki.desk.url=https://ec2-203-0-113-10.compute-1.amazonaws.com/app
wiki.admin.login=Administrator
wiki.admin.pass=<generated-per-instance>
mariadb.benchadmin.login=benchadmin
mariadb.benchadmin.pass=<generated-per-instance>

The site address is taken from /etc/cloudimg/frappe-wiki-hostname if that file exists, otherwise from IMDSv2: the instance's public DNS name, else its public IPv4 address, else its private IPv4 address. If you keep credentials centrally, copy them into AWS Secrets Manager.

Step 5: Verify the services

systemctl is-active mariadb redis-server supervisor nginx
ls /var/lib/cloudimg/frappe-wiki-firstboot.done

All four services print active, and the first-boot marker exists (first boot runs once; after a reboot its unit shows as skipped, which is expected).

Only 22, 80 and 443 listen on a public address. gunicorn (8000), MariaDB (3306), Redis (6379) and the loopback health listener (8088) are on localhost:

sudo ss -Hltn | awk '{print $4}' | sort -u
0.0.0.0:22
0.0.0.0:443
0.0.0.0:80
127.0.0.1:3306
127.0.0.1:6379
127.0.0.1:8000
127.0.0.1:8088
127.0.0.53%lo:53
127.0.0.54:53
[::1]:6379
[::]:22
[::]:443
[::]:80

The two :53 lines are Ubuntu's local DNS stub resolver.

The application processes (gunicorn, the realtime server, the scheduler and the workers) all run as the unprivileged frappe user, and the realtime server listens on a UNIX socket owned by frappe and reachable only by nginx's group:

ps -eo user=,args= | awk '/[g]unicorn|[s]ocketio\.js|[b]ench_helper/{print $1}' | sort | uniq -c
sudo stat -c '%A %U %G %n' /run/frappe-socketio/socketio.sock
      6 frappe
srwxrwx--- frappe www-data /run/frappe-socketio/socketio.sock

supervisor itself runs as root, so the programs it starts are defined in a root-owned file that the frappe user cannot change, and its control socket is root-only:

sudo supervisorctl status
stat -c '%U:%G %a %n' /etc/supervisor/conf.d/frappe-bench.conf /var/run/supervisor.sock
frappe-bench-web:frappe-bench-frappe-web                      RUNNING   pid 1234, uptime 0:05:00
frappe-bench-web:frappe-bench-node-socketio                   RUNNING   pid 1235, uptime 0:05:00
frappe-bench-workers:frappe-bench-frappe-long-worker-0        RUNNING   pid 1236, uptime 0:05:00
frappe-bench-workers:frappe-bench-frappe-schedule             RUNNING   pid 1237, uptime 0:05:00
frappe-bench-workers:frappe-bench-frappe-short-worker-0       RUNNING   pid 1238, uptime 0:05:00
root:root 644 /etc/supervisor/conf.d/frappe-bench.conf
root:root 700 /var/run/supervisor.sock

The health endpoint answers pong, the HTTPS front end on the configured name answers 200, port 80 redirects to HTTPS, and the realtime (Socket.IO) handshake through nginx answers 200:

SITE=$(sudo cat /var/lib/cloudimg/frappe-wiki-current-hostname)
curl -s http://127.0.0.1:8088/api/method/ping; echo
curl -sk -o /dev/null -w "%{http_code}\n" --resolve "${SITE}:443:127.0.0.1" "https://${SITE}/login"
curl -s -o /dev/null -w "%{http_code}\n" --resolve "${SITE}:80:127.0.0.1" "http://${SITE}/"
curl -sk -o /dev/null -w "%{http_code}\n" --resolve "${SITE}:443:127.0.0.1" "https://${SITE}/socket.io/?EIO=4&transport=polling"
{"message":"pong"}
200
301
200

A request for any other host name is dropped (nginx closes the connection, so curl prints 000):

curl -sk -o /dev/null -w "%{http_code}\n" --resolve "evil.example:443:127.0.0.1" "https://evil.example/login" || true

The wiki is private by default, and anonymous OAuth client registration is off; both print 404:

SITE=$(sudo cat /var/lib/cloudimg/frappe-wiki-current-hostname)
curl -sk -o /dev/null -w "%{http_code}\n" --resolve "${SITE}:443:127.0.0.1" "https://${SITE}/docs/welcome-to-frappe-wiki"
curl -sk -o /dev/null -w "%{http_code}\n" --resolve "${SITE}:443:127.0.0.1" -X POST \
  -H 'Content-Type: application/json' --data '{"client_name":"test","redirect_uris":["https://example.invalid/cb"]}' \
  "https://${SITE}/api/method/frappe.integrations.oauth2.register_client"

The application cannot reach the instance metadata service. This runs the same request from inside supervisor.service (gunicorn, the workers and the realtime server) as the frappe user, and from your shell; the first prints 000 (blocked), the second 200:

CG=$(systemctl show -p ControlGroup --value supervisor.service)
sudo bash -c "echo \$\$ > /sys/fs/cgroup${CG}/cgroup.procs; exec setpriv --reuid=frappe --regid=frappe --init-groups curl -s -m 5 -o /dev/null -w '%{http_code}\n' -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 60' http://169.254.169.254/latest/api/token"
curl -s -m 5 -o /dev/null -w '%{http_code}\n' -X PUT -H 'X-aws-ec2-metadata-token-ttl-seconds: 60' http://169.254.169.254/latest/api/token

Step 6: Sign in

  1. Open the wiki.url address from Step 4 in a browser. Plain http:// redirects to HTTPS.
  2. The certificate is self-signed and unique to your instance, so the browser warns the first time. Before you continue, compare the SHA-256 fingerprint the browser shows with the one the instance prints (do not add the certificate to a trust store), or install a trusted certificate (Step 10):
sudo openssl x509 -in /etc/ssl/frappe-wiki/frappe-wiki.crt -noout -fingerprint -sha256
  1. Wiki spaces are private, so the wiki first asks you to sign in. Sign in as Administrator with the wiki.admin.pass value.

The Frappe sign-in page

After signing in, open the wiki.url address (/wiki-app). The wiki app opens on its Overview (page views and open change requests), with your spaces listed in the sidebar under Spaces and All Spaces. A space is a self-contained documentation site with its own route, page tree and permissions; the image ships one space, Wiki, at the route docs. Use New Space to add more, for example one for product documentation and one for an internal handbook. Page-view tracking is off until you turn it on (it stores a device fingerprint for each visitor).

The Frappe Wiki app overview and spaces

Common default passwords (admin, password, changeme, frappe, wiki and the like) are rejected; only the per-instance password works. Change it after your first sign-in under My Settings in the desk (/app).

Step 7: Write and publish pages

Open a space to get the authoring view: the page tree on the left, the block editor in the middle, and the page's route and publish state at the top. The toolbar gives you headings, bold and italic, inline and block code, bullet, numbered and task lists, quotes, dividers, tables, links, images and video embeds. Drag pages in the tree to re-order or nest them. Your edits are collected in a draft change request (Drafting Changes above the page tree). Submit it for review; a reviewer approves and merges it, and only then do readers see the change. The Change Requests view in the sidebar lists everything in review.

The Frappe Wiki block editor

Readers see the published page at the space route, with the page tree, search and an automatic "On this page" table of contents:

A published Frappe Wiki page

Step 8: Choose who can read your wiki

The shipped space is private: only signed-in users with access to it can read it, and an anonymous visitor gets 404. To publish a space to the public internet, open the space's record in the desk at /app/wiki-space and add the Guest role to its Roles table. Anonymous visitors can then read every published page in that space, while editing still requires a sign-in. Grant access to named teams the same way, by adding the roles that should read or contribute.

Add people as normal Frappe users: in the desk at /app/user, create the user (self sign-up is off), then give them the Wiki roles they need: Wiki User to read and draft changes, Wiki Approver to review and merge change requests, and Wiki Manager to manage spaces.

Step 9: Use your own hostname

Frappe builds the links in emails and password-reset messages from the site's host_name, which this image sets from configuration. To serve the wiki on your own DNS name, point a DNS record (for example in Amazon Route 53) at the instance's public IP (an Elastic IP keeps it stable), then run on the instance:

sudo frappe-wiki-set-hostname wiki.example.org

The helper writes the name to /etc/cloudimg/frappe-wiki-hostname, issues a new self-signed certificate for it, rewrites the site host_name, the nginx server_name and the cross-origin allow-list, updates the credentials file, and restarts the application and nginx. An instance without an Elastic IP gets a new public DNS name when it is stopped and started; run the helper with the new name so links and the certificate follow it.

Step 10: Install a trusted certificate

The site certificate is /etc/ssl/frappe-wiki/frappe-wiki.crt and its key /etc/ssl/frappe-wiki/frappe-wiki.key (mode 600, root). To use Let's Encrypt, first set your DNS name (Step 9), open port 80 to the internet, then:

sudo apt-get install -y certbot
sudo certbot certonly --standalone -d wiki.example.org \
  --pre-hook "systemctl stop nginx" --post-hook "systemctl start nginx" \
  --deploy-hook "install -m 644 \$RENEWED_LINEAGE/fullchain.pem /etc/ssl/frappe-wiki/frappe-wiki.crt && install -m 600 \$RENEWED_LINEAGE/privkey.pem /etc/ssl/frappe-wiki/frappe-wiki.key"

Maintenance

  • Self-test. The self-test creates a wiki page under the default space, reads it back, renders it with the Wiki's own renderer and deletes it:
sudo /usr/local/sbin/frappe-wiki-selftest.sh
  • Backups. Take a backup with files from the bench directory; it is written under the site's private/backups. Copy it off the instance (for example to Amazon S3) on a schedule.
cd /opt/frappe/frappe-bench
sudo -u frappe bench --site wiki.localhost backup --with-files
  • Security updates. Unattended-upgrades is enabled and applies OS security updates.
  • Application updates. The frappe and wiki apps are pinned for a reproducible image and ship without git history. To move to a newer release, take a backup and re-fetch the app with bench get-app --overwrite at the release you want, then bench --site wiki.localhost migrate and bench build, in a maintenance window. bench setup supervisor only rewrites the bench's own config/supervisor.conf, which supervisor does not read; the programs supervisor runs are defined in the root-owned /etc/supervisor/conf.d/frappe-bench.conf, so review that file (it must keep user=frappe on every program) if an update adds a process. Re-fetching an app re-applies upstream's own dependency pins, which are older than the fixed versions this image ships (see Security notes); re-check pip-audit and npm audit after an update.

Security notes

The upstream releases pin several Python and JavaScript dependencies at versions with published advisories. This image ships fixed versions and the build fails if an audit finds anything else:

  • Python (bench environment): Pillow 13.0.0, pypdf 6.21.4 (Frappe pins 6.19.0; 6.21.3 and 6.21.4 fix October 2026 advisories in PDF parsing) and orjson 3.11.9, installed from hash-pinned wheels, plus GitPython 3.2.1 and filelock 4.2.5. The image's own validation checks the exact running versions and exercises them as normal (non-administrator) users: uploading PNG, JPEG, GIF, TIFF, WebP and BMP images through the Wiki editor, generating thumbnails, reading a page with images as a reader, and printing a page to PDF.
  • Wiki editor (browser bundle): the Wiki 3.4.0 release bundles DOMPurify 3.4.11, Tiptap 3.29.2 and prosemirror-view 1.41.8, which have published advisories. This image rebuilds the Wiki front end with DOMPurify 3.4.17, Tiptap 3.32.0 and prosemirror-view 1.42.8 (same major versions), and checks the shipped bundle.
  • Realtime server (Node.js): socket.io 4.8.4, engine.io 6.6.11, socket.io-parser 4.2.7 and ws 8.21.3, which fix denial-of-service advisories in the Socket.IO transports, plus fixed lodash, moment, nanoid, minimatch, picomatch and other bundled libraries.
  • OS packages: fully patched at build, with no package whose security fixes are only in Ubuntu Pro ESM.

Recorded residual advisories (moderate, with no fixed release inside the Frappe v16 toolchain). The audit records are in /usr/share/doc/cloudimg/frappe-wiki/:

  • pdfkit 1.0.0 (PYSEC-2026-2860, path traversal in from_string, local): no fixed release exists. Reachable only through print-format HTML, which only System Managers can author.
  • showdown 2.1.0 (Markdown preview cross-site scripting and ReDoS) and quill 2.0.3 / 1.3.7 (HTML export cross-site scripting): client-side desk editor components, reachable only by signed-in desk users. Give desk access only to trusted staff.
  • Build-time tools in the Frappe tree: postcss-selector-parser (through cssnano 5), esbuild 0.14 with esbuild-plugin-vue3, and minimatch 3 with glob and stylus (through the asset build's PostCSS plugin; minimatch's October 2026 denial-of-service fix exists only in its 11.x line). They run only when an administrator runs bench build, with fixed file patterns; never on the request path.
  • npm-audit-wiki.json audits the Wiki front end's build tool chain (vite, rollup and their helpers). Those packages built the shipped browser bundle and were then removed from the image; none of them is on disk.
sudo python3 -c "import json;d=json.load(open('/usr/share/doc/cloudimg/frappe-wiki/npm-audit-frappe.json'));print(d['metadata']['vulnerabilities']);print(sorted(d['vulnerabilities']))"
{'info': 0, 'low': 0, 'moderate': 9, 'high': 0, 'critical': 0, 'total': 9}
['esbuild', 'esbuild-plugin-vue3', 'frappe-quill-image-resize', 'glob', 'minimatch', 'postcss-selector-parser', 'quill', 'showdown', 'stylus']

Troubleshooting

  • The site does not answer. Check first boot: systemctl status frappe-wiki-firstboot.service and sudo journalctl -u frappe-wiki-firstboot. Supervisor and nginx stay stopped until first boot finishes.
  • Certificate warning. Expected until you install your own certificate; the first-boot certificate is self-signed. Compare its fingerprint as in Step 6.
  • A page returns 404 when you are not signed in. That is the shipped default: wiki spaces are private. Add the Guest role to the space (Step 8) to make it publicly readable.
  • 403 on an API link opened from another site or typed into the address bar. Expected for a signed-in browser: such GET requests to /api/method/... are refused (see Security posture). Open the page from the wiki instead.
  • 403 on a form or action. The origin guard refuses cross-origin state-changing requests; use the site's own address, not a raw IP that does not match the configured name.
  • Live updates do not appear. The realtime server runs under supervisor as frappe-bench-web:frappe-bench-node-socketio; check it with sudo supervisorctl status and its log in /var/log/frappe-wiki/node-socketio.error.log (supervisor writes every program log under /var/log/frappe-wiki/).
  • You need the Administrator password again. It is only ever written to /root/frappe-wiki-credentials.txt on that instance. If it has been changed and lost, reset it with cd /opt/frappe/frappe-bench && sudo -u frappe bench --site wiki.localhost set-admin-password (the command prompts for the new password).

What this image is and is not

This is Frappe Wiki 3.4.0 on the Frappe framework v16.54.0, hardened and automated by cloudimg for AWS Marketplace: HTTPS on first boot, per-instance secrets, a credential-free database, private spaces, unprivileged application processes, localhost-bound data services and dependency security fixes. It is not affiliated with or endorsed by Frappe Technologies. Frappe and Frappe Wiki are trademarks of their respective owners; the software is used under the MIT License.