Isso 0.14.0
Overview
This guide covers launching and using the Isso 0.14.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. 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. 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 you get:
- 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
The default SSH user is ubuntu. The architecture is x86_64, and the root volume is 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, with one SQLite file and two gunicorn workers. Smaller types such as t3.small also serve a busy blog.
Connecting to your instance
For Isso 0.14.0 on Ubuntu 24.04 (operating system Ubuntu 24.04 LTS), the SSH user is ubuntu.
Launch the instance
Option A: AWS Marketplace console
- Open the SecureAnyCloud 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.
- When 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.
- Configure the 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 open to everyone who reads your site, and 80 too, which only redirects.
- Choose your VPC and subnet and leave the metadata defaults, because 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. Replace 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, because 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).
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, which is the instance's public DNS name when it has one. It creates the TLS certificate for that name and generates the admin password and the hash salt. It then 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.txtISSO_URL=https://ec2-44-203-203-198.compute-1.amazonaws.com/ ISSO_URL= ISSO_ADMIN_URL= ISSO_ADMIN_PASSWORD= 600 root:root- 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.
Confirm Isso is running
After first boot, the Isso and nginx services 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"; echoactive 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}}Prove the login and the web defaults
These checks show the following. 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.shadmin 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 onlyAllow your website
This step makes Isso work on your own site. Isso accepts comments only from the sites in its allowed list, which 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 the list 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' doneisso-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.comUse your own site in place of https://blog.example.com. 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.
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. 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 rejects 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:
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 command 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. You can order the list, group it by thread or search by URL:
Pending comments are deleted after 30 days (purge-after). To publish comments immediately instead, set enabled = false under [moderation] in /etc/isso/isso.cfg with sudo nano, then run sudo systemctl restart isso.
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 and updates nginx's accepted name and sign-in guard. It makes https://comments.example.com the first allowed origin and Isso's public address. It records the name in /etc/sac/isso-hostname, which is kept across reboots, and updates ISSO_URL:
sudo isso-set-hostname comments.example.comisso-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. Then run sudo systemctl reload nginx.
For Let's Encrypt, port 80 already serves /.well-known/acme-challenge/ from /var/www/letsencrypt. 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.
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-passwordTo choose your own, set password under [admin] in /etc/isso/isso.cfg with sudo nano (at least 20 characters), then run sudo systemctl restart isso. 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. The file is mode 0640, readable only by root and the isso service account.
Check the security controls
These checks confirm the following. 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-preflightsubject=CN = ec2-44-203-203-198.compute-1.amazonaws.com, O = sac 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)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.dbok 6 comment(s) 600 root:root /root/isso-backup.dbCopy the file off the instance, then delete the copies on the instance. For example, run sudo cp /root/isso-backup.db /tmp/ && sudo chown ubuntu /tmp/isso-backup.db, then scp the file from your workstation.
To restore, run sudo systemctl stop isso. Put the file at /var/lib/isso/comments.db with owner isso:isso and mode 0640. Then 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>, because the environment has its own pip. Then remove the compiler packages again and restart Isso.
Troubleshooting
The widget stays empty on my site. The cause is 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
SecureAnyCloud 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. SecureAnyCloud is not affiliated with or endorsed by them.
Deploy on AWS
Launch Isso 0.14.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