Developer Tools Azure

Mataroa on Ubuntu 24.04 on Azure User Guide

| Product: Mataroa on Ubuntu 24.04 LTS on Azure

Overview

This guide covers the deployment and configuration of Mataroa on Ubuntu 24.04 on Azure using cloudimg 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 is included:

  • 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); never network-exposed 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 minted 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 seamless cloud integration and SSH key injection

  • 24/7 cloudimg 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 allowing 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

Step 1: Deploy from the Azure Portal

  1. Locate the Mataroa on Ubuntu 24.04 LTS image in the Azure Marketplace and select Create.

  2. Choose your subscription, resource group and region.

  3. Select a VM size (Standard_B2s or larger) and provide your SSH public key for the azureuser account.

  4. On the Networking tab, allow inbound 443 and 80 from your users, and restrict 22 to your management network.

  5. Review and create. When the VM is running, browse to https://<your-public-ip>/.

Step 2: Deploy from the Azure CLI

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

az vm create \
  --resource-group my-resource-group \
  --name mataroa \
  --image <marketplace-image-urn> \
  --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>/.

Step 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.

The per-VM mataroa-credentials.txt showing the site URL, the Basic Auth username and the admin username, with all passwords redacted, alongside the baked Django and Python versions and the pinned Mataroa source commit

Step 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 9). Your browser will then prompt 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.

The Mataroa sign in page served over TLS, showing username and password fields and a Login button

Step 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.

The Mataroa writer dashboard after signing in, showing a New post link and Content, Manage and Account sections with links for Images, Pages, Newsletter, Analytics, Blog settings and Export blog

Step 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.

The Mataroa Create a new post editor, showing a Title field, a Publication date field with a set as draft option, and a Markdown Content textarea filled with a heading, bold text, a bullet list and a fenced code block, with a Save button

Step 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.

A published Mataroa blog post titled Hello, Mataroa, rendered over TLS with the Markdown converted to a heading, paragraphs, a bullet list, a blockquote and a syntax-highlighted Python code block, a publication date, and Subscribe via RSS or Email links

Step 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:

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

  2. 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

  1. 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/cloudimg-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.

Step 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.

systemctl reports postgresql, nginx and mataroa all active, and ss shows gunicorn bound to 127.0.0.1:8000 loopback only with nginx listening on port 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.

curl showing port 80 returning a 301 redirect, the healthz probe returning 200, an unauthenticated request returning 401 and a request with Basic Auth returning 200, with the password redacted

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

The mataroa-verify-login.sh output printing OK, having proven the landing page returned 200, the Django admin was protected, a wrong password was rejected and the per-VM admin password authenticated

Step 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.

Step 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/cloudimg-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 cloudimg support with a guaranteed 24-hour response SLA. Contact support@cloudimg.co.uk for assistance. Mataroa is open source software distributed under the GNU Affero General Public License v3.0 (AGPL-3.0) and is free; the cloudimg charge covers packaging, hardening, security patching, image maintenance and support.