Ha
Security Azure

Hanko on Ubuntu 24.04 on Azure User Guide

| Product: Hanko on Ubuntu 24.04 LTS on Azure

Overview

This image runs Hanko 2.7.0, the open source authentication backend from teamhanko, on Ubuntu 24.04 LTS. Hanko lets you add modern, passwordless sign in to your applications through a clean REST API: passkeys and security keys (FIDO2 and WebAuthn), email passcodes, passwords, multi factor authentication and social login. It handles registration, sign in, credential management and account recovery, and issues signed session tokens your application verifies against a published JSON Web Key Set.

The pinned upstream backend is run as the official ghcr.io/teamhanko/hanko container under a systemd service that starts it on boot and restarts it on failure, backed by a local PostgreSQL 16 database on the same VM. nginx sits in front as a TLS ready entry point and serves the public authentication API on port 80. The administrative API is bound to loopback only (port 8001) and is never exposed on the network, as the Hanko project intends.

What is included:

  • Hanko 2.7.0 (official ghcr.io/teamhanko/hanko backend image, pinned by digest), run by systemd
  • The public authentication API behind nginx on :80
  • The administrative API on 127.0.0.1:8001 (loopback only)
  • A per-VM PostgreSQL password and a per-VM session signing key generated on first boot and recorded in a root-only file
  • Database migrations applied automatically on first boot
  • PostgreSQL 16, bound to loopback, holding users, credentials and sessions
  • postgresql.service, docker.service, nginx.service and hanko.service as systemd units, enabled and active
  • No administrator baked into the image: the first user you register becomes your admin
  • 24/7 cloudimg support

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a sensible starting point; size up for heavier use. NSG inbound: allow 22/tcp from your management network and 80/tcp for the public authentication API. Do not expose port 8001 - the administrative API is intentionally loopback only. Hanko serves plain HTTP on port 80; for production, front it with your own domain and TLS (see Production hardening).

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Hanko by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Then Review + create then Create.

Step 2 - Deploy from the Azure CLI

az vm create \
  --resource-group <your-rg> \
  --name hanko \
  --image <marketplace-image-urn> \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_ed25519.pub \
  --vnet-name <your-vnet> --subnet <your-subnet> \
  --public-ip-sku Standard

az vm open-port --resource-group <your-rg> --name hanko --port 80 --priority 1010

Step 3 - Connect to your VM

ssh azureuser@<vm-public-ip>

Step 4 - Confirm the services are running

The backend runs as the hanko systemd service (a host-networked ghcr.io/teamhanko/hanko container) and talks to PostgreSQL on 127.0.0.1:5432. nginx publishes the authentication API on port 80. Confirm all four services are active and check the listeners - only nginx (:80) is on all interfaces; the Hanko public (:8000) and admin (:8001) APIs are on loopback:

systemctl is-active postgresql docker nginx hanko
sudo ss -tlnp | grep -E ':80 |:8000 |:8001 ' | awk '{print $4}' | sort -u

Expected output:

active
active
active
active
0.0.0.0:80
127.0.0.1:8000
127.0.0.1:8001
[::]:80

postgresql, docker, nginx and the hanko backend active, the pinned image digest, and the listeners showing only nginx on :80 with the Hanko public and admin APIs on loopback

Step 5 - Retrieve your per-VM secrets

On the first boot of every VM, hanko-firstboot.service generates a unique PostgreSQL password and a unique session signing key, creates the database, applies migrations, and writes the details into a root-only file. Read it with:

sudo cat /root/hanko-credentials.txt

There is no default or shared credential in the image: Hanko ships with no administrator baked in, and the database password and session key are unique to this VM. The first user you register (through your application or the admin API) becomes your organization administrator. Store these secrets somewhere safe.

The per-VM Hanko credentials file showing the public URL, the loopback admin API address, and the per-VM database password and session key generated on first boot

Step 6 - Check the public authentication API

The public API is served through nginx on port 80. Confirm the health endpoints return 200, look at the enabled sign-in methods, and fetch the JSON Web Key Set your application uses to verify Hanko's session tokens:

curl -s -o /dev/null -w '/health/ready : HTTP %{http_code}\n' http://127.0.0.1/health/ready
curl -s -o /dev/null -w '/health/alive : HTTP %{http_code}\n' http://127.0.0.1/health/alive
curl -s http://127.0.0.1/.well-known/config | python3 -m json.tool | sed -n '1,10p'
curl -s http://127.0.0.1/.well-known/jwks.json | python3 -c 'import sys,json;k=json.load(sys.stdin)["keys"][0];print("jwks kid:",k["kid"],"alg:",k["alg"])'

