Remark42
Overview
Remark42 is an open-source, self-hosted commenting engine by Umputun. It gives any page a comment section that you own. You add a small snippet of JavaScript to your template, and readers get threaded discussions, replies, voting, markdown, emoji and RSS. You get a moderation plane to pin, block, verify and remove content.
This image runs Remark42 1.16.4 on Ubuntu 24.04 LTS, and it is deliberately small. The whole engine is a single static Go binary with the web frontend compiled into it, and the entire data tier is a BoltDB file on the VM itself. There is no separate database server to run, no cache tier, and no third-party service holding your readers' conversations.
Secure by default. This image ships no working credential at all. On the first boot of every VM, remark42-firstboot.service generates two independent random values unique to that VM:
- a JWT signing key (
SECRET), which signs every authentication token - an admin password (
ADMIN_PASSWD), which authenticates the built-inadminuser over HTTP Basic auth
The signing key matters more than it first appears. A key baked into a shared image would let anyone who deployed that same image forge an administrator session on every VM built from it, so neither value exists in the image.
Remark42 is wired to refuse to start until both values have been generated, using two independent levers. First, remark42.service declares a hard Requires dependency on the first-boot rotation. Second, a preflight check re-runs on every start and refuses a marker token or a weak value. This also catches a half-edited configuration, not only a failed first boot.
Authentication without an external identity provider. Remark42 normally federates to OAuth providers such as Google or GitHub, and each one needs an OAuth application that you register yourself. So that the appliance is usable the moment it boots, this image enables Remark42's built-in anonymous provider. A reader picks a display name and comments, with no third-party dependency.
The administration plane is unaffected. The per-VM admin password and signing key protect it, and RESTRICTED_NAMES prevents an anonymous visitor from claiming the admin name. Step 13 covers adding OAuth or email providers and turning anonymous access off for a locked-down deployment.
What you get:
- Remark42 1.16.4, installed from the pinned upstream release binary and verified against its published SHA256 checksum at build time
- Remark42 bound to
127.0.0.1:8080, so the engine has no public listener of its own - nginx on port
80as a reverse proxy for the engine, so it is reachable without a port suffix, plus an unauthenticated/healthzendpoint for Azure Load Balancer health probes - A per-VM admin password and JWT signing key generated on first boot, with the password recorded at
/root/remark42-credentials.txt(mode0600) remark42.service,nginx.serviceand a one-shotremark42-firstboot.serviceas systemd units, enabled and active- An Ubuntu 24.04 LTS base, patched at build time, with unattended security upgrades enabled
- 24/7 SecureAnyCloud support
Prerequisites
You need an active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region.
Standard_B2s (2 vCPU / 4 GiB RAM) is comfortable for a busy site. Remark42 serves comments from an embedded key-value store and does no rendering work of its own.
Configure the network security group (NSG) to allow inbound 22/tcp from your management network and 80/tcp for readers. You never need to open port 8080, because the engine listens only on loopback and nginx is the sole way in.
Step 1 - Deploy from the Azure Marketplace
In the Azure portal, search the Marketplace for Remark42 on Ubuntu 24.04 LTS by SecureAnyCloud, and select Create.
In the wizard, choose your subscription, resource group and region, and keep the recommended Standard_B2s size. Select SSH public key authentication with the username azureuser, and allow inbound ports 22 and 80.
Select Review + create, then Create.
Step 2 - Deploy from the Azure CLI
Accept the Marketplace terms once per subscription, create the VM and open port 80:
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:remark42-ubuntu-24-04:default:latest
az vm create \
--resource-group <your-rg> \
--name remark42 \
--image sac1647283583153:remark42-ubuntu-24-04:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name remark42 --port 80 --priority 1010Step 3 - Connect to your VM
Connect over SSH as azureuser.
ssh azureuser@<vm-public-ip>Step 4 - Confirm the services are running
Three units make up the appliance: the one-shot remark42-firstboot.service that generated this VM's credentials, the remark42.service engine itself, and nginx.service in front of it. Check that all three are active and that the expected ports are listening:
systemctl is-active remark42 nginx remark42-firstbootsudo ss -tlnp | grep -E ':80 |:8080 'The output looks like this:
active active active LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=8709,fd=5),...) LISTEN 0 4096 127.0.0.1:8080 0.0.0.0:* users:(("remark42",pid=8700,fd=6))Remark42 is bound to 127.0.0.1, not to a public interface. Everything reaches it through nginx.
Step 5 - Retrieve your per-VM admin password
The password generated on this VM's first boot is recorded in a root-only file. The commands below mask the value, so you can read the file safely on a shared screen:
sudo ls -l /root/remark42-credentials.txtsudo sed -E 's/^(REMARK42_ADMIN_PASSWORD=).*/\1********/' /root/remark42-credentials.txt | head -12The output looks like this:
-rw------- 1 root root 1121 Jul 16 15:57 /root/remark42-credentials.txt
# Remark42 - generated on first boot by remark42-firstboot.service
# These values are unique to this VM. Store them somewhere safe. REMARK42_URL=http://20.51.157.213/ REMARK42_ADMIN_USER=admin REMARK42_ADMIN_PASSWORD=<set-at-first-boot>The password is written exactly once in that file, so the sed command fully redacts it for a screenshot or a support ticket. The JWT signing key is not repeated in the file at all. It lives only in /etc/remark42/remark42.env, because it signs every token and belongs in exactly one place.
Step 6 - Confirm the engine is serving
nginx serves an unauthenticated /healthz endpoint for load balancer probes. Remark42 publishes its version and enabled authentication providers at /api/v1/config.
The admin API requires the password from Step 5. An unauthenticated request is rejected with 401, while an authenticated one returns 200:
curl -sI http://127.0.0.1/healthz | head -1
curl -s 'http://127.0.0.1/api/v1/config?site=remark' | jq -r '.version, (.auth_providers|tostring)'PW=$(sudo grep '^REMARK42_ADMIN_PASSWORD=' /root/remark42-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w "no auth -> HTTP %{http_code}\n" 'http://127.0.0.1/api/v1/admin/blocked?site=remark'
curl -s -o /dev/null -w "with pw -> HTTP %{http_code}\n" -u "admin:$PW" 'http://127.0.0.1/api/v1/admin/blocked?site=remark'The output looks like this:
HTTP/1.1 200 OK v1.16.4-6e7820d2-20260710T031048 ["anonymous"] no auth -> HTTP 401 with pw -> HTTP 200Reading the password into $PW instead of typing it keeps it out of your shell history and scrollback. To confirm which identity the password authenticates as, run:
PW=$(sudo grep '^REMARK42_ADMIN_PASSWORD=' /root/remark42-credentials.txt | cut -d= -f2-)
curl -s -u "admin:$PW" 'http://127.0.0.1/api/v1/user?site=remark' | jq -c '{id, name}'Step 7 - Confirm no default credential shipped
Remark42 ships no default password upstream, and this image adds none. Before first boot, both secrets sit in the configuration as a non-secret marker token, and the engine will not start while that token is present.
You can verify this yourself. The marker token appears nowhere in the running configuration, it does not authenticate, and remark42.service declares a hard Requires dependency on the first-boot rotation:
sudo grep -c SAC_SET_ON_FIRSTBOOT /etc/remark42/remark42.env curl -s -o /dev/null -w "admin:SAC_SET_ON_FIRSTBOOT -> HTTP %{http_code}\n" -u admin:SAC_SET_ON_FIRSTBOOT 'http://127.0.0.1/api/v1/admin/blocked?site=remark' systemctl show remark42 -p Requires --valueThe output looks like this:
0 admin:SAC_SET_ON_FIRSTBOOT -> HTTP 401 remark42-firstboot.service system.slice sysinit.target -.mountStep 8 - Set your public URL
This is the one setting you should change before going live. Remark42 builds every avatar URL, picture URL and emitted link from REMARK_URL, so it must be the address your readers use.
First boot resolves it to the VM's public IP, which is fine for a quick look. A real deployment sits behind your own domain name. Set it with:
sudo sed -i -E 's#^REMARK_URL=.*#REMARK_URL=https://comments.example.com#' /etc/remark42/remark42.env
sudo systemctl restart remark42To check the current value at any time, run:
grep '^REMARK_URL=' /etc/remark42/remark42.envTo serve Remark42 over HTTPS, terminate TLS at nginx on this VM (or at an Azure Application Gateway in front of it) and set REMARK_URL to the https:// address.
Avatar URLs already stored in existing comments keep the value that was in force when the comment was written. Set REMARK_URL before you collect real traffic.
Step 9 - Open the comment interface
Browse to http://<vm-public-ip>/. The root redirects to /web/, which is Remark42's own demo page with a live comment thread. Readers see threaded discussion, avatars, relative timestamps, per-comment scores and a sort control.
Step 10 - Sign in and post a comment
Select Sign In under the comment box. This image enables the built-in anonymous provider, so readers can pick a display name and start commenting immediately. There is no OAuth application to register and no account to create.
After you sign in, the composer shows your identity and a markdown toolbar, with Preview and Send. Comments support markdown, code blocks, emoji and an editing grace period, and readers can subscribe to a thread by RSS.
The widget follows the host page's colour scheme and ships a dark theme.
Step 11 - Embed the widget in your own site
Add the snippet below to the template of any page you want comments on. Replace the host with your own REMARK_URL from Step 8. Place a <div id="remark42"></div> where the thread should appear:
<script> var remark_config = { host: "https://comments.example.com", site_id: "remark", components: ["embed"] }; (function(c) { for (var i = 0; i < c.length; i++) { var d = document, s = d.createElement("script"); s.src = remark_config.host + "/web/" + c[i] + ".js"; s.defer = true; (d.head || d.body).appendChild(s); } })(remark_config.components || ["embed"]); </script> <div id="remark42"></div>site_id is the site name Remark42 was started with, which is remark on this image.
The counter component renders a comment count for an index page, and last-comments renders a list of recent comments. A documentation tree can therefore show counts on the index and full threads on each article.
Step 12 - Moderate from the admin API
The admin password authenticates the built-in admin user against every endpoint under /api/v1/admin/. You can list blocked users, block someone for a period, or pin a comment. This example lists blocked users:
PW=$(sudo grep '^REMARK42_ADMIN_PASSWORD=' /root/remark42-credentials.txt | cut -d= -f2-)
curl -s -u "admin:$PW" 'http://127.0.0.1/api/v1/admin/blocked?site=remark' | jq -c .Remark42 also scores comments. It collapses anything below the low-score threshold automatically, and you can pin the most useful answers to the top of a thread.
To export the whole site as a backup file at any time, run:
PW=$(sudo grep '^REMARK42_ADMIN_PASSWORD=' /root/remark42-credentials.txt | cut -d= -f2-)
curl -s -u "admin:$PW" 'http://127.0.0.1/api/v1/admin/export?site=remark&mode=file' -o /tmp/remark42-export.gz ls -lh /tmp/remark42-export.gzStep 13 - Add an OAuth provider and lock down anonymous access
For a public site, you will usually want readers to sign in with a real identity. Register an OAuth application with your provider and set its callback URL to <your REMARK_URL>/auth/github/callback. Then add the credentials and turn anonymous access off:
sudo tee -a /etc/remark42/remark42.env >/dev/null <<'EOF' AUTH_GITHUB_CID=your-client-id AUTH_GITHUB_CSEC=your-client-secret EOF
sudo sed -i 's/^AUTH_ANON=true/AUTH_ANON=false/' /etc/remark42/remark42.env
sudo systemctl restart remark42Remark42 supports Google, GitHub, Microsoft, Facebook, Yandex, Twitter, Patreon, Discord, a custom OAuth2 provider, Telegram, and email-based sign-in. /api/v1/config reports which providers are live, so you can confirm that the change took effect:
curl -s 'http://127.0.0.1/api/v1/config?site=remark' | jq -r '.auth_providers|tostring'To make a signed-in user an administrator, add their Remark42 user ID to ADMIN_SHARED_ID in the same file and restart. The admin basic-auth credential keeps working alongside it as an emergency path.
Maintenance
The whole data tier is a BoltDB file plus the avatar and image directories. All of it lives under /var/lib/remark42/var, owned by the unprivileged remark42 service account:
sudo ls -lh /var/lib/remark42/var/A backup is therefore a file copy: stop the service, copy the directory, and start the service again. Remark42 also writes its own periodic backups into /var/lib/remark42/var/backup, and the export endpoint in Step 12 produces a portable dump.
The Ubuntu base has unattended security upgrades enabled, so OS patches apply automatically. This image pins a specific verified binary instead of tracking a moving tag. To move to a newer Remark42 release, watch the upstream releases feed.
Support
SecureAnyCloud provides 24/7 support for this image. For help, contact sales@secureanycloud.com and include your Azure subscription ID and the VM name.
Deploy on Azure
Launch Remark42 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