Isso 0.14.0 on AWS User Guide
Overview
This guide covers launching and using the Isso 0.14.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg forums and social networks listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
Isso is a lightweight open source comment server, a self-hosted alternative to Disqus. You add one script tag to your site's page template and every page gets its own comment thread: readers post without an account, reply, vote and format with Markdown, and you approve, edit or delete comments from Isso's admin interface. All comments live in one SQLite database on your instance, so no third party sees your readers or their conversations.

What is on the image:
- Isso 0.14.0 (MIT) from PyPI, verified by SHA-256, in a virtual environment at
/opt/issowhere every Python package is installed hash-pinned - gunicorn 26.0.0 running Isso as the unprivileged
issouser on127.0.0.1:8080only, under the systemd unitisso.service - nginx as the only network-facing service: HTTPS on port 443 with a self-signed certificate created for your instance on first boot, port 80 redirecting to HTTPS
- A per-instance admin password, generated on first boot into a root-only file
isso-sites, which lists, adds and removes the websites allowed to embed your comments, andisso-set-hostname, which serves Isso under your own DNS name and re-issues the certificate for it- A landing page at
https://<your-instance>/with the embed snippet and a live comment thread to try
Default user: ubuntu · Architecture: x86_64 · Root volume: 8 GiB gp3
Security posture
- No default admin password. Isso's sample configuration ships a published admin password. This image is captured with the admin interface switched off and an empty password, which Isso can never match. On each instance's first boot a 32-character admin password and a 24-character email-hash salt are generated, and only then is the admin interface switched on.
- A session-signing key of your own. Isso signs admin sessions with a key it stores inside its SQLite database when the database is created. The image carries no database, so your instance mints its own key on first start and no two instances can accept each other's sessions.
- Moderation stays on. New comments wait in the admin queue until you approve them, and a reader's earlier approved email does not skip the queue. Isso's own guard also limits each reader address to 2 new comments a minute.
- Exact allowed sites, never a wildcard. Isso answers cross-origin requests (CORS) only for this server's own address and the sites you add with
isso-sites. A request from any other origin gets this server's own address back, which browsers refuse. Wildcards and paths are rejected. - The address comes from configuration, never from a request. First boot reads the instance's public DNS name from EC2 instance metadata (IMDSv2) and writes it into Isso's and nginx's configuration. nginx answers only for that name (any other
Hostheader gets its connection closed) and replaces any client-suppliedX-Forwarded-For,X-Real-IPorForwardedheader with the real peer address. - Rate-limited, same-origin admin sign-in. nginx limits sign-in attempts per address (
429after a short burst) and refuses a sign-inPOSTfrom another site (403). The admin session cookie isSecure,HttpOnlyandSameSite=Strict. - Fails closed.
isso-preflightruns before Isso and before nginx on every start. It refuses an empty, default or short admin password, a world-readable configuration, an empty or wildcard site list, a non-HTTPS public address, Isso's published default salt and any trusted proxy other than this host. nginx also waits for first boot to finish and for the instance's own certificate. - Only nginx faces the network. Isso listens on loopback only. nginx uses TLS 1.2 or 1.3 and sends HSTS.
- No metadata access from the services.
isso.serviceandnginx.serviceare denied the instance metadata service with systemdIPAddressDeny, and the AMI is registered withImdsSupport v2.0, so instances refuse token-less (IMDSv1) metadata requests. - No build toolchain and no core dumps. The C compiler used to build Isso's Markdown extension is removed in the same build step. Isso, nginx and the first-boot units run with core dumps disabled.
- Per-instance identity. SSH host keys, the machine ID and the TLS certificate are generated fresh on every instance.
Prerequisites
- An AWS account and permission to launch EC2 instances
- An EC2 key pair in the Region you launch in
- A website where you can edit the page template, to embed the comment widget
Recommended instance type: m5.large, the listing default. Isso is light: one SQLite file and two gunicorn workers, so smaller types such as t3.small also serve a busy blog.
Connecting to your instance
| Version | Operating system | SSH user |
|---|---|---|
| Isso 0.14.0 on Ubuntu 24.04 | Ubuntu 24.04 LTS | ubuntu |
Step 1: Launch the instance
Option A: AWS Marketplace console
- Open the cloudimg Hardened Discourse, Mastodon & Forums and Social Networks AMIs listing on AWS Marketplace and choose View purchase options, then Subscribe. One subscription covers every version in the listing.
- Once the subscription is active, choose Launch your software.
- Choose One-click launch from AWS Marketplace (or Launch from EC2 Console for the full launch wizard). In Version, choose Isso 0.14.0 on Ubuntu 24.04, then choose your Region.
- Pick an instance type (the software charge is $0.04 per vCPU per hour) and your key pair.
- Security group: the listing's default group opens SSH (22), HTTP (80) and HTTPS (443). Restrict SSH to your own IP address. Your readers' browsers load the comments straight from this server, so keep 443 (and 80, which only redirects) open to everyone who reads your site.
- Choose your VPC and subnet, leave the metadata defaults (the image already requires IMDSv2), then choose Launch.
Option B: AWS CLI
Run this on your own workstation. It creates a security group that allows SSH from your management address and HTTPS from anywhere, then launches the instance. Replace the AMI ID with the one shown for your Region on the Marketplace launch page, and the VPC, subnet, key pair and CIDR with your own:
SG=$(aws ec2 create-security-group --group-name isso \
--description "Isso comment server" --vpc-id vpc-0123456789abcdef0 \
--query GroupId --output text)
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 22 --cidr <your-mgmt-cidr>
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 443 --cidr 0.0.0.0/0
aws ec2 authorize-security-group-ingress --group-id "$SG" --protocol tcp --port 80 --cidr 0.0.0.0/0
aws ec2 run-instances \
--image-id ami-0123456789abcdef0 \
--instance-type m5.large \
--key-name my-key-pair \
--security-group-ids "$SG" \
--subnet-id subnet-0123456789abcdef0 \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=isso}]'
You do not need --metadata-options: the AMI already requires IMDSv2. Associate an Elastic IP if you want the address to survive a stop and start, and point a DNS name at it (Step 8).
Step 2: Connect and find your login
Connect as ubuntu with the key pair you chose:
ssh -i ~/.ssh/my-key-pair.pem ubuntu@<public-ip>
First boot finishes within a minute of launch. It sets the name Isso is served as (the instance's public DNS name when it has one), creates the TLS certificate for it, generates the admin password and the hash salt, checks the configuration with isso-preflight and writes /root/isso-credentials.txt (mode 0600, root). Only then do Isso and nginx start. These are the entries in that file (the password is not shown here):
sudo grep -E '^ISSO_URL=' /root/isso-credentials.txt
sudo grep -oE '^[A-Z_]+=' /root/isso-credentials.txt
sudo stat -c '%a %U:%G' /root/isso-credentials.txt
ISSO_URL=https://ec2-44-203-203-198.compute-1.amazonaws.com/
ISSO_URL=
ISSO_ADMIN_URL=
ISSO_ADMIN_PASSWORD=
600 root:root
| Value | What it is for |
|---|---|
ISSO_URL |
The address of this comment server, used in your embed snippet |
ISSO_ADMIN_URL |
The moderation interface (/admin/) |
ISSO_ADMIN_PASSWORD |
The admin password, generated on this instance (Isso's admin has no username) |
To read the password, run sudo cat /root/isso-credentials.txt in your own session and store it in your password manager.
Step 3: Confirm Isso is running
First boot, Isso and nginx are active. Only sshd and nginx listen beyond loopback: Isso (gunicorn) is on 127.0.0.1:8080. The public /config endpoint shows the reader-facing settings:
systemctl is-active isso-firstboot.service isso.service nginx.service
/opt/isso/bin/python -I -c 'import importlib.metadata as m; print("isso", m.version("isso"), "/ gunicorn", m.version("gunicorn"))'
sudo ss -Htlnp | awk '{print $4, $6}' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort -u
H=$(sudo sed -n 's/^set \$isso_host "\(.*\)";$/\1/p' /etc/nginx/isso-host.conf)
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/config"; echo
active
active
active
isso 0.14.0 / gunicorn 26.0.0
0.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"))
127.0.0.1:8080 users:(("gunicorn"),("gunicorn"),("gunicorn"))
127.0.0.53%lo:53 users:(("systemd-resolve"))
127.0.0.54:53 users:(("systemd-resolve"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))
{"config": {"reply-to-self": false, "require-email": false, "require-author": false, "reply-notifications": false, "gravatar": false, "feed": false}}
Step 4: Prove the login and the web defaults
A signed-out visit to /admin/ gets the sign-in form, a wrong password gets no session, a sign-in POST from another site is refused, an unknown Host header gets no answer, and plain HTTP redirects to HTTPS. The last line runs the image's own round-trip check, which signs in with this instance's password without printing it:
H=$(sudo sed -n 's/^set \$isso_host "\(.*\)";$/\1/p' /etc/nginx/isso-host.conf)
R=(--resolve "$H:443:127.0.0.1")
curl -sk "${R[@]}" -o /dev/null -w 'admin page, signed out -> %{http_code}\n' "https://$H/admin/"
curl -sk "${R[@]}" -o /dev/null -D /tmp/isso-login.hdr -w 'wrong password -> %{http_code}' -H "Origin: https://$H" \
--data-urlencode 'password=not-the-password' "https://$H/login/"
echo ", admin session cookies issued: $(grep -ci '^set-cookie: *admin-session' /tmp/isso-login.hdr)"; rm -f /tmp/isso-login.hdr
curl -sk "${R[@]}" -o /dev/null -w 'cross-origin sign-in POST -> %{http_code}\n' -H 'Origin: https://evil.example' \
--data-urlencode 'password=x' "https://$H/login/"
curl -sk -o /dev/null -w 'unknown Host -> %{http_code}\n' -H 'Host: evil.example' https://127.0.0.1/ || true
curl -s -o /dev/null -w 'plain http -> %{http_code}\n' -H "Host: $H" http://127.0.0.1/
sudo /usr/local/sbin/isso-roundtrip.sh
admin page, signed out -> 200
wrong password -> 200, admin session cookies issued: 0
cross-origin sign-in POST -> 403
unknown Host -> 000
plain http -> 301
OK round-trip https://ec2-44-203-203-198.compute-1.amazonaws.com/: /config 200; anonymous /admin/ is the login form; a wrong password gets no session; the per-VM password -> 302 /admin/ with a Secure HttpOnly SameSite=Strict admin-session; a foreign Origin is answered with https://ec2-44-203-203-198.compute-1.amazonaws.com only
Step 5: Allow your website
This is the step that makes Isso work on your own site. Isso accepts comments only from the sites in its allowed list, which is what stops anyone else embedding your comment server. First boot puts this server's own address in the list, so the landing-page demo works straight away; your site is not in it yet.
Add each site that will embed comments, as an exact origin (https:// plus the host name, and a port if it is not the default). isso-sites restarts Isso for you. This example adds https://blog.example.com and shows that Isso now answers CORS for it, while any other origin gets only this server's own address back:
sudo isso-sites add https://blog.example.com
sudo isso-sites list
H=$(sudo sed -n 's/^set \$isso_host "\(.*\)";$/\1/p' /etc/nginx/isso-host.conf)
R=(--resolve "$H:443:127.0.0.1")
for i in $(seq 1 30); do curl -skf "${R[@]}" -o /dev/null "https://$H/config" && break; sleep 1; done
for o in https://blog.example.com https://evil.example; do
printf 'Origin %s -> ' "$o"
curl -sk "${R[@]}" -o /dev/null -D - -H "Origin: $o" "https://$H/config" | grep -i '^access-control-allow-origin' | tr -d '\r'
done
isso-sites: allowed origins are now https://ec2-44-203-203-198.compute-1.amazonaws.com https://blog.example.com
https://ec2-44-203-203-198.compute-1.amazonaws.com (this server)
https://blog.example.com
Origin https://blog.example.com -> access-control-allow-origin: https://blog.example.com
Origin https://evil.example -> access-control-allow-origin: https://ec2-44-203-203-198.compute-1.amazonaws.com
Use your own site in place of https://blog.example.com, and add the http:// origin as well if the site is also served over plain HTTP. Remove a site with sudo isso-sites remove https://blog.example.com. A wildcard such as https://*.example.com or a URL with a path is refused.
Step 6: Embed the widget in your site
Add these two lines to your page template where the comments should appear, using the address from ISSO_URL:
<script src="https://comments.example.com/js/embed.min.js" data-isso="https://comments.example.com/"></script>
<section id="isso-thread" data-title="My first post"></section>
Each page gets its own thread, keyed by its URL path. data-title names the thread; without it, Isso fetches your page once to read its <title>, which needs your site to be reachable from the instance. To show comment counts on an index page, add <script src="https://comments.example.com/js/count.min.js"></script> and link to /my-post/#isso-thread.
Isso's cookies are SameSite=None; Secure, so the widget works when your site and the comment server are on different domains, as long as both are served over HTTPS. Until you install a trusted certificate (Step 8), each reader's browser would reject the self-signed one, so do that before you go live.
The landing page at ISSO_URL shows the snippet and a live thread you can post to:

Step 7: Moderate comments
Moderation is on: a new comment is accepted (202) in mode 2, waiting for approval, and the public thread does not show it until you approve it. This posts a test comment the way a reader's browser does:
sudo sed -n '/^\[moderation\]/,/^$/p' /etc/isso/isso.cfg | grep -v '^#'
H=$(sudo sed -n 's/^set \$isso_host "\(.*\)";$/\1/p' /etc/nginx/isso-host.conf)
R=(--resolve "$H:443:127.0.0.1")
P="%2Fguide-test-$(date +%s)"
jq -nc '{text:"Hello from the user guide", author:"Reader", title:"Guide test"}' |
curl -sk "${R[@]}" -o /tmp/isso-new.json -w 'new comment -> %{http_code}' -H 'Content-Type: application/json' \
-H "Origin: https://$H" --data-binary @- "https://$H/new?uri=$P"
echo ", mode $(jq -r .mode /tmp/isso-new.json) (2 = waiting for approval)"; rm -f /tmp/isso-new.json
curl -sk "${R[@]}" "https://$H/?uri=$P" | jq -c '{published_comments: .total_replies}'
[moderation]
enabled = true
approve-if-email-previously-approved = false
purge-after = 30d
new comment -> 202, mode 2 (2 = waiting for approval)
{"published_comments":0}
Browse to ISSO_ADMIN_URL (https://<your-instance>/admin/). The certificate is self-signed until Step 8, so your browser warns once. Isso's admin has a single password field; enter ISSO_ADMIN_PASSWORD:

The dashboard opens on Pending. Each comment shows its thread, author and time, with Edit, Delete and Validate. Validate publishes the comment; Valid and Staled list the rest, and you can order the list, group it by thread or search by URL:

Pending comments are deleted after 30 days (purge-after). If you prefer to publish comments immediately, set enabled = false under [moderation] in /etc/isso/isso.cfg with sudo nano and run sudo systemctl restart isso.
Step 8: Use your own DNS name and certificate
Point a DNS record (for example comments.example.com) at the instance's Elastic IP, then tell Isso the name. The helper re-issues the self-signed certificate for it, updates nginx's accepted name and sign-in guard, makes https://comments.example.com the first allowed origin and Isso's public address, records the name in /etc/cloudimg/isso-hostname (kept across reboots) and updates ISSO_URL:
sudo isso-set-hostname comments.example.com
isso-set-hostname: Isso is served at https://comments.example.com/
Until you set a name, the instance follows its own public DNS name: if it comes back from a stop and start with a new address, the certificate and names are updated at boot.
Readers' browsers need a certificate they trust. Replace /etc/ssl/isso/isso.crt and /etc/ssl/isso/isso.key with one from your own CA or from Let's Encrypt (keep the key mode 0600, owner root), set the name with isso-set-hostname first so the automatic refresh leaves your certificate alone, and run sudo systemctl reload nginx. For Let's Encrypt, port 80 already serves /.well-known/acme-challenge/ from /var/www/letsencrypt, so after sudo apt install certbot, sudo certbot certonly --webroot -w /var/www/letsencrypt -d comments.example.com works without stopping nginx; copy or link the issued files into place and reload nginx after each renewal.
Step 9: Change the admin password
isso-reset-password generates a new random password, writes it to /root/isso-credentials.txt and restarts Isso; the old password stops working at once:
sudo isso-reset-password
To choose your own, set password under [admin] in /etc/isso/isso.cfg with sudo nano (at least 20 characters) and run sudo systemctl restart isso. Then update or delete /root/isso-credentials.txt so it does not hold a stale password. The configuration file keeps the password in plain text because Isso compares it as a string; it is mode 0640, readable only by root and the isso service account.
Step 10: Check the security controls
The certificate was made for this instance on first boot, Isso runs as the isso user with no new privileges and no core dumps, both services are denied the metadata service, the configuration, database and TLS key are closed to other users, and the preflight accepts the current configuration:
H=$(sudo sed -n 's/^set \$isso_host "\(.*\)";$/\1/p' /etc/nginx/isso-host.conf)
openssl s_client -connect 127.0.0.1:443 -servername "$H" </dev/null 2>/dev/null | openssl x509 -noout -subject -enddate
systemctl show -p User,IPAddressDeny,NoNewPrivileges,LimitCORE isso.service
systemctl show -p IPAddressDeny --value nginx.service
sudo stat -c '%a %U:%G %n' /etc/isso/isso.cfg /var/lib/isso /var/lib/isso/comments.db /etc/ssl/isso/isso.key
sudo isso-preflight
subject=CN = ec2-44-203-203-198.compute-1.amazonaws.com, O = cloudimg
notAfter=Feb 9 19:26:48 2029 GMT
IPAddressDeny=168.63.129.16/32 169.254.169.254/32
LimitCORE=0
User=isso
NoNewPrivileges=yes
168.63.129.16/32 169.254.169.254/32
640 root:isso /etc/isso/isso.cfg
750 isso:isso /var/lib/isso
640 isso:isso /var/lib/isso/comments.db
600 root:root /etc/ssl/isso/isso.key
isso-preflight: OK (per-VM admin password, 2 allowed origin(s), https public endpoint, per-VM salt)
Step 11: Back up your comments
Every thread, comment and the session-signing key are in one SQLite file. .backup takes a consistent copy while Isso is running; the copy is root-only because it holds the signing key:
sudo sh -c "umask 077 && sqlite3 /var/lib/isso/comments.db \".backup '/root/isso-backup.db'\""
sudo sqlite3 -readonly /root/isso-backup.db 'PRAGMA integrity_check;' 'SELECT count(*) || " comment(s)" FROM comments;'
sudo stat -c '%a %U:%G %n' /root/isso-backup.db
ok
6 comment(s)
600 root:root /root/isso-backup.db
Copy the file off the instance (for example sudo cp /root/isso-backup.db /tmp/ && sudo chown ubuntu /tmp/isso-backup.db, then scp from your workstation), and delete the copies on the instance afterwards. To restore, run sudo systemctl stop isso, put the file at /var/lib/isso/comments.db with owner isso:isso and mode 0640, and run sudo systemctl start isso. EBS snapshots of the root volume also capture the database.
Maintenance
- Logs:
sudo journalctl -u issofor Isso,/var/log/nginx/for HTTPS access,sudo journalctl -u isso-firstbootfor first boot. - Restart:
sudo systemctl restart isso, orsudo systemctl restart nginx. Both runisso-preflightfirst. - Settings: Isso's options are in
/etc/isso/isso.cfg(see the server configuration reference). Email notifications neednotify = smtpunder[general]and an[smtp]section with your mail server. Restart Isso after a change; the preflight refuses an unsafe setting and names it. - Operating system updates: Ubuntu's unattended security upgrades stay on, so nginx, Python and the rest of the OS keep receiving patches. Apply the rest with
sudo apt update && sudo apt upgradein a maintenance window. - Upgrading Isso: read the release notes and take a backup first. Isso is in the virtual environment at
/opt/isso; its Markdown extension needs a C compiler to build, so installbuild-essentialandpython3-devfor the upgrade, runsudo /opt/isso/bin/python -m pip install --upgrade isso==<version>(the environment has its own pip), remove the compiler packages again and restart Isso.
Troubleshooting
The widget stays empty on my site. Almost always Step 5: the site is not in the allowed list. Your browser's console shows a CORS error naming this server's address. Run sudo isso-sites list, add the exact origin your pages are served from (scheme and host, and port if any) and reload the page. Also check that your site and the comment server are both on HTTPS with a certificate the browser trusts (Step 8).
A comment is refused with 403. Isso's guard allows 2 new comments a minute and 3 top-level comments per thread from one reader address, and no reply to your own comment while it can still be edited. Raise ratelimit or direct-reply under [guard] in /etc/isso/isso.cfg if that is too strict for your readers.
A comment fails with "URI is not accessible and no title was provided". The thread did not exist yet and Isso could not fetch your page to read its title. Add data-title to the <section id="isso-thread"> element (Step 6).
The admin page says nothing or the password is refused. Use ISSO_ADMIN_PASSWORD from /root/isso-credentials.txt exactly, over https://. After several quick attempts nginx answers 429 for a short while; wait a minute and try again. If the password is lost, run sudo isso-reset-password (Step 9).
Isso or nginx does not start. sudo journalctl -u isso --no-pager | tail -20 shows the preflight's reason, for example a password shorter than 20 characters, a wildcard in the site list or a world-readable configuration. Fix the setting and run sudo systemctl restart isso nginx.
Support
cloudimg provides 24/7 support for this image by email and live chat: launching and connecting, embedding and allowed sites, moderation, TLS and hostnames, and upgrades.
Isso is developed by Martin Zimmermann and the Isso contributors and is released under the MIT licence. The licence text and a pointer to the source ship on the instance in /usr/share/doc/isso/. Ubuntu is a registered trademark of Canonical Ltd. All other product and company names are trademarks or registered trademarks of their respective holders. cloudimg is not affiliated with or endorsed by them.