The /.well-known/config response advertises which sign-in methods are enabled (passwords are on by default; passkeys and email passcodes are configurable), and /.well-known/jwks.json publishes the per-VM public key your backend uses to validate the JWTs Hanko issues.

The Hanko public API answering through nginx: health checks returning 200, the enabled sign-in methods, and the session JWKS key id and algorithm

Step 7 - Create a user through the admin API

The administrative API on 127.0.0.1:8001 manages users directly. It is deliberately bound to loopback with no network authentication, so run these commands on the VM (over SSH) and never expose port 8001. Create a user and read it back:

EMAIL="alice-$(date +%s)@example.com"
RESP=$(curl -s -X POST http://127.0.0.1:8001/users -H 'Content-Type: application/json' \
  -d "{\"emails\":[{\"address\":\"${EMAIL}\",\"is_primary\":true,\"is_verified\":true}]}")
echo "$RESP" | python3 -m json.tool | sed -n '1,10p'
NEW_ID=$(echo "$RESP" | python3 -c 'import sys,json;print(json.load(sys.stdin)["id"])')
curl -s -o /dev/null -w 'read back: HTTP %{http_code}\n' http://127.0.0.1:8001/users/${NEW_ID}

The create call returns the new user's id and email, and reading it back returns HTTP 200 - proving the database, migrations and server are all working end to end.

Creating a user through the loopback admin API: the returned user JSON, the read-back returning HTTP 200, and the user count in the directory

Step 8 - Wire Hanko into your application

Hanko is an authentication backend. To give your users a sign-in screen, embed the official Hanko login web component (@teamhanko/hanko-elements, MIT licensed) in your own frontend and point it at this server's public API. A minimal example:

<script type="module">
  import { register } from "https://esm.run/@teamhanko/hanko-elements";
  await register("http://<vm-public-ip>");     // your Hanko public API URL
</script>
<hanko-auth></hanko-auth>

The <hanko-auth> element renders registration and sign in (passkeys, passcodes and passwords per your configuration). After a user signs in, your backend validates the session JWT against http://<vm-public-ip>/.well-known/jwks.json. See the Hanko docs for the frontend SDK and framework-specific examples.

Step 9 - Production hardening

The image ships the fully functional authentication server. For production:

  • TLS and DNS: front Hanko with your own domain and a TLS-terminating reverse proxy or Azure Application Gateway. nginx on the VM already proxies port 80 to the backend; add your certificate to the nginx site at /etc/nginx/sites-available/hanko, or terminate TLS upstream.
  • Relying party domain: passkeys are bound to a domain. Set webauthn.relying_party.id and webauthn.relying_party.origins in /etc/hanko/config.yaml to your production domain, then sudo systemctl restart hanko. By default the relying party id is localhost so the server always starts.
  • Email passcodes: configure SMTP under email_delivery in /etc/hanko/config.yaml to enable email passcode and verification delivery (disabled by default because no mail server is shipped).
  • Social login and SAML: enable Google, GitHub, Apple, Microsoft and other providers, or SAML, under third_party / saml in /etc/hanko/config.yaml.

Maintenance

  • Back up your data: all authentication state - users, credentials and sessions - lives in the local hanko PostgreSQL database. Snapshot the OS disk, or use pg_dump hanko for logical backups.
  • Public origins: on every boot hanko-configure.service re-renders /etc/hanko/config.yaml so the CORS and WebAuthn origins track the VM's current public IP. For a stable hostname, set your domain in the config as described above.
  • Security updates: unattended security upgrades are enabled, so the OS keeps itself patched. Reboot when a new kernel is installed.
  • Upgrades: the backend image is pinned for reproducibility; pull a newer ghcr.io/teamhanko/hanko tag and update the tag in /etc/systemd/system/hanko.service when you choose to upgrade.

Source code (AGPL-3.0)

Hanko's backend is free software licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). This image runs the stock upstream backend, unmodified, configured through external files only. In accordance with AGPL section 13, the complete corresponding source for the exact version deployed here is published by the upstream project at github.com/teamhanko/hanko (tag backend/v2.7.0). The Hanko login web components (hanko-elements, hanko-frontend-sdk) are separately licensed under the MIT License.

Support

cloudimg provides 24/7/365 expert technical support for this image. Contact support@cloudimg.co.uk. Hanko's backend is licensed under the AGPL-3.0 License; the corresponding source is linked above. This image is provided by cloudimg; additional charges apply for build, maintenance and 24/7 support.