Remark42 1.18.0
Overview
Remark42 is a self-hosted, privacy-friendly comment engine and an alternative to Disqus. You add a small script to your site's page template, and every page gets its own comment thread. Readers comment, reply, vote, edit for a few minutes after posting, and format with Markdown. You moderate by deleting, pinning and blocking, and by making threads read-only, and you can export and import comments, including from Disqus and WordPress. Remark42 is one Go program with its comment store (BoltDB) and its web widget built in.
This guide covers the Remark42 1.18.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud forums and social networks listing on AWS Marketplace, and you select it as the software version when you subscribe and launch. The image is set up to run Remark42 securely on EC2.
What you get:
- No default account and no built-in secret. The image ships with no administrator password, no signing secret, no comment store and no TLS key. On your instance's first boot, a 48-character
SECRETand a 24-character administrator password are generated into a file that only root can read. Remark42 signs every reader's session with it and keys its hashes with it. - Anonymous readers, no social logins. Readers sign in with a name of their choice and no password, using Remark42's anonymous sign-in. No OAuth client secret ships, so every social login (GitHub, Google and the rest) and e-mail sign-in stay off until you add your own application keys (Step 9). Names such as
adminandmoderatorare reserved. - Only your sites can embed it. Remark42 answers cross-origin requests (CORS) and allows framing of its widget (
frame-ancestors) only for this instance and the sites you add withremark42-sites, never for*. Any other site's page that tries to post, vote or edit with a reader's session is refused. - The administrator API refuses other sites. nginx refuses an admin API request that another website's page sends from your browser, even with the right password.
- Rate-limited sign-in. nginx answers
429to repeated sign-ins and to repeated administrator password attempts from one address, in addition to Remark42's own limiter. - An image proxy that cannot reach your private network. Remark42 can fetch images for comments through its own proxy. On every boot, the image refuses the
remark42account, at the kernel level, any connection to the EC2 instance metadata service; to link-local, private (RFC 1918), carrier-grade NAT and reserved addresses; to your VPC's own address ranges; and to all IPv6. Public images still come through. - HTTPS from the first boot. nginx serves Remark42 on port 443 with a certificate that is generated on your instance's first boot, and port 80 redirects to HTTPS. Nothing answers until first boot has finished.
- Localhost-only listener. Remark42 listens on
127.0.0.1:8080, runs as its own unprivileged user with no Linux capabilities, and takes the client address only from nginx. - Built from source, with current fixes. Remark42 1.18.0 is built from its tagged release source with Go 1.27.3, and two of its Go libraries are raised to their fixed releases (
golang.org/x/netandgithub.com/klauspost/compress). The web widget is upstream's own 1.18.0 build, checked file by file. - Fully patched base. The image is built on a fully patched Ubuntu 24.04 LTS, with unattended security upgrades switched on.
What is on the image: Remark42 1.18.0, installed at /opt/remark42/remark42, with its configuration in /etc/remark42/remark42.env, its secrets in /etc/remark42/secrets.env, and its comments, avatars, pictures and daily backups in /var/lib/remark42. The image also contains nginx, the tools remark42-sites, remark42-admin, remark42-set-hostname and remark42-selftest, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu. Architecture: x86_64. Recommended instance type: m5.large.
Prerequisites
Before you launch the instance, make sure you have:
- An AWS account subscribed to the listing, an EC2 key pair, and a VPC subnet with internet access
- A security group that allows TCP 22 (SSH) from your own network. It must also allow TCP 443 (Remark42) and TCP 80 (redirects to HTTPS) from your readers, which for a public website means from anywhere.
- A website whose page template you can edit, so that you can embed the comment widget
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, and then choose Continue to Configuration. Select Remark42 1.18.0 on Ubuntu 24.04 as the software version, select your Region, and choose Launch through EC2. Choose m5.large, your key pair, a subnet, and a security group that meets the prerequisites.
Remark42 itself needs very little, so a smaller instance such as t3.small serves a busy blog.
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=remark42}]'Connect to your instance
Connect over SSH as the ubuntu user, which is the SSH user for Remark42 1.18.0 on Ubuntu 24.04:
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
First boot sets the host name and generates the certificate, the SECRET and the administrator password. The egress backstop then loads, and Remark42 and nginx start:
systemctl is-active remark42-firstboot sac-remark42-egress remark42 nginx
sudo test -f /var/lib/sac/data/remark42-firstboot.done && echo FIRSTBOOT_DONEAll four units are active, and the first-boot marker is present:
active active active active FIRSTBOOT_DONEOnly SSH and nginx listen on the network, and Remark42 listens on localhost only:
sudo ss -lntpH | awk '{print $4, $6}' | sed -E 's/users:\(\("([^"]+)".*/\1/' | grep -v '127.0.0.5' | sort -u0.0.0.0:22 sshd 0.0.0.0:443 nginx 0.0.0.0:80 nginx 127.0.0.1:8080 remark42 [::]:22 sshd [::]:443 nginx [::]:80 nginxGet your URL and administrator password
sudo cat /var/lib/sac/data/remark42-credentials.txtOnly root can read the file. It holds the URL, the site ID, and the administrator's user name and password:
# Remark42 1.18.0 - generated on first boot by remark42-firstboot.service
# The administrator password is unique to this instance; none was baked into the image. Keep this file secret. REMARK42_URL=https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/ REMARK42_SITE_ID=remark REMARK42_ADMIN_USER=admin REMARK42_ADMIN_PASSWORD=<REMARK42_ADMIN_PASSWORD>
#
# Readers comment through the widget you embed in your site (https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/web/ is a demo page). They sign in
# anonymously (a chosen name, no password); no social login is configured. The administrator is HTTP basic 'admin' with
# REMARK42_ADMIN_PASSWORD on the admin API (moderation, export, import), e.g.:
#
sudo remark42-admin GET '/api/v1/admin/blocked?site=remark'
# Allow your site to embed the widget:
sudo remark42-sites add https://your-blog.example
# Self-test:
sudo remark42-selftest Change the served name:
sudo remark42-set-hostname <your-domain>
# User guide: https://secureanycloud.com/stacks/remark42-1-18-0/The URL uses the instance's public DNS name from EC2 instance metadata. If you open https://<public-ip>/, you are redirected to that name.
The image includes a self-test that runs through nginx. It checks the redirect, the health and configuration endpoints, the widget and the content security policy. It confirms that the admin API refuses a request with no password, a wrong password or another site, and accepts the right password. It also checks the cross-origin and unknown-host guards, that the image proxy refuses instance metadata, the service's privileges, the egress backstop and the metadata block:
sudo remark42-selftestSELFTEST-OK https 301 + healthz + ping + config v1.18.0-sac (anonymous sign-in) + widget scripts carry https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com + CSP frame-ancestors without * and no X-Frame-Options + admin API 401 without credentials, 401 wrong password, 200 per-instance password + 403 cross-site admin + 403 cross-origin POST + 444 unknown host + no CORS for an unlisted origin + image proxy refused metadata (404) + remark42 uid remark42, no capabilities, NoNewPrivs, listening 127.0.0.1:8080 only, egress backstop loaded + metadata 000 inside remark42 and nginx (control 200)Try the demo page
Open REMARK42_URL followed by web/ (for example https://ec2-<public-ip-dashed>.<region>.compute.amazonaws.com/web/) in your browser. The certificate is self-signed, so your browser asks you to confirm it the first time. Step 11 replaces it.
The page is Remark42's own demo with a live comment thread. Choose Sign In, enter a name of at least three letters or digits in the Anonymous form, and choose Submit. Then write a comment and choose Send.
A reader can reply, vote, edit a comment for five minutes, and delete it, and Sort by orders the thread. Each reader's anonymous identity comes from the name and the address they connect from, and it lasts as long as their browser keeps its session.
Allow your website
This step makes Remark42 work on your own site. Remark42 shows its widget in a frame inside your page and calls its API from your page. Both are allowed only for this instance and the sites you list, which stops anyone else from embedding your comment server or acting with your readers' sessions. A new instance lists no site, so the list command prints nothing:
sudo remark42-sites listAdd each site that will embed comments as an exact origin: https:// plus the host name, and a port if it is not 443. remark42-sites writes the list into Remark42's ALLOWED_HOSTS (its frame-ancestors policy) and into nginx's CORS and request guards, and then restarts Remark42:
sudo remark42-sites add https://blog.example.com
sudo remark42-sites remove https://blog.example.comThe tool refuses a wildcard (* or https://*.example.com), an http:// origin, localhost and a URL with a path.
The trade-off: each listed site can frame the widget and call the API with a reader's session from its own pages. List only sites that you control, and add each host name that your blog answers on, for example both https://example.com and https://www.example.com.
Embed the widget in your site
Add the following snippet to your page template where you want the comments to appear. Use the host from REMARK42_URL:
<div id="remark42"></div> <script> var remark_config = { host: 'https://comments.example.com', site_id: 'remark' }; </script> <script>!function(e,n){for(var o=0;o<e.length;o++){var r=n.createElement("script"),c=".js",d=n.head||n.body;"noModule"in r?(r.type="module",c=".mjs"):r.async=!0,r.defer=!0,r.src=remark_config.host+"/web/"+e[o]+c,d.appendChild(r)}}(remark_config.components||["embed"],document);</script>Each page gets its own thread, keyed by its URL. To show comment counts on an index page or the latest comments in a sidebar, add "counter" or "last-comments" to components, as described in the Remark42 documentation.
The widget keeps the reader's session in its own storage and sends it in a header, so it works when your site and the comment server are on different domains. Readers' browsers refuse the self-signed certificate, so install a trusted certificate (Step 11) before you go live.
Moderate and manage comments
The administrator is the HTTP basic user admin with REMARK42_ADMIN_PASSWORD, and works through Remark42's admin API. remark42-admin calls the API through nginx for you. It reads the password from the credentials file and never puts it on a command line. This command lists the blocked readers, and a new instance has none:
sudo remark42-admin GET '/api/v1/admin/blocked?site=remark'nullYou can do other tasks the same way. The <comment-id> and <user-id> values come from the comment's JSON, for example from /api/v1/find?site=remark&url=<page-url>&format=plain:
sudo remark42-admin DELETE '/api/v1/admin/comment/<comment-id>?site=remark&url=<page-url>'
sudo remark42-admin PUT '/api/v1/admin/user/<user-id>?site=remark&block=1&ttl=24h'
sudo remark42-admin PUT '/api/v1/admin/readonly?site=remark&url=<page-url>&ro=1'
sudo remark42-admin PUT '/api/v1/admin/pin/<comment-id>?site=remark&url=<page-url>&pin=1'To moderate from inside the widget instead, name a reader account as an administrator with ADMIN_SHARED_ID. Anonymous identities depend on the address that a reader connects from, so use a social or e-mail sign-in for this (Step 9). Sign in, copy your user ID from your profile in the widget, and add ADMIN_SHARED_ID=<user-id> to /etc/remark42/remark42.env. Then run sudo systemctl restart remark42.
Sign-in options and notifications
The image enables anonymous sign-in only (AUTH_ANON=true in /etc/remark42/remark42.env). Anonymous comments suit a blog that wants no accounts. They cannot be tied to a person, so moderation is how you deal with abuse.
Readers who reach the instance from a private address, inside your VPC or over a VPN, all appear to Remark42 as the proxy. Two such readers who choose the same name share one anonymous identity, so use a social or e-mail sign-in for them.
To add GitHub, Google, Microsoft, Telegram or e-mail sign-in, or e-mail and Telegram notifications, register your own application with the provider. Keep its secret out of remark42.env, because everyone on the instance can read that file, and give the secret to Remark42 in a file that only root can read:
sudo install -m 0600 -o root -g root /dev/null /etc/remark42/oauth.env
sudo nano /etc/remark42/oauth.env
# AUTH_GITHUB_CID=... and AUTH_GITHUB_CSEC=...
sudo systemctl edit remark42
# [Service] then EnvironmentFile=/etc/remark42/oauth.env
sudo systemctl restart remark42The provider's callback URL is REMARK42_URL followed by auth/github/callback (or google, and so on). The Remark42 parameters reference lists all settings. If a notification target, such as an SMTP relay or a webhook receiver, is on a private address in your VPC, allow it in the egress list (Step 10).
The image proxy and outbound connections
Remark42 fetches avatars from sign-in providers and, through /api/v1/img, images that readers link to. Because readers supply those addresses, the remark42 account is refused any connection to the instance metadata service; to link-local, private, carrier-grade NAT, benchmarking (198.18.0.0/15), IETF protocol (192.0.0.0/24), multicast and reserved addresses; to this VPC's own IPv4 ranges, which the instance reads from instance metadata on every boot; and over every IPv6 connection.
If the VPC ranges cannot be read, every new IPv4 connection of the remark42 account is refused until they can. Remark42 does not start without these rules:
sudo iptables -S SAC-REMARK42-EGRESS | grep -c -- '-j REJECT'
sudo ip6tables -S SAC-REMARK42-EGRESS | tail -n 1
sudo cat /run/sac/remark42-vpc-cidrs24 -A SAC-REMARK42-EGRESS -j REJECT --reject-with icmp6-adm-prohibited 172.31.0.0/16The count of refused IPv4 ranges includes your VPC's ranges, and the VPC range shown is your own. To let Remark42 reach a service on a private address, such as an SMTP relay or webhook receiver in your VPC, add its range to /etc/sac/remark42-egress-allow, one CIDR per line, and reload. Metadata and link-local addresses can never be allowed:
echo 10.20.30.40/32 | sudo tee -a /etc/sac/remark42-egress-allow
sudo systemctl reload sac-remark42-egressUse your own domain and certificate
- In your DNS, point
<your-domain>at the instance's public IP address with an A record, for example forcomments.example.com. An Elastic IP keeps that address stable. - Tell the instance to use the domain. This sets the name that nginx accepts, the request guards and Remark42's
REMARK_URL, and generates a new self-signed certificate for the domain:
sudo remark42-set-hostname <your-domain>- For a certificate your readers' browsers trust, put the certificate chain for
<your-domain>in/etc/ssl/remark42/remark42.crtand its private key in/etc/ssl/remark42/remark42.key(root only, mode 600), and runsudo systemctl reload nginx. Alternatively, put the instance behind an Application Load Balancer that uses an AWS Certificate Manager certificate and forwards to port 443 on the instance.
Comments are stored against the page URL and the site ID, so changing the comment server's name keeps every thread.
Data and backups
Everything Remark42 keeps is in /var/lib/remark42: var/remark.db (the comment store), avatars/, pictures/ (images readers upload) and backup/, where Remark42 writes a compressed export of every site each day and keeps the last ten. To take an export now, run:
sudo remark42-admin GET '/api/v1/admin/export?site=remark&mode=file' -o /root/remark42-export.json.gz
sudo ls -l /root/remark42-export.json.gz | awk '{print $1, $3, $NF}'-rw-r--r-- root /root/remark42-export.json.gzTo restore an export on a new instance, unpack it and send it to the admin import endpoint. An import replaces all of that site's comments:
sudo gunzip -k /root/remark42-export.json.gz
sudo remark42-admin POST '/api/v1/admin/import?site=remark&provider=native' --data-binary @/root/remark42-export.jsonTo bring existing comments into a new instance before readers start commenting, use provider=disqus, provider=wordpress or provider=commento with an export file from those services.
An EBS snapshot of the instance's volume is also a complete backup. Keep /etc/remark42/secrets.env with your backups if you want readers' sessions to survive a move. Without it, readers simply sign in again.
Security checks
nginx and Remark42 cannot reach the instance metadata service:
for u in nginx remark42; 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 remark42 168.63.129.16/32 169.254.169.254/32 fd00:ec2::254/128Remark42 runs as remark42 with no Linux capabilities and cannot gain any:
P=$(systemctl show -p MainPID --value remark42) ps -o user= -p "$P" grep -E '^(CapEff|CapBnd|CapAmb|NoNewPrivs)' /proc/"$P"/statusremark42 CapEff: 0000000000000000 CapBnd: 0000000000000000 CapAmb: 0000000000000000 NoNewPrivs: 1These checks confirm the following behaviour. The admin API refuses a request without the password and a request that another site's page sends. A change from a site that you have not listed is refused and gets no CORS answer. A request for a host name that the instance does not serve gets no answer at all. Plain HTTP redirects to HTTPS.
H=$(sudo sed -n 's#^REMARK42_URL=https://\([^/]*\)/$#\1#p' /var/lib/sac/data/remark42-credentials.txt)
curl -sk -o /dev/null -w 'admin API without the password: %{http_code}\n' --resolve "$H:443:127.0.0.1" -H "Origin: https://$H" "https://$H/api/v1/admin/blocked?site=remark"
curl -sk -o /dev/null -w 'admin API from another site: %{http_code}\n' --resolve "$H:443:127.0.0.1" -H 'Sec-Fetch-Site: cross-site' "https://$H/api/v1/admin/blocked?site=remark"
curl -sk -o /dev/null -w 'comment from an unlisted site: %{http_code}\n' --resolve "$H:443:127.0.0.1" -X POST -H 'Content-Type: application/json' -H 'Origin: https://evil.example' --data '{}' "https://$H/api/v1/comment?site=remark"
curl -sk -D - -o /dev/null --resolve "$H:443:127.0.0.1" -H 'Origin: https://evil.example' "https://$H/api/v1/config?site=remark" | grep -ci '^access-control-allow-origin' | sed 's/^/CORS headers for an unlisted site: /'
curl -sk -o /dev/null -w 'unknown host name: %{http_code}\n' --resolve evil.example:443:127.0.0.1 https://evil.example/ || true
curl -s -o /dev/null -w 'http: %{http_code}\n' --resolve "$H:80:127.0.0.1" "http://$H/web/"admin API without the password: 401 admin API from another site: 403 comment from an unlisted site: 403 CORS headers for an unlisted site: 0 unknown host name: 000 http: 301Only root can read the secrets, the credentials and the TLS key, and only root and the remark42 service user can read the comment store. systemd reads the secrets file as root and hands SECRET and the password to Remark42 in its environment, which only remark42 and root can read:
P=$(systemctl show -p MainPID --value remark42)
sudo stat -c '%a %U:%G %n' /var/lib/sac/data/remark42-credentials.txt /etc/remark42/secrets.env /etc/remark42/remark42.env /etc/ssl/remark42/remark42.key /var/lib/remark42 /var/lib/remark42/var/remark.db /proc/"$P"/environ600 root:root /var/lib/sac/data/remark42-credentials.txt 600 root:root /etc/remark42/secrets.env 644 root:root /etc/remark42/remark42.env 600 root:root /etc/ssl/remark42/remark42.key 700 remark42:remark42 /var/lib/remark42 600 remark42:remark42 /var/lib/remark42/var/remark.db 400 remark42:remark42 /proc/<pid>/environOther notes:
- The administrator password is stored in
/etc/remark42/secrets.env(ADMIN_PASSWD) and in the credentials file. To change it, edit both files withsudo nano, using 24 letters and digits, and then runsudo systemctl restart remark42. The image checks the format before it starts Remark42. ChangingSECRETsigns every reader out. - Rate limits. From one address, nginx allows about ten sign-ins a minute and twelve requests a minute that carry an administrator password, with a small burst. It answers
429beyond that. - Client addresses. nginx replaces any
X-Forwarded-FororX-Real-IPheader that a client sends with the address the client connected from, and Remark42 trusts those headers only from nginx on127.0.0.1. Remark42's log records a keyed hash of the address, not the address itself. - NVMe host identity. No NVMe-oF host identity ships in the image. If you install
nvme-cli, its host NQN and host ID belong to your instance. - Network access. Keep port 22 open only to your own network. Ports 80 and 443 serve your readers.
- The build.
/usr/share/doc/sac-remark42/BUILD-INFO.txtrecords the release, the Go toolchain and the dependency updates. The filesgo.mod,go.sumandgo-version-m.txtnext to it list every library in the program, andweb-manifest.txtlists the widget's files. - Logs. nginx logs, which omit query strings, are in
/var/log/nginx. Remark42 logs to the journal (journalctl -u remark42).
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 Remark42 releases arrive as new versions of this listing, built and checked the same way. To move to a new version, take an export (Step 12), launch the new version, add your sites and import the export. Read the Remark42 release notes before you move.
Support and licensing
Remark42 is open source under the MIT licence (Umputun and contributors). The licence text is in /usr/share/doc/sac-remark42/, and the source code is at github.com/umputun/remark42. The Go and web libraries that it is built with are open source, mainly under the MIT, Apache 2.0 and BSD licences, and go-version-m.txt and web-manifest.txt in the same directory list them.
There is no per-user licence fee. The AMI price covers SecureAnyCloud's build, hardening, first-boot setup and support.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24-hour response SLA
For the other versions in this listing, see the forums and social networks guide.
Deploy on AWS
Launch Remark42 1.18.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