Mataroa 1.5.0
Overview
This guide covers how to launch and use the Mataroa 1.5.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud 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, and pages carry no tracking or advertising.
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>).
What you get:
- HTTPS from the first boot. nginx serves Mataroa on port 443 with a certificate that is generated on the first boot of your instance. 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. Its password is generated on the first boot of your instance, so 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. It writes that password, together with the gate password, to/var/lib/sac/data/mataroa-credentials.txt(root-only). Mataroa stores only a PBKDF2 hash. The Django 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 stays 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. The database role of Mataroa 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. First boot sets the Mataroa domain from the public DNS name of the instance. nginx refuses requests for host names that 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 the Django 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). No request, post or SQL statement can read the metadata or role credentials of the instance.
- A pinned, audited release. Mataroa 1.5.0 is installed from the official release archive (checksum pinned), with one later upstream security fix backported (only blog owners can post author comments on their own blog). It runs on the Python 3.12 of Ubuntu 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.
- 24/7 support from SecureAnyCloud.
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 that is 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, because the database is not on the network.
- To publish blogs to readers, a domain that you control, for example
example.com, where you can create two DNS records (Step 7).
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, select your Region, and choose Launch through EC2.
Choose m5.large, your key pair, a security group as described above and a subnet. The database lives on the root volume. Mataroa stores uploaded images in the database, so increase the volume size at launch if your writers will upload many images.
To launch with the AWS CLI instead, 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}]'Connect to your instance
Version SSH user
Mataroa 1.5.0 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot sets the host name and certificate, initialises the database and creates the secrets. Mataroa and nginx then start:
systemctl is-active postgresql@16-main mataroa.socket mataroa nginx mataroa-firstboot
sudo test -f /var/lib/sac/data/mataroa-firstboot.done && echo FIRSTBOOT_DONEConfirm that all five units are active and the first-boot marker is present:
active active active active active FIRSTBOOT_DONEConfirm that 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 -u0.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.5432Get your URL and passwords
sudo cat /var/lib/sac/data/mataroa-credentials.txtOnly root can read the file. It holds the URL, the user name and password of the gate, and the user name and password of the administrator 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://secureanycloud.com/stacks/mataroa-1-5-0/The URL uses the public DNS name of the instance from EC2 instance metadata. Opening https://<public-ip>/ sends you on to that name.
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. Alternatively, 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. The sign-in page of Mataroa 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 the Sign up link of Mataroa, but it leads nowhere until you allow sign-ups (Step 8).
The image includes a self-test. Through nginx, it passes the gate and signs editor in with the real login form of Mataroa. It checks that common guesses such as admin/admin are refused (without locking the account), and it checks the security settings:
sudo mataroa-selftestSELFTEST-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 hostWrite your first post
The dashboard is the home of the writer. It offers 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 save the post without publishing it, or set a future date to publish the post then. Drag images into the editor to upload them. Choose Save:
Under Blog settings, set the title of the blog, its description (shown on the home page of the blog), a footer, whether comments are enabled, and the theme.
Publish your blog on your own domain
Mataroa serves the blog of each user on a subdomain of the domain of the instance. For example, 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 public IP address of the instance. An Elastic IP keeps that address stable. The domain must have exactly two labels, such asexample.com. This is the Mataroa model: the dashboard on the domain and the blogs one level below it. - Tell Mataroa to use the domain. This sets the Mataroa domain, the names that 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:
Each post is at /blog/<post-slug>/, with links to subscribe by RSS or e-mail:
- For a certificate that 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). Then 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 nginxWith the gate off, the dashboard and the Django admin (/dja/) are protected by the sign-in of Mataroa, so 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.
Add writers
Self sign-up is off, so the administrator creates accounts in the Django admin. On the dashboard, choose Django Admin, then Users and Add user. Enter a user name (it becomes the subdomain of the blog) 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 the sign-up page of Mataroa. 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 nginxThe 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.conf16.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 rejectPosts, pages, images, comments and sessions all live in the mataroa database, so back it up as one unit. To back up to a directory of your choice, run:
sudo mkdir -p <backup-dir> && sudo -u postgres pg_dump -Fc mataroa | sudo tee <backup-dir>/mataroa-$(date +%F).dump >/dev/nullAlso keep copies off the instance, for example in Amazon S3, or take EBS snapshots of the volume. Writers can export their own blog from the dashboard (Export blog).
Turn on e-mail (optional)
E-mail is off, so 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 the values of your relay (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' SAC_SMTP_HOST=email-smtp.<region>.amazonaws.com SAC_SMTP_PORT=587 SAC_SMTP_USER=<smtp-user> SAC_SMTP_PASSWORD=<smtp-password> EOF
sudo systemctl restart mataroaAllow outbound TCP 587 from the instance to your relay. The Mataroa management commands send newsletters and the daily summaries (sudo mataroa-manage processnotifications, sudo mataroa-manage mailsummary). The image does not schedule these commands, so add a systemd timer or cron entry for them if you use newsletters.
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; donenginx 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/128The gate answers before Mataroa does. nginx refuses a state-changing request that the page of another web site sends, and a request for a host name that 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/ || true401 403 000Other notes:
- Passwords. Mataroa stores PBKDF2 hashes (the Django default). The administrator and gate passwords that 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. The Django 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. The Mataroa request log is in the journal (
journalctl -u mataroa). The nginx logs are in/var/log/nginx.
Keep it up to date
Ubuntu security updates install automatically. To apply all updates now, run:
sudo apt-get update && sudo apt-get upgrade -yNew Mataroa releases arrive as new versions of this listing, built and checked the same way. To move to a new version, 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/sac-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 that Mataroa 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 the SecureAnyCloud build, hardening, first-boot setup and support.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24 hour response SLA
The other versions on this listing are on the developer and API tools guide.
Deploy on AWS
Launch Mataroa 1.5.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com