Mataroa 1.5.0 on AWS User Guide
Overview
This guide covers launching and using the Mataroa 1.5.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg developer and API tools listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
Mataroa is a minimalist, open source blogging platform for writers. Each user gets a plain, fast blog: posts are written in Markdown, pages carry no tracking or advertising, and every blog has an RSS feed, an e-mail newsletter, optional comments, static pages, image uploads, simple analytics and exports to Markdown, a static site or an e-book. One instance hosts many blogs, each on its own subdomain of your domain (<username>.<your-domain>).
The cloudimg image locks Mataroa down:
- HTTPS from the first boot. nginx serves Mataroa on port 443 with a certificate generated on your instance's first boot. Port 80 redirects to HTTPS.
- A password gate in front of everything. Until you decide to publish, the whole site (dashboard, blogs and the Django admin) sits behind an HTTP Basic Auth prompt whose password is generated on your instance's first boot. A fresh instance is never an open blog on the internet. Step 7 shows how to open the blogs to readers when you are ready.
- No default login. The image contains no Mataroa account. First boot creates the administrator
editorwith a random 24 character password and writes it, with the gate password, to/var/lib/cloudimg/mataroa-credentials.txt(root only). Mataroa stores only a PBKDF2 hash. Django's secret key is also generated per instance. Common guesses such asadmin/adminare refused. - Closed to strangers by default. Self sign-up, Stripe billing and the inbound e-mail webhooks are switched off, and no Stripe key is on the image. E-mail is off until you configure your own SMTP relay, so nothing leaves the instance.
- Your own database, no password anywhere. The image ships an empty PostgreSQL data directory. On first boot PostgreSQL initialises it for your instance, and Mataroa's database role signs in over the local socket with operating system (peer) authentication, so there is no database password to leak. PostgreSQL has no network listener at all.
- The host name comes from configuration, never from the request. Mataroa's domain is set on first boot from the instance's public DNS name, nginx refuses requests for host names the instance does not serve, and Mataroa always sees the real client address, whatever forwarding headers a request carries.
- Other web sites cannot act with your login. nginx refuses any state changing request (POST and the like) whose browser
Originis not this instance, on top of Django's own CSRF tokens. - The application cannot reach the instance metadata service. nginx, Mataroa (gunicorn) and PostgreSQL are blocked at the kernel level from the EC2 instance metadata service (IPv4 and IPv6), so no request, post or SQL statement can read the instance's metadata or role credentials.
- A pinned, audited release. Mataroa 1.5.0 is installed from the official release archive (checksum pinned), with one later upstream security fix backported (blog owners only can post author comments on their own blog). It runs on Ubuntu's own Python 3.12 in an isolated environment that holds only checksum-pinned packages, each at its newest fixed release, with no known vulnerabilities at build time. There is no compiler and no package installer on the image.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.
What is on the image: Mataroa 1.5.0 (in /opt/mataroa, configuration in /etc/mataroa), gunicorn, PostgreSQL 16, nginx, Python 3.12, 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.
- A security group that allows TCP 22 (SSH), TCP 443 (Mataroa) and TCP 80 (redirects to HTTPS). While the password gate is on, open them to your own networks only. Mataroa needs no other port: the database is not on the network.
- To publish blogs to readers: a domain you control, for example
example.com, where you can create two DNS records (Step 7).
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Mataroa 1.5.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair, a security group as above and a subnet. The database lives on the root volume; increase its size at launch if your writers will upload many images, which Mataroa stores in the database.
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=mataroa}]'
Step 2: Connect to your instance
| Version | SSH user |
|---|---|
| Mataroa 1.5.0 on Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
First boot sets the host name and certificate, initialises the database and creates the secrets, then Mataroa and nginx start:
systemctl is-active postgresql@16-main mataroa.socket mataroa nginx mataroa-firstboot
sudo test -f /var/lib/cloudimg/mataroa-firstboot.done && echo FIRSTBOOT_DONE
All five units are active and the first boot marker is present:
active
active
active
active
active
FIRSTBOOT_DONE
Only SSH and nginx listen on the network. Mataroa (gunicorn) and PostgreSQL use local sockets only:
sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v '127.0.0.5' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort
sudo ss -lxp | grep -E 'gunicorn|postgres' | awk '{print $5}' | sort -u
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))
/run/mataroa/gunicorn.sock
/var/run/postgresql/.s.PGSQL.5432
Step 4: Get your URL and passwords
sudo cat /var/lib/cloudimg/mataroa-credentials.txt
The file is readable by root only and holds the URL, the gate's user name and password, and the administrator's user name and password for this instance:
# Mataroa 1.5.0 - generated on first boot by mataroa-firstboot.service
# These credentials are unique to this instance; none was baked into the image. Keep this file secret.
MATAROA_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/
MATAROA_BASIC_USER=mataroa
MATAROA_BASIC_PASSWORD=<MATAROA_BASIC_PASSWORD>
MATAROA_ADMIN_USER=editor
MATAROA_ADMIN_PASSWORD=<MATAROA_ADMIN_PASSWORD>
#
# Open MATAROA_URL in a browser (self-signed certificate minted on this instance's first boot; port 80 redirects to
# https), pass the HTTP Basic Auth prompt with MATAROA_BASIC_USER / MATAROA_BASIC_PASSWORD, then sign in to Mataroa as
# MATAROA_ADMIN_USER. The Django admin is at ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/dja/. Sign-ups, billing and e-mail are OFF.
# Database: PostgreSQL 'mataroa' over the local socket only (peer authentication, no database password).
# Self-test: sudo mataroa-selftest Change the served name: sudo mataroa-set-hostname <your-domain>
# User guide: https://www.cloudimg.co.uk/guides/mataroa-1-5-0-on-ubuntu-24-04-aws/
The URL uses the instance's public DNS name from EC2 instance metadata. Opening https://<public-ip>/ sends you on to that name.
Step 5: Sign in
Open the MATAROA_URL in your browser. The certificate is self signed, so your browser asks you to confirm it the first time (or install your own certificate, Step 7). The browser then asks for a user name and password: this is the gate, so enter MATAROA_BASIC_USER and MATAROA_BASIC_PASSWORD. Mataroa's own sign-in page appears; enter editor and MATAROA_ADMIN_PASSWORD and choose Login:

Then change the password: on the dashboard choose Change password. (On the instance, sudo mataroa-manage changepassword editor does the same and asks for the new password twice.) The sign-in page still shows Mataroa's Sign up link; it leads nowhere until you allow sign-ups (Step 8).
The image includes a self test. Through nginx it passes the gate, signs editor in with Mataroa's real login form, checks that common guesses such as admin/admin are refused (without locking the account), and checks the security settings:
sudo mataroa-selftest
SELFTEST-OK https 301 + healthz + gate 401/401/200 + editor signed in via the login form + dashboard + Django admin 200 + 6 common guesses refused (no lockout) + sign-up/billing/webhooks 404 + 403 cross-origin POST + 444 unknown host
Step 6: Write your first post
The dashboard is the writer's home: new posts, pages, images, the newsletter, analytics, import and export, blog settings and, for the administrator, moderation and the Django admin:

Choose New post, give it a title and write the body in Markdown. The publication date defaults to today; clear it to keep the post as a draft, or set a future date to publish it then. Drag images into the editor to upload them. Choose Save:

Under Blog settings set the blog's title, its description (shown on the blog's home page), a footer, whether comments are enabled, and the theme.
Step 7: Publish your blog on your own domain
Mataroa serves each user's blog on a subdomain of the instance's domain: the editor blog of example.com is https://editor.example.com/. The EC2 host name cannot have such subdomains, so give the instance your own domain to publish:
- In your DNS, point
<your-domain>and the wildcard*.<your-domain>(A records) at the instance's public IP address. An Elastic IP keeps that address stable. The domain must have exactly two labels, such asexample.com: that is Mataroa's model, the dashboard on the domain and the blogs one level below it. - Tell Mataroa to use it. This sets Mataroa's domain, the names nginx accepts and the Origin guard, and generates a new self signed certificate for the domain and its wildcard:
sudo mataroa-set-hostname <your-domain>
The blog of each user is now at https://<username>.<your-domain>/, with the posts listed on its home page:

and each post at /blog/<post-slug>/, with links to subscribe by RSS or e-mail:

