Deployment guide

Mataroa

Overview

This guide covers the deployment and configuration of Mataroa on Ubuntu 24.04 on Azure using SecureAnyCloud Azure Marketplace images. Mataroa is an open source, self-hosted, multi-user blogging platform for minimalists. You write posts in Markdown and publish a clean, fast, distraction-free and ad-free blog, with per-user blogs, a simple writer dashboard, RSS feeds, custom domains and full blog exports.

Mataroa is a plain Django application, and this image runs it natively rather than in a container. The image builds the pinned upstream source into a dedicated Python 3.13 virtual environment, served by gunicorn bound to loopback only (127.0.0.1:8000) under systemd. nginx is the sole network-facing surface: it terminates TLS on port 443 (self-signed certificate regenerated per VM on first boot) and reverse-proxies to gunicorn. Port 80 redirects to HTTPS. Blog content is stored in an on-box PostgreSQL database on the same disk.

Secure by default. Unlike a bare deployment, this image ships locked down. The whole site is additionally gated by HTTP Basic Auth as defence in depth, so a fresh instance is never left open to the internet with open blog sign-ups. A unique administrator password, database password, HTTP Basic Auth password and Django secret key are all generated on each virtual machine's first boot, written to a root-only file. There are no shared or default credentials, nothing is baked into the image, and the database ships with zero user accounts.

What you get:

  • The pinned Mataroa release, built from source into a Python 3.13 venv and run under systemd
  • gunicorn bound to loopback only (127.0.0.1:8000), so it is never exposed to the network directly
  • nginx terminating TLS on :443 and reverse-proxying to Mataroa, with per-site HTTP Basic Auth
  • Port :80 returns a 301 redirect to HTTPS, plus an unauthenticated /healthz endpoint (HTTP 200) for load balancer probes
  • An on-box PostgreSQL database with a per-VM role password created on first boot
  • A Django admin superuser created per VM (at /dja/) with a unique per-VM password
  • A self-signed TLS certificate and Basic Auth password regenerated per VM on first boot
  • Ubuntu 24.04 LTS base with the latest security patches applied at build time
  • Azure Linux Agent for cloud integration and SSH key injection
  • 24/7 SecureAnyCloud support with a guaranteed 24-hour response SLA

Prerequisites

  • An Azure subscription with permission to deploy virtual machines
  • An SSH key pair for administrative access to the VM as the azureuser account
  • A Network Security Group that allows inbound TCP 443 (HTTPS) and 80 (redirect) from the networks that should reach the service, and 22 (SSH) from your management network only
  • A recommended size of Standard_B2s or larger
1

Deploy from the Azure Portal

  • Find the Mataroa on Ubuntu 24.04 LTS image in the Azure Marketplace and select Create.
  • Choose your subscription, resource group and region.
  • Select a VM size (Standard_B2s or larger) and provide your SSH public key for the azureuser account.
  • On the Networking tab, allow inbound 443 and 80 from your users, and restrict 22 to your management network.
  • Review and create the VM. When the VM is running, browse to https://<your-public-ip>/.
2

Deploy from the Azure CLI

Deploy the image from the command line, restricting SSH to your management network. Replace the example values with your own:

# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:mataroa-ubuntu-24-04:default:latest
az vm create \
  --resource-group my-resource-group \
  --name mataroa \
  --image sac1647283583153:mataroa-ubuntu-24-04:default:latest \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_rsa.pub \
  --public-ip-sku Standard
# Allow the web UI from your users and SSH from your management network only
az vm open-port --resource-group my-resource-group --name mataroa --port 443 --priority 1001
az network nsg rule create --resource-group my-resource-group --nsg-name mataroaNSG \
  --name allow-ssh --priority 1002 --destination-port-ranges 22 \
  --source-address-prefixes <your-mgmt-cidr> --access Allow --protocol Tcp

When the VM is running, browse to https://<public-ip>/.

3

Retrieve your per-instance credentials

Each VM generates its own credentials on first boot, written to a root-only file. Read them over SSH:

ssh azureuser@<vm-ip> 'sudo cat /root/mataroa-credentials.txt'

The file contains, for this specific VM: the site URL, the HTTP Basic Auth username and password (MATAROA_BASIC_USER / MATAROA_BASIC_PASSWORD), and the Django admin username and password (MATAROA_ADMIN_USER / MATAROA_ADMIN_PASSWORD). Keep them secret. Nothing is baked into the image.

4

Sign in

Browse to https://<vm-ip>/. Because the certificate is self-signed per VM, your browser will show a certificate warning the first time; accept it to proceed (or place a CA-signed certificate in front, as covered in Step 10). Your browser then prompts for the HTTP Basic Auth credentials from Step 3. This outer gate keeps the instance private until you decide to open it.

Once past the Basic Auth prompt, Mataroa's own sign-in page appears. Enter the admin username and password from Step 3.

5

The writer dashboard

After signing in, Mataroa presents the writer dashboard, with links to create a new post, manage images, pages, the newsletter and analytics, and to configure your blog settings, import and export.

6

Write a post in Markdown

Click New post to open the editor. Give the post a title, optionally set a publication date (leave it blank to keep the post as a draft, or use a future date to schedule it), and write the body in Markdown. Mataroa renders headings, lists, blockquotes, links, images and syntax-highlighted code blocks.

7

Your published blog

