Application Servers AWS

Algernon 1.17.12 on AWS User Guide

| Product: Algernon 1.17.12 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Algernon 1.17.12 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg web servers listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Algernon is a small, self-contained web and application server written in Go. One static binary serves static files, renders Markdown to HTML on request, runs server-side Lua pages and Pongo2 or Amber templates, and keeps data in an embedded Bolt database. A complete dynamic site needs no PHP-FPM, no separate application server and no database daemon.

How this image is put together, and what cloudimg adds:

  • Algernon 1.17.12, the security release. This version fixes GHSA-xc5m-rvcg-wv5h, where a symbolic link in the served directory could expose any file the server can read, and GHSA-w53j-93hc-qq47. It is also built with a newer Go toolchain and libraries that fix several HTTP/2 denial-of-service issues. The symbolic link fix is an option upstream (--strict-symlinks), and this image turns it on.
  • HTTPS from the first boot. On your instance's first boot a certificate is generated for this instance alone, and the site is served on port 443 over HTTP/2. Port 80 redirects to HTTPS. No certificate or key is baked into the image.
  • nginx as the public edge, Algernon behind it. Algernon listens on 127.0.0.1:8080 only. nginx terminates TLS on ports 80 and 443, answers only for the configured host name, limits each client address to 100 requests per second (with a burst of 200) and 64 connections, and closes idle and slow connections.
  • Production mode only. No interactive console, no debug mode (an error in a Lua page never shows its source to visitors), no auto-refresh, and no directory listings. Dotfiles are never served. Server configuration is read only from /etc/algernon/serverconf.lua, never from the folder you publish, and nginx never serves a serverconf.lua file.
  • Locked down service. Algernon runs as the unprivileged algernon account with no capabilities and a read-only view of the system, and it cannot write the site it serves. Lua pages can make HTTP requests, so Algernon (and nginx) are blocked from the instance metadata service, and the AMI requires IMDSv2.
  • Fail closed. nginx will not start until first boot has generated the certificate, so nothing is served before the instance is set up.
  • The official release, verified. The Algernon 1.17.12 release and its binary are checked against pinned SHA-256 checksums before installation. Ubuntu 24.04 LTS is fully patched at build time, with unattended security upgrades switched on.

Algernon has no login and no administration console, so there is no password on this image.

What is on the image: Algernon 1.17.12 (/usr/local/bin/algernon, licence and release checksums in /usr/share/doc/algernon), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or the private route your users reach it by).
  • A security group that allows TCP 22 (SSH) from your own networks, and TCP 443 (the site) and TCP 80 (redirects to HTTPS) from your visitors.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Algernon 1.17.12 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above.

With the AWS CLI (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=algernon}]'

Step 2: Connect to your instance

Version SSH user
Algernon 1.17.12 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot generates the certificate and works out the host name, then Algernon and nginx start:

systemctl is-active algernon-firstboot algernon nginx
algernon --version | head -1
active
active
active
Algernon 1.17.12

Algernon listens on the loopback address only; nginx owns ports 80 and 443:

sudo ss -lntp | grep -E ':(80|443|8080) ' | awk '{print $4, $6}' | sed -E 's/users:\(\("([a-z-]+)".*/\1/'
0.0.0.0:80 nginx
0.0.0.0:443 nginx
127.0.0.1:8080 algernon
[::]:80 nginx
[::]:443 nginx

Step 4: Find your site's address

The address the site is served at is in an information file written on first boot (it holds no secret):

sudo grep '^SITE_URL=' /root/algernon-info.txt
SITE_URL=https://ec2-54-152-46-131.compute-1.amazonaws.com/

Open SITE_URL in your browser. The certificate is self signed, so the browser asks you to confirm the first time; Step 9 shows how to use your own name and certificate. You will see the cloudimg landing page, which is itself a Lua page (/srv/algernon/index.lua) that Algernon runs on every request.

Step 5: Check HTTPS, the redirect and the host name check

These commands run on the instance and talk to nginx over the loopback address, using the configured host name:

H=$(sudo grep '^SITE_URL=' /root/algernon-info.txt | sed -E 's#^SITE_URL=https://([^/]+)/$#\1#')
curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w 'https://%{url.host}/  -> %{http_code} over HTTP/%{http_version}\n' "https://$H/"
curl -s --resolve "$H:80:127.0.0.1" -o /dev/null -w 'http://%{url.host}/   -> %{http_code} %{redirect_url}\n' "http://$H/"
curl -sk --resolve unknown.example:443:127.0.0.1 -o /dev/null -w 'unknown host -> %{http_code} (connection closed)\n' https://unknown.example/ || true
https://ec2-54-152-46-131.compute-1.amazonaws.com/  -> 200 over HTTP/2
http://ec2-54-152-46-131.compute-1.amazonaws.com/   -> 301 https://ec2-54-152-46-131.compute-1.amazonaws.com/
unknown host -> 000 (connection closed)