- For a certificate your readers' browsers trust, put a wildcard certificate chain for
<your-domain>and*.<your-domain>in/etc/ssl/mataroa/mataroa.crtand its private key in/etc/ssl/mataroa/mataroa.key(root only, mode 600), and runsudo systemctl reload nginx. Alternatively put the instance behind an Application Load Balancer with an AWS Certificate Manager wildcard certificate that forwards to port 443 on the instance. - Open the blogs to readers by switching the password gate off, then allow port 443 (and 80) from anywhere in the security group:
# readers can now open https://<your-domain>/ and the blogs without the gate password
sudo sed -i -E 's/^(auth_basic)/# \1/' /etc/nginx/mataroa-gate.conf && sudo nginx -t && sudo systemctl reload nginx
With the gate off, the dashboard and the Django admin (/dja/) are protected by Mataroa's own sign-in; keep the editor password strong. To put the gate back, remove the # in front of the two auth_basic lines of /etc/nginx/mataroa-gate.conf and reload nginx.
Step 8: Add writers
Self sign-up is off. The administrator creates accounts in the Django admin: on the dashboard choose Django Admin, then Users, Add user, enter a user name (it becomes the blog's subdomain) and a password, and choose Save. The writer signs in at https://<your-domain>/accounts/login/ and changes the password under Change password.
To let anyone create an account instead, allow Mataroa's sign-up page; nginx keeps refusing billing and the webhooks:
# self sign-up at https://<your-domain>/accounts/create/
sudo sed -i -E 's#^(location \^~ /accounts/(create|humanity-diagnostics)/)#\# \1#' /etc/nginx/mataroa-closed.conf && sudo nginx -t && sudo systemctl reload nginx
Step 9: The database
PostgreSQL 16 runs on the instance with no network listener. Each role signs in only as the operating system user of the same name (peer authentication), and no role has a password:
sudo -u postgres psql -X -A -t -c "SELECT current_setting('server_version'), current_setting('data_directory'), current_setting('listen_addresses') = '' AS no_tcp"
sudo -u postgres psql -X -A -t -c "SELECT rolname, rolsuper, rolpassword IS NULL AS no_password FROM pg_authid WHERE rolname NOT LIKE 'pg\_%' ORDER BY 1"
sudo grep -v -E '^#|^$' /etc/postgresql/16/main/pg_hba.conf
16.15 (Ubuntu 16.15-0ubuntu0.24.04.1)|/var/lib/postgresql/16/main|t
mataroa|f|t
postgres|t|t
local all postgres peer
local mataroa mataroa peer
local all all reject
host all all 0.0.0.0/0 reject
host all all ::/0 reject
Posts, pages, images, comments and sessions all live in the mataroa database, so back it up as one unit. A backup to a directory of your choice:
sudo mkdir -p <backup-dir> && sudo -u postgres pg_dump -Fc mataroa | sudo tee <backup-dir>/mataroa-$(date +%F).dump >/dev/null
Keep copies off the instance too, for example in Amazon S3, or take EBS snapshots of the volume. Writers can also export their own blog from the dashboard (Export blog).
Step 10: Turn on e-mail (optional)
E-mail is off: comment notifications, newsletters, exports by e-mail and password reset e-mails are discarded. To send them through your own SMTP relay (for example Amazon SES), create /etc/mataroa/local.env with your relay's values (STARTTLS on the port you give), then restart Mataroa:
# notifications are sent from addresses at <your-domain>
sudo install -m 0640 -o root -g mataroa /dev/null /etc/mataroa/local.env
sudo tee /etc/mataroa/local.env >/dev/null <<'EOF'
CLOUDIMG_SMTP_HOST=email-smtp.<region>.amazonaws.com
CLOUDIMG_SMTP_PORT=587
CLOUDIMG_SMTP_USER=<smtp-user>
CLOUDIMG_SMTP_PASSWORD=<smtp-password>
EOF
sudo systemctl restart mataroa
Allow outbound TCP 587 from the instance to your relay. Newsletters and the daily summaries are sent by Mataroa's management commands (sudo mataroa-manage processnotifications, sudo mataroa-manage mailsummary), which the image does not schedule; add a systemd timer or cron entry for them if you use newsletters.
Step 11: Security notes
nginx, Mataroa and PostgreSQL are blocked from the instance metadata service:
for u in nginx mataroa postgresql@16-main; do printf '%s ' "$u"; systemctl show -p IPAddressDeny --value "$u" | tr ' ' '\n' | sort | tr '\n' ' '; echo; done
nginx 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
mataroa 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
postgresql@16-main 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128
The gate answers before Mataroa does, nginx refuses a state changing request that another web site's page sends, and a request for a host name the instance does not serve gets no answer at all:
H=$(sudo sed -n 's/^DOMAIN=//p' /etc/mataroa/host.env)
curl -sk -o /dev/null -w '%{http_code}\n' --resolve "$H:443:127.0.0.1" "https://$H/"
curl -sk -o /dev/null -w '%{http_code}\n' --resolve "$H:443:127.0.0.1" -X POST -H 'Origin: https://evil.example' --data 'x=1' "https://$H/accounts/login/"
curl -sk -o /dev/null -w '%{http_code}\n' --resolve evil.example:443:127.0.0.1 https://evil.example/ || true
401
403
000
Other notes:
- Passwords. Mataroa stores PBKDF2 hashes (Django's default). The administrator and gate passwords this image generates are 24 character random strings; the gate password is stored as a SHA-512 crypt hash in
/etc/nginx/mataroa.htpasswd. - Secrets. Django's secret key is in
/etc/mataroa/mataroa.env(readable by root and themataroagroup only). Mataroa runs as its own unprivileged user that cannot change its code, and gunicorn listens only on a socket that nginx alone can open. - Logs. Mataroa's request log is in the journal (
journalctl -u mataroa); nginx logs are in/var/log/nginx.
Step 12: Keep it up to date
Ubuntu security updates install automatically. To apply all updates now:
sudo apt-get update && sudo apt-get upgrade -y
New Mataroa releases arrive as new versions of this listing, built and checked the same way. To move to one, back up the database (Step 9), launch the new version and restore your backup into its mataroa database. Read the Mataroa changelog before you move.
Support and licensing
Mataroa is open source under the GNU Affero General Public License version 3 (the Mataroa contributors); the licence text is in /usr/share/doc/cloudimg-mataroa/, and the source code is at github.com/mataroa-blog/mataroa. If you change Mataroa and let others use it over the network, the AGPL asks you to offer them your changed source. The Python packages it uses are open source under the BSD, MIT, Apache 2.0, LGPL, MPL 2.0 and PSF licences. There is no per user licence fee. The AMI price covers cloudimg's build, 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
The other versions on this listing are on the developer and API tools guide.