Deployment guide

Algernon 1.17.12

Overview

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.

This guide covers the Algernon 1.17.12 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud web servers listing on AWS Marketplace. You pick it as the software version when you subscribe and launch. Algernon has no login and no administration console, so this image has no password.

What you get:

  • 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 upstream option (--strict-symlinks), and this image turns it on.
  • HTTPS from first boot. On first boot, your instance generates a certificate for itself alone and serves the site 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. There is no interactive console, no debug mode (an error in a Lua page never shows its source to visitors), no auto-refresh and no directory listing. 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 does not start until first boot has generated the certificate, so nothing is served before the instance is set up.
  • A verified official release. 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.
  • Image contents: Algernon 1.17.12 (/usr/local/bin/algernon, with the licence and release checksums in /usr/share/doc/algernon), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
  • Defaults: 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
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 select your Region. Choose Launch through EC2, then select m5.large, your key pair and a security group as described above.

Alternatively, launch 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}]'
2

Connect to your instance

Connect over SSH. For Algernon 1.17.12 on Ubuntu 24.04, the SSH user is ubuntu.

ssh -i <your-key.pem> ubuntu@<public-ip>
3

Check the services

On first boot, the instance generates the certificate and works out the host name. Algernon and nginx then start. Confirm that both are active:

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
4

Find your site's address

The address of your site is in an information file written on first boot. The file holds no secret. Read it with:

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.

The SecureAnyCloud landing page opens. It is itself a Lua page (/srv/algernon/index.lua) that Algernon runs on every request.

5

Check HTTPS, the redirect and the host name check

Run these commands on the instance. They 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 instance generated the certificate on first boot. It 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 = sac 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
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
SAC_ALGERNON_LUA_OK:5050 echo:42 method:GET tick:94.058883 <h2 id="sac-algernon-md-h2">SAC_ALGERNON_MD_H2</h2>

Run the built-in self-test. It checks the whole stack: redirect, landing page, Lua, Markdown, directory listing off, unknown host names refused, and Algernon on loopback only:

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

Check the hardening

Check the security headers. Every response carries them, and none shows the 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, but a file inside it is served. 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 removes its link immediately afterwards:

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

Finally, review 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;
8

Publish your own site

Algernon serves whatever is in /srv/algernon. It renders .md files as Markdown, runs .lua files on the server, treats .amber and .po2 files as templates, and serves everything else as a static file. The folder is owned by root, so the server can read it but never change it.

To try it, add a Markdown page and a Lua page, fetch them over HTTPS, then remove them:

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 immediately, with no restart. 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 {} +

Keep these notes in mind 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 run 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, not on the command line.
  • Lua pages run as the algernon account. They 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.
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 that your visitors' browsers trust, obtain one for your name (for example with certbot or acme.sh). Point nginx at it in /etc/nginx/sites-available/sac-algernon, in the two ssl_certificate lines of the second port 443 server block. Then check the configuration and reload:

sudo nginx -t && sudo systemctl reload nginx
10

Configuration reference

These are the files and folders that matter, and what each holds:

  • Your site: /srv/algernon
  • Algernon options: /etc/systemd/system/algernon.service (run 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/sac-algernon
  • nginx limits: /etc/nginx/algernon-limits.conf, /etc/nginx/conf.d/sac-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, and it overwrites anything a visitor sends. A Lua page can read the address with header("X-Real-IP"). Algernon's own log records nginx's loopback address.

11

Security recommendations

  • Allow port 22 in the security group from your own networks only.
  • Use your own DNS name and a certificate that your visitors trust (Step 9).
  • Keep everything under /srv/algernon owned by root, because 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. These options expose error details, expose 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, with no per-user or per-CPU licence fee. The licence is in /usr/share/doc/algernon/LICENSE. SecureAnyCloud is not affiliated with or endorsed by the Algernon project. The AMI price covers SecureAnyCloud's hardening, first-boot setup and support.

  • Email: sales@secureanycloud.com
  • Website: https://secureanycloud.com
  • Support: 24/7, with a 24-hour response SLA

Deploy on AWS

Launch Algernon 1.17.12 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

Deploy on AWS