Algernon 1.17.12 on AWS User Guide
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:8080only. 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 aserverconf.luafile. - Locked down service. Algernon runs as the unprivileged
algernonaccount 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.luaanswers 404 for any folder without an index file (index.lua,index.html,index.mdand so on). Delete it if you want Algernon's directory listings, or replace it with your own handler. If you replace the whole folder withrsync --delete, keep a copy of it. - Server configuration goes in
/etc/algernon/serverconf.lua(owned by root; the image ships none), thensudo systemctl restart algernon. Aserverconf.luainside/srv/algernonis ignored and never served. If you enable Algernon's user and permission system there, set your own cookie secret withSetCookieSecret()in that file rather than on the command line. - Lua pages run as the
algernonaccount 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:8080behind nginx, and keep--strict-symlinksin/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/algernonowned by root: Algernon runs any.luafile it serves. Never put secrets,.envfiles or private keys in the site folder. - Do not add
--debug,--dev,--autorefreshor--nolimitto 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 upgradeapplies 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