Deployment guide

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.service under an unprivileged algernon account, 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.

1

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.
2

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 tsv

Then 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 Inbound
3

Connect 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/VERSION

Expected 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.

4

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-selftest

The 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 -2

The 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:54

The tick value changes on every request, even for an identical URL. This shows that the response is computed rather than replayed from a cache.

5

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 -dates

The 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):'
6

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.txt

The 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.

7

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.md

The 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.

8

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 algernon

Algernon 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.

9

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 ExecStart

This 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 algernon

Two 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.

10

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-pager

The 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 0

Server 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 algernon

To 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.log

A 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.log

A 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 .lua files found there and automatically executes serverconf.lua. Publish with sudo. The systemd unit also applies ProtectSystem=strict, NoNewPrivileges and a capability bounding set of just CAP_NET_BIND_SERVICE.
  • No REPL and no debug surface. The server runs with --noninteractive and 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 a serverconf.lua placed in the document root as 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

Deploy on Azure