Hanko
Overview
Hanko 2.7.0 is the open-source authentication backend from teamhanko. It adds 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. Hanko handles registration, sign-in, credential management and account recovery, and it issues signed session tokens that your application verifies against a published JSON Web Key Set.
This SecureAnyCloud image runs the pinned upstream backend on Ubuntu 24.04 LTS as the official ghcr.io/teamhanko/hanko container, backed by a local PostgreSQL 16 database on the same VM. A systemd service starts the container on boot and restarts it on failure.
What you get:
- Hanko 2.7.0 (official
ghcr.io/teamhanko/hankobackend image, pinned by digest), run by systemd - The public authentication API on
:80, served by nginx as a TLS-ready entry point - The administrative API on
127.0.0.1:8001, bound to loopback only and never exposed on the network, as the Hanko project intends - 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.serviceandhanko.serviceas enabled, active systemd units- No administrator baked into the image: the first user you register becomes your admin
- 24/7 SecureAnyCloud support
Prerequisites
You need an active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region.
Standard_B2s (2 vCPU / 4 GiB RAM) is a sensible starting size. Size up for heavier use.
Configure the network security group (NSG) to allow inbound 22/tcp from your management network and 80/tcp for the public authentication API. Do not expose port 8001, because the administrative API is intentionally loopback only. Hanko serves plain HTTP on port 80, so 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 SecureAnyCloud, and select Create.
On the Basics tab, 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).
Select Review + create, then Create.
Step 2 - Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:hanko-ubuntu-24-04:default:latest
az vm create \
--resource-group <your-rg> \
--name hanko \
--image sac1647283583153:hanko-ubuntu-24-04:default:latest \
--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 1010Step 3 - Connect to your VM
Connect to the VM over SSH:
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 that all four services are active and check the listeners. Only nginx (:80) listens on all interfaces. The Hanko public API (:8000) and admin API (:8001) listen on loopback:
systemctl is-active postgresql docker nginx hanko
sudo ss -tlnp | grep -E ':80 |:8000 |:8001 ' | awk '{print $4}' | sort -uThe expected output is:
active active active active 0.0.0.0:80 127.0.0.1:8000 127.0.0.1:8001 [::]:80Step 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. It also creates the database, applies migrations and writes the details to a root-only file. Read the file with:
sudo cat /root/hanko-credentials.txtThe image contains no default or shared credential. 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.
Step 6 - Check the public authentication API
nginx serves the public API on port 80. Run these commands to confirm that the health endpoints return 200, to see the enabled sign-in methods, and to fetch the JSON Web Key Set that 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 lists the enabled sign-in methods. Passwords are on by default, and passkeys and email passcodes are configurable.
The /.well-known/jwks.json endpoint publishes the per-VM public key that your backend uses to validate the JWTs Hanko issues.
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 the user back returns HTTP 200. This confirms that the database, migrations and server work end to end.
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. Point the component 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, using passkeys, passcodes and passwords according to 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 a fully functional authentication server. For production, complete these tasks:
- 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.idandwebauthn.relying_party.originsin/etc/hanko/config.yamlto your production domain, then runsudo systemctl restart hanko. By default the relying party id islocalhost, so the server always starts. - Email passcodes: configure SMTP under
email_deliveryin/etc/hanko/config.yamlto enable email passcode and verification delivery. Delivery is disabled by default because the image ships no mail server. - Social login and SAML: enable Google, GitHub, Apple, Microsoft and other providers, or SAML, under
third_partyandsamlin/etc/hanko/config.yaml.
Maintenance
- Back up your data: all authentication state (users, credentials and sessions) lives in the local
hankoPostgreSQL database. Snapshot the OS disk, or usepg_dump hankofor logical backups. - Public origins: on every boot,
hanko-configure.servicere-renders/etc/hanko/config.yamlso that 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. To upgrade, pull a newer
ghcr.io/teamhanko/hankotag and update the tag in/etc/systemd/system/hanko.service.
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, and configures it through external files only.
In accordance with AGPL section 13, the upstream project publishes the complete corresponding source for the exact version deployed here 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
SecureAnyCloud provides 24/7/365 expert technical support for this image. Contact sales@secureanycloud.com.
Hanko's backend is licensed under the AGPL-3.0 License, and the corresponding source is linked above. SecureAnyCloud provides this image, and additional charges apply for build, maintenance and 24/7 support.
Deploy on Azure
Launch Hanko 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