Algernon
Overview
Algernon is a small, self-contained web and application server written in Go and published under the BSD 3-Clause license. One static binary is enough to serve a whole dynamic site. It serves ordinary static files and also renders on the fly: Markdown becomes a themed HTML page, Lua files execute server-side and write their own response, and Amber, Pongo2, GCSS, TypeScript and JSX are handled the same way.
HTTP/2, TLS and a datastore are built in, so a Lua page can keep state without a database. There is no PHP-FPM pool to supervise, no application server to deploy into, no reverse proxy in front and no database daemon behind. You manage one process, one configuration surface and your content directory.
This SecureAnyCloud image runs Algernon on Ubuntu 24.04 on Azure and serves a small default site from the moment it boots, so it never shows a blank index.
What you get:
- Algernon 1.17.11, the official upstream release binary fetched from the project's own GitHub release, pinned by SHA-256 and redistributed unmodified. The artifact, its digest and its provenance are recorded at
/opt/algernon/VERSION - Algernon running under systemd as
algernon.serviceunder an unprivilegedalgernonaccount, holding ports 80 and 443 through a single Linux capability rather than by running as root - A default site written by SecureAnyCloud that demonstrates Markdown and Lua rendering. The landing page at / is index.lua, executed on every request. /welcome.md is a plain Markdown file rendered to HTML on request. /status.lua computes its response in a loop and echoes back a query parameter, so you can see per-request execution. You can delete all of it once your own content is in place
- A per-instance TLS certificate generated at first boot, so no two VMs share key material
- The embedded Bolt datastore, so the Lua storage API works with no extra daemon
algernon-selftest, a one-command proof that the server is really rendering- Ubuntu 24.04 LTS, fully patched at build time, with unattended security updates left enabled
- 24/7 SecureAnyCloud support
Key facts
- Platform: Ubuntu 24.04 LTS (Azure Gen2)
- Default SSH user: azureuser
- Service: algernon.service
- Runs as: algernon (unprivileged)
- Document root: /srv/algernon
- HTTP port: 80
- HTTPS port: 443 (HTTP/2)
- Configuration: systemd unit flags, /etc/systemd/system/algernon.service
- Datastore: /var/lib/algernon/algernon.db (embedded Bolt)
Prerequisites
Before you deploy, make sure you have:
- An active Azure subscription
- An SSH key pair, or the ability to generate one in the Azure Portal
- Permission to create virtual machines, virtual networks and network security groups
- A network security group that allows inbound TCP 22 from your management network, and inbound TCP 80 and 443 from wherever your site should be reachable
Recommended VM size: Standard_B2s (2 vCPU, 4 GiB). Algernon itself is very light, so size up only for your own traffic and content.
Deploy from the Azure Portal
- In the Azure Portal, choose Create a resource and search the Marketplace for Algernon on Ubuntu 24.04 LTS by SecureAnyCloud.
- Select the offer and choose Create.
- On the Basics tab, pick your subscription, resource group and region, set the VM name, and choose size
Standard_B2s. - Leave the authentication type as SSH public key and the username as
azureuser. - Under Inbound port rules, allow SSH (22), HTTP (80) and HTTPS (443).
- Select Review + create, then Create. The VM is ready in about a minute, and first boot generates this VM's TLS certificate.
Deploy from the Azure CLI
Create a resource group, accept the Marketplace terms and create the VM:
az group create --name algernon-rg --location eastus
az vm image terms accept --urn sac1647283583153:algernon:default:latest
az vm create \
--resource-group algernon-rg \
--name algernon-vm \
--image sac1647283583153:algernon:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
# the public address of the new VM
az vm show --resource-group algernon-rg --name algernon-vm \
--show-details --query publicIps --output tsvThen open the ports your site needs.
az network nsg rule create --resource-group algernon-rg \
--nsg-name algernon-vmNSG --name allow-http \
--priority 1010 --destination-port-ranges 80 443 \
--protocol Tcp --access Allow --direction InboundConnect and confirm Algernon is running
Connect over SSH as azureuser:
ssh azureuser@<public-ip>Confirm that the service came up and check which upstream artifact this image shipped:
algernon --version systemctl is-active algernon.service cat /opt/algernon/VERSIONExpected output:
Algernon 1.17.11 active product=Algernon version=1.17.11 upstream=https://github.com/xyproto/algernon release=https://github.com/xyproto/algernon/releases/download/v1.17.11/algernon-1.17.11-linux_x86_64_static.tar.xz tarball_sha256=73075b76ccbc5bf9d383e95ca9987cd66a1bf46f37dfeaa00ec0ad5714871abf binary_sha256=cd81e9884f8ac9797e32129b7f903421292578eb93ef19dc71007c7efa935a3e licence=BSD-3-Clause (verbatim text at /opt/algernon/LICENSE)The appliance listens on three ports only. List them and their owners:
sudo ss -lntp | grep -E ':(22|80|443)\s'Ports 80 and 443 belong to algernon. There is no monitor port, no admin console, no debug endpoint and no database listener, because the datastore is an embedded file rather than a separate daemon.
See the rendering for yourself
Open http://<ALGERNON_HOST>/ in a browser. You land on index.lua, executed on the server for that request. Reload the page and the generated timestamp changes.
The shipped selftest checks the whole thing in one command:
sudo /usr/local/sbin/algernon-selftestThe selftest exits non-zero and says why if anything is wrong. On a healthy VM it reports that:
- Markdown rendered into real HTML elements
- Lua executed per request
- The same site is served over TLS with HTTP/2
- No REPL, debug or scripting surface is exposed
Markdown is rendered, not served verbatim. /srv/algernon/welcome.md is an ordinary Markdown file. Request it over HTTP and you get HTML:
grep -m1 '^## ' /srv/algernon/welcome.md curl -s http://127.0.0.1/welcome.md | grep -oE '<h2[^>]*>[^<]*</h2>' | head -1 curl -s http://127.0.0.1/welcome.md | grep -oE '<td>[^<]*</td>' | head -2The heading arrives as an <h2> element and the pipe table arrives as <td> cells. Neither tag exists anywhere in the source file.
Lua executes on every request. /srv/algernon/status.lua sums the numbers 1 to 100 in a loop and multiplies any ?n= parameter by six, so the answers cannot be sitting in a file waiting to be served:
curl -s http://127.0.0.1/status.lua
curl -s 'http://127.0.0.1/status.lua?n=7' | grep '^echo:'
curl -s 'http://127.0.0.1/status.lua?n=9' | grep '^echo:'Expected output:
SAC_ALGERNON_LUA_OK:5050 echo:0 path:/status.lua method:GET tick:12.345678 server:Algernon on Ubuntu 24.04 LTS by sac echo:42 echo:54The tick value changes on every request, even for an identical URL. This shows that the response is computed rather than replayed from a cache.
TLS and HTTP/2 are already on
Algernon terminates TLS itself. The same site is served on port 443 with HTTP/2 negotiated by ALPN, using a certificate generated on this VM at first boot. Verify it:
curl -sk https://127.0.0.1/status.lua | head -1
curl -sk --http2 -o /dev/null -w 'negotiated HTTP/%{http_version}\n' https://127.0.0.1/ echo | openssl s_client -connect 127.0.0.1:443 2>/dev/null | openssl x509 -noout -subject -datesThe certificate is self-signed, so a browser warns until you install your own. It is unique to this VM: no certificate and no private key ship in the image, so no two customers, and no two of your own VMs, share TLS key material. The private key lives at /etc/algernon/tls/key.pem as 0640 root:algernon.
Every response also carries the standard protective headers plus a content security policy:
curl -sI http://127.0.0.1/ | grep -iE '^(server|x-content-type-options|x-frame-options|x-xss-protection|content-security-policy):'Read this VM's endpoint summary
First boot writes a summary of this VM's own endpoints and paths, readable only by root. Read it:
sudo cat /root/algernon-endpoints.txtThe file records the public URL, the document root, the datastore path, the log paths, the certificate fingerprint and the selftest command. Algernon has no login and no password, so the file contains no credential. It exists so you always know where everything lives.
Publish your own content
The document root is /srv/algernon. Add files as root and they are served immediately, with no build step and no restart:
sudo tee /srv/algernon/example-page.md >/dev/null <<'PAGE'
# My first page This file was written as plain Markdown and is being rendered on request. - Markdown becomes a themed HTML page - A `.lua` file runs server side and writes its own response - Anything else is served as an ordinary static file PAGE curl -s http://127.0.0.1/example-page.md | grep -oE '<h1[^>]*>[^<]*</h1>'
sudo rm -f /srv/algernon/example-page.mdThe file extension decides how Algernon treats a file:
- .md, .markdown: rendered as Markdown into a themed HTML page
- .lua, .tl: executed server-side, writes its own response
- .amber, .po2, .pongo2, .tmpl: rendered as a template
- .gcss, .scss: rendered to CSS
- .jsx, .tsx, .ts: transpiled for the browser
- Anything else: served as a static file
Index files are resolved in this order, so an index.lua wins over an index.html, which wins over an index.md.
The shipped default site consists of three files: index.lua, welcome.md and status.lua. List them, and delete them once your own site is in place:
ls -la /srv/algernon/Why root owns the document root, not the service account. Algernon executes any .lua file it finds in the document root. It also executes a serverconf.lua there automatically as server configuration, if one exists.
If the account Algernon runs as could write into that directory, any bug that let an attacker write a file would become remote code execution. So the tree is root:root, and the systemd unit also mounts the whole filesystem read-only for the service, except the datastore and log directories. Use sudo to publish, and do not chown the tree to the service account.
Install your own TLS certificate
Replace the self-signed pair with a certificate for your own domain, keeping the same paths and ownership:
sudo install -o root -g algernon -m 0644 \
/etc/letsencrypt/live/<your-domain>/fullchain.pem /etc/algernon/tls/cert.pem
sudo install -o root -g algernon -m 0640 \
/etc/letsencrypt/live/<your-domain>/privkey.pem /etc/algernon/tls/key.pem
sudo systemctl restart algernonAlgernon can also obtain and renew certificates itself using Let's Encrypt. This suits a VM that already has a public DNS name pointed at it. Add --letsencrypt --domain to the ExecStart line in the unit and remove the --cert and --key flags.
Test against the staging CA first with --testcert, so a misconfiguration does not use up your rate limit.
Tune the server
All configuration is on the ExecStart line of the systemd unit, so there is one place to look. Print it:
systemctl cat algernon.service | grep -A20 ExecStartThis image sets the following flags:
- --dir=/srv/algernon: the document root
- --http-addr=:80 / --https-addr=:443: serve both, with no forced redirect
- --cert / --key: the per-instance certificate generated at first boot
- --boltdb=/var/lib/algernon/algernon.db: the embedded datastore, so there is no database daemon and no extra listener
- --cache=prod: cache static files and rendered Markdown, never cache Lua
- --limit=200: requests per second per client
- --noninteractive: no Lua REPL, and debug mode forced off
- --stricter: adds the content security policy
- --hide-dotfiles: dotfiles are neither listed nor served
- --servername=Algernon: identifies the product without publishing the exact build
- --accesslog / --log: request log and server log under /var/log/algernon
To change a flag, edit the unit and reload:
$
sudo systemctl edit --full algernon.service $
sudo systemctl daemon-reload $
sudo systemctl restart algernonTwo flags are useful while you author content. --cache=off makes Algernon re-read every file from disk on each request, which is what you want while editing. --autorefresh injects a small script that reloads the browser when a file changes.
Both are development conveniences. Leave them off in production, and note that --autorefresh opens an additional event stream port.
Flags this image deliberately does not set. --lua starts an interactive Lua prompt instead of serving. --debug renders failing Lua source, including file paths, straight into the HTTP response. --dev turns on debug mode and raises the rate limit. --nolimit removes rate limiting entirely.
None of these belong on an internet-facing VM, and the selftest checks that none of them is present on the running process.
Watch what the server is doing
Read the access log, the server log and the journal:
sudo tail -5 /var/log/algernon/access.log
sudo tail -5 /var/log/algernon/server.log
sudo journalctl -u algernon.service -n 20 --no-pagerThe access log is Combined Log Format, so anything that reads Apache or nginx logs will read it. The server log is JSON, one object per line.
Note that Algernon does not always exit when something goes wrong. If it cannot read its TLS certificate, for example, it logs a single error, drops the HTTPS listener and carries on serving plain HTTP. systemd still reports active (running) and nothing restarts. When something looks wrong, read server.log instead of trusting the unit state:
sudo grep -c '"level":"error"' /var/log/algernon/server.log || echo 0Server components
- Algernon 1.17.11: web and application server, Markdown and Lua rendering, TLS, HTTP/2
- Ubuntu 24.04 LTS: base operating system
- systemd 255: service supervision and sandboxing
- OpenSSL 3.0.x: per-instance certificate generation at first boot
Filesystem layout
- /usr/local/bin/algernon: the server binary (single static executable)
- /srv/algernon: document root, root:root 0755
- /etc/algernon/tls: per-instance certificate and private key
- /var/lib/algernon: embedded Bolt datastore
- /var/log/algernon: access log and server log
- /opt/algernon/VERSION: upstream artifact provenance and digests
- /opt/algernon/LICENSE: the verbatim BSD 3-Clause license text
- /usr/local/sbin/algernon-selftest: the rendering proof
- /root/algernon-endpoints.txt: this VM's endpoint summary, root only
Managing the service
Check the status and restart the service with systemctl:
sudo systemctl status algernon --no-pager sudo systemctl restart algernonTo stop the server, run sudo systemctl stop algernon. The unit is enabled, so the server starts again automatically after a reboot.
Scripts and log files
- /usr/local/sbin/algernon-firstboot.sh: generates this VM's TLS certificate on first boot
- /usr/local/sbin/algernon-selftest: proves Markdown rendering and Lua execution
- /var/log/sac/sac-firstboot.log: first-boot log
- /var/log/algernon/access.log: requests, Combined Log Format
- /var/log/algernon/server.log: server log, JSON per line
- /var/lib/sac/data/algernon-firstboot.done: first-boot sentinel
On startup
On the first boot only, algernon-firstboot.service runs before the server starts. It resolves this VM's public address, generates the per-instance TLS certificate and writes the endpoint summary. It then releases the bootstrap gate and starts algernon.service, and proves the server is rendering before it finishes.
algernon.service carries ConditionPathExists on a bootstrap marker that only first boot creates. This is deliberate: the server cannot start before this VM's own key material exists. Because it is a condition rather than an ordering dependency, it cannot create a boot job cycle that would hang the VM. Later reboots skip first boot and start the server directly.
Troubleshooting
The site does not answer on port 80 or 443.
systemctl is-active algernon.service
sudo journalctl -u algernon.service -n 40 --no-pager sudo grep '"level":"error"' /var/log/algernon/server.log | tail || true
sudo grep -q '"level":"error"' /var/log/algernon/server.log || echo "no errors logged"Also check that the network security group allows inbound 80 and 443 from where you are testing.
HTTPS is refused but HTTP works. This almost always means the service account cannot read the certificate or key. The server logs one error and keeps serving plain HTTP:
sudo ls -la /etc/algernon/tls/
sudo grep 'permission denied' /var/log/algernon/server.log || echo "no certificate permission errors logged"Both /etc/algernon and /etc/algernon/tls must be group algernon, and key.pem must be 0640 root:algernon.
First boot did not complete.
systemctl status algernon-firstboot.service --no-pager sudo cat /var/log/sac/sac-firstboot.logA Lua page returns an error but does not say why. This is intentional. Debug mode is off, so failing source is never rendered into the response where a visitor could read it. The reason is in the server log:
sudo tail -20 /var/log/algernon/server.logA page shows stale content after editing. Static files and rendered Markdown are cached (--cache=prod). Restart the service, or switch to --cache=off while you author content.
Requests are rejected under load. The default ceiling is 200 requests per second per client (--limit). Raise it on the ExecStart line if your traffic needs more.
Security notes
- No login and no default credential. Algernon has no admin console and no seeded account. Its optional user system creates nothing until your own Lua asks it to, and nothing in this image does.
- No key material ships in the image. The TLS certificate and private key are generated on your VM at first boot. The datastore is also created on your VM, so no record from the build can ride along.
- The service account cannot write to the document root, because Algernon executes
.luafiles found there and automatically executesserverconf.lua. Publish withsudo. The systemd unit also appliesProtectSystem=strict,NoNewPrivilegesand a capability bounding set of justCAP_NET_BIND_SERVICE. - No REPL and no debug surface. The server runs with
--noninteractiveand reads nothing from standard input. - Restrict SSH to your management network, and expose ports 80 and 443 only as widely as your site needs.
- Replace the self-signed certificate before you serve real traffic.
- If you enable the user system, set your own login cookie secret with
SetCookieSecret()in aserverconf.luaplaced in the documentrootas root. Sessions then survive a restart under a secret you control. - Keep updates on. Unattended security updates remain enabled, as on every SecureAnyCloud image.
Support
SecureAnyCloud provides 24/7 support for this image. Contact sales@secureanycloud.com.
Algernon itself is developed at github.com/xyproto/algernon and published under the BSD 3-Clause license. SecureAnyCloud is not affiliated with or endorsed by the Algernon project.
Deploy on Azure
Launch Algernon on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com