The certificate was generated on this instance's first boot and names the host and the instance's addresses:

H=$(sudo grep '^SITE_URL=' /root/algernon-info.txt | sed -E 's#^SITE_URL=https://([^/]+)/$#\1#')
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = ec2-54-152-46-131.compute-1.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name: 
    DNS:ec2-54-152-46-131.compute-1.amazonaws.com, IP Address:54.152.46.131, IP Address:172.31.88.106, IP Address:127.0.0.1, DNS:localhost

Step 6: See Algernon render Lua and Markdown

/status.lua is a Lua page: every value is computed when you request it, and ?n=7 is multiplied by six. /welcome.md is a plain Markdown file that Algernon turns into HTML on request:

H=$(sudo grep '^SITE_URL=' /root/algernon-info.txt | sed -E 's#^SITE_URL=https://([^/]+)/$#\1#')
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/status.lua?n=7"
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/welcome.md" | grep -o '<h2[^>]*>[^<]*</h2>' | head -1
CLOUDIMG_ALGERNON_LUA_OK:5050
echo:42
method:GET
tick:94.058883
<h2 id="cloudimg-algernon-md-h2">CLOUDIMG_ALGERNON_MD_H2</h2>

The built-in self-test checks the whole stack (redirect, landing page, Lua, Markdown, directory listing off, unknown host names refused, Algernon on loopback only):

sudo algernon-selftest
OK 301 + landing + lua + markdown + dirlist-off + unknown host refused + algernon loopback-only

Step 7: Check the hardening

Security headers on every response, with no server version:

H=$(sudo grep '^SITE_URL=' /root/algernon-info.txt | sed -E 's#^SITE_URL=https://([^/]+)/$#\1#')
curl -skI --resolve "$H:443:127.0.0.1" "https://$H/" | grep -iE '^(server|strict-transport|x-frame|x-content-type|content-security|referrer-policy)'
server: nginx
content-security-policy: connect-src 'self'; object-src 'self'; form-action 'self'
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
strict-transport-security: max-age=31536000
referrer-policy: strict-origin-when-cross-origin

A directory with no index file is not listed, a file inside it is served, and the permission-protected /admin path, dotfiles and serverconf.lua are not reachable:

H=$(sudo grep '^SITE_URL=' /root/algernon-info.txt | sed -E 's#^SITE_URL=https://([^/]+)/$#\1#')
for p in /downloads/ /downloads/README.txt /admin /.env /serverconf.lua /no-such-page; do
  printf '%-22s %s\n' "$p" "$(curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w '%{http_code}' "https://$H$p")"
done
/downloads/            404
/downloads/README.txt  200
/admin                 403
/.env                  404
/serverconf.lua        404
/no-such-page          404

A symbolic link that leads out of the site is refused (this is the 1.17.12 fix, switched on with --strict-symlinks). The test link is removed again straight away:

H=$(sudo grep '^SITE_URL=' /root/algernon-info.txt | sed -E 's#^SITE_URL=https://([^/]+)/$#\1#')
sudo ln -s /etc/passwd /srv/algernon/passwd-link
curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w 'symlink to /etc/passwd -> %{http_code}\n' "https://$H/passwd-link"
sudo rm -f /srv/algernon/passwd-link
symlink to /etc/passwd -> 404

The options Algernon runs with, the account it runs as, the metadata block on both services and the per-client limits nginx enforces:

systemctl show -p ExecStart algernon.service | grep -oE -- '--[a-z-]+(=[^ ;]+)?' | grep -vE -- '--(path|argv)' | tr '\n' ' '; echo
ps -o user=,comm= -C algernon
systemctl show -p IPAddressDeny algernon.service nginx.service | grep .
sudo nginx -T 2>/dev/null | grep -E '^\s*(limit_req |limit_conn |client_header_timeout|keepalive_timeout)' | sort -u
--httponly --addr=127.0.0.1:8080 --dir=/srv/algernon --conf=/dev/null --boltdb=/var/lib/algernon/algernon.db --log=/var/log/algernon/server.log --servername=Algernon --cache=prod --limit=5000 --noninteractive --nobanner --stricter --hide-dotfiles --strict-symlinks 
algernon algernon
IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32
IPAddressDeny=fd00:ec2::254/128 168.63.129.16/32 169.254.169.254/32
client_header_timeout 15s;
keepalive_timeout     30s;
limit_conn alg_perip 64;
limit_req zone=alg_req burst=200 nodelay;

Step 8: Publish your own site

Algernon serves whatever is in /srv/algernon: .md files are rendered as Markdown, .lua files run on the server, .amber and .po2 files are templates, and everything else is served as a static file. The folder is owned by root and the server can read but never change it. To try it, add a Markdown page and a Lua page, fetch them over HTTPS and remove them again:

H=$(sudo grep '^SITE_URL=' /root/algernon-info.txt | sed -E 's#^SITE_URL=https://([^/]+)/$#\1#')
printf '# Hello\n\nThis is **my** page.\n' | sudo tee /srv/algernon/hello.md >/dev/null
printf 'content("text/plain")\nprint("2 + 2 = " .. (2 + 2))\n' | sudo tee /srv/algernon/sum.lua >/dev/null
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/hello.md" | grep -oE '<(h1|strong)[^>]*>[^<]*</(h1|strong)>'
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/sum.lua"
sudo rm -f /srv/algernon/hello.md /srv/algernon/sum.lua
curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w 'after removal: %{http_code}\n' "https://$H/hello.md"
<h1 id="hello">Hello</h1>
<strong>my</strong>
2 + 2 = 4
after removal: 404

New files are served at once; no restart is needed. To publish a whole site, copy it from your own machine and fix the permissions on the instance:

# from your machine:
#   rsync -a --delete -e "ssh -i <your-key.pem>" ./site/ ubuntu@<public-ip>:/tmp/site/
# then on the instance:
#   sudo rsync -a --delete /tmp/site/ /srv/algernon/
#   sudo chown -R root:root /srv/algernon
#   sudo find /srv/algernon -type d -exec chmod 0755 {} +
#   sudo find /srv/algernon -type f -exec chmod 0644 {} +

Notes for your own content:

  • Directory listings are off. /srv/algernon/handler.lua answers 404 for any folder without an index file (index.lua, index.html, index.md and so on). Delete it if you want Algernon's directory listings, or replace it with your own handler. If you replace the whole folder with rsync --delete, keep a copy of it.
  • Server configuration goes in /etc/algernon/serverconf.lua (owned by root; the image ships none), then sudo systemctl restart algernon. A serverconf.lua inside /srv/algernon is ignored and never served. If you enable Algernon's user and permission system there, set your own cookie secret with SetCookieSecret() in that file rather than on the command line.
  • Lua pages run as the algernon account and can make outbound HTTP requests, but not to the instance metadata service. They can write only to /var/lib/algernon (the Bolt database) and /var/log/algernon.
  • Keep Algernon on 127.0.0.1:8080 behind nginx, and keep --strict-symlinks in /etc/systemd/system/algernon.service.

Step 9: Your own DNS name and certificate

Point your DNS name at the instance (an Elastic IP keeps the address stable across stops and starts), then tell the instance its name. This records the name, generates a new self signed certificate for it and reloads nginx; port 80 then redirects to the new name:

sudo algernon-set-hostname <your-domain>

To use a certificate your visitors' browsers trust, obtain one for your name (for example with certbot or acme.sh) and point nginx at it in /etc/nginx/sites-available/cloudimg-algernon (the two ssl_certificate lines of the second port 443 server block), then check and reload:

sudo nginx -t && sudo systemctl reload nginx

Step 10: Configuration reference

What Where
Your site /srv/algernon
Algernon options /etc/systemd/system/algernon.service (sudo systemctl daemon-reload && sudo systemctl restart algernon after a change)
Optional server configuration /etc/algernon/serverconf.lua
Bolt database and logs /var/lib/algernon/algernon.db, /var/log/algernon/server.log
nginx site (ports 80/443, host names, certificate) /etc/nginx/sites-available/cloudimg-algernon
nginx limits /etc/nginx/algernon-limits.conf, /etc/nginx/conf.d/cloudimg-algernon.conf
Served host names /etc/nginx/algernon-host.conf (written by algernon-set-hostname)
Certificate and key /etc/ssl/algernon/ (key readable by root only)
Visitor access log (real client addresses) /var/log/nginx/access.log
Site address note /root/algernon-info.txt
Licence and release checksums /usr/share/doc/algernon/

nginx passes the real visitor address to Algernon in the X-Forwarded-For and X-Real-IP headers (it overwrites anything a visitor sends), so a Lua page can read it with header("X-Real-IP"). Algernon's own log records nginx's loopback address.

Step 11: Security recommendations

  • Allow port 22 in the security group from your own networks only.
  • Use your own DNS name and a certificate your visitors trust (Step 9).
  • Keep everything under /srv/algernon owned by root: Algernon runs any .lua file it serves. Never put secrets, .env files or private keys in the site folder.
  • Do not add --debug, --dev, --autorefresh or --nolimit to the service: they expose error details, a live file-change stream or remove the request limit.
  • Keep the operating system patched; unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else.

Support and licensing

Algernon is open source under the BSD 3-Clause licence; there is no per user or per CPU licence fee. The licence is in /usr/share/doc/algernon/LICENSE. cloudimg is not affiliated with or endorsed by the Algernon project. The AMI price covers cloudimg's hardening, first boot setup and support.

  • Email: support@cloudimg.co.uk
  • Website: https://www.cloudimg.co.uk
  • Support: 24/7, with a 24 hour response SLA