Save the post and it is published to your blog. Each user's blog lives on their own subdomain of your configured domain (see Step 8), and every post renders as a clean, fast, ad-free page with an RSS feed and an optional email subscription.

8

Set your domain and open sign-ups

Mataroa is a multi-user platform: each author signs up and gets their own blog at username.<your-domain>. Out of the box the image serves on the VM's public IP and is closed behind Basic Auth. To run it as a public blogging service:

Point a domain (with a wildcard DNS record, *.example.com, for per-user subdomains) at the VM.

Set DOMAIN to your domain in the environment file and update the trusted origins:

bash sudo sed -i 's/^DOMAIN=.*/DOMAIN=example.com/' /opt/mataroa/mataroa.env sudo sed -i 's#^CSRF_TRUSTED_ORIGINS=.*#CSRF_TRUSTED_ORIGINS=https://example.com#' /opt/mataroa/mataroa.env sudo systemctl restart mataroa

  • To let visitors read blogs and to let authors sign up, remove the Basic Auth gate: delete the two auth_basic lines from /etc/nginx/sites-available/sac-mataroa and run sudo systemctl reload nginx. Sign-ups are controlled by SIGNUPS_ENABLED in the environment file (1 = open, 0 = closed).

The Django admin, for moderation and user management, is always available at https://<your-site>/dja/ with the admin credentials from Step 3.

9

Verify the deployment

SSH to the VM and confirm the services are running and gunicorn is bound to loopback only:

systemctl is-active postgresql nginx mataroa
sudo ss -tlnp | grep -E ':8000|:443 |:80 ' | sed 's/users:.*//'

All three services report active, and gunicorn listens on 127.0.0.1:8000 only. nginx is the only component exposed on :80 and :443.

Confirm the TLS termination, the HTTP-to-HTTPS redirect and the Basic Auth gate:

DOMAIN=$(sudo grep '^DOMAIN=' /opt/mataroa/mataroa.env | cut -d= -f2-)
curl -s -o /dev/null -w 'port 80 -> HTTP %{http_code}\n' http://127.0.0.1/ curl -sk -o /dev/null -w 'healthz -> HTTP %{http_code}\n' https://127.0.0.1/healthz curl -sk -o /dev/null -w 'unauthenticated -> HTTP %{http_code}\n' -H "Host: $DOMAIN" https://127.0.0.1/
BP=$(sudo grep '^MATAROA_BASIC_PASSWORD=' /root/mataroa-credentials.txt | cut -d= -f2-)
curl -sk -u mataroa:"$BP" -o /dev/null -w 'with Basic Auth -> HTTP %{http_code}\n' -H "Host: $DOMAIN" https://127.0.0.1/

Port 80 returns 301, the unauthenticated /healthz probe returns 200, an unauthenticated request to the site returns 401, and a request carrying the per-VM Basic Auth credentials returns 200.

Prove the full admin login round-trip end to end. The built-in check reads this VM's admin password and confirms the landing page loads, the Django admin is protected, a wrong password is rejected, and the correct per-VM password authenticates into the admin:

sudo /usr/local/sbin/mataroa-verify-login.sh
10

Replace the certificate for production

The per-VM certificate is self-signed, so browsers warn on first use. For a public deployment, put a real certificate in front. The simplest path is to point a DNS name at the VM and obtain a free certificate with Certbot:

# Point https://<your-domain> at this VM first, then:
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d <your-domain> -d '*.<your-domain>'

Alternatively, terminate TLS at an Azure Application Gateway or a load balancer in front of the VM and forward to port 443.

11

Configuration and email

Service and environment. The gunicorn service is mataroa.service; its environment (the per-VM SECRET_KEY, DATABASE_URL, DOMAIN and CSRF origins) is in /opt/mataroa/mataroa.env. The nginx site is /etc/nginx/sites-available/sac-mataroa.

Rotate the admin password from the Django admin at /dja/, or with sudo /usr/local/sbin/mataroa-manage changepassword admin.

Email. The image ships with the console email backend so notification and password-reset flows do not fail without a mail relay. To send real email (newsletter delivery, password resets), configure an SMTP relay such as Postmark in settings and set the email environment variables, then restart the service.

Database. PostgreSQL runs on the box, bound to loopback. Back it up with sudo -u postgres pg_dump mataroa.

Security notes

No default login. A unique admin password, database password, Basic Auth password and Django secret key are generated on each VM's first boot into a root-only file. The database ships with zero user accounts.

Loopback-only application. gunicorn binds to 127.0.0.1:8000 and is only reachable through the nginx TLS reverse proxy on :443. Keep it that way and let nginx (or a load balancer) be the sole network-facing surface.

Defence in depth. HTTP Basic Auth gates the whole site out of the box so a fresh instance is never exposed with open sign-ups. Remove it deliberately (Step 8) only when you are ready to serve blogs publicly.

Restrict access. Allow 443 only from the networks that need it, keep 22 restricted to your management network, and replace the self-signed certificate with a CA-signed one for production.

HTTPS is load bearing. Reaching the platform over plaintext HTTP would expose the login password. Always reach it over TLS.

Support

This image is backed by 24/7 SecureAnyCloud support with a guaranteed 24-hour response SLA. Contact sales@secureanycloud.com for assistance. Mataroa is open source software distributed under the GNU Affero General Public License v3.0 (AGPL-3.0) and is free; the SecureAnyCloud charge covers packaging, hardening, security patching, image maintenance and support.

Deploy on Azure

Launch Mataroa on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure