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: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. 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 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 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
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}]'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>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 -1active active active Algernon 1.17.12Algernon 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 nginxFind 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.txtSITE_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.
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/ || truehttps://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 subjectAltNamesubject=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:localhostSee 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 -1SAC_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-selftestOK 301 + landing + lua + markdown + dirlist-off + unknown host refused + algernon loopback-onlyCheck 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-originA 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 404A 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-linksymlink to /etc/passwd -> 404Finally, 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;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: 404New 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.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). Then runsudo 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, not on the command line. - Lua pages run as the
algernonaccount. 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:8080behind nginx, and keep--strict-symlinksin/etc/systemd/system/algernon.service.
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 nginxConfiguration 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.
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/algernonowned by root, because 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. 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 upgradeapplies 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