Krill on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and configuration of Krill on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Krill is an open source RPKI Certificate Authority from NLnet Labs. It lets a network operator run their own Resource Public Key Infrastructure: issuing certificates for the IP address space and AS numbers they hold, and publishing Route Origin Authorizations (ROAs) that state which networks are allowed to originate their prefixes. Krill can run as a delegated child under a parent authority such as a Regional Internet Registry, giving operators direct control over the RPKI objects that protect their routing.
Krill is a single self contained Rust binary set — the krill daemon and the krillc command line client — with an embedded data store (no external database) and a built in web interface. The image installs Krill from the official NLnet Labs apt repository and bakes it reproducibly (the exact version is recorded in /opt/krill/VERSION). Krill runs under systemd as the unprivileged krill system user.
The Krill daemon binds to loopback only (127.0.0.1:3000) with its own TLS; nginx is the sole network facing surface and terminates TLS on port 443, reverse proxying to the Krill HTTPS upstream. On the very first boot this image generates a unique administrative token for this specific VM, sets the Krill service URL to https://<your public ip>/, and regenerates a self signed TLS certificate whose Subject Alternative Names include the VM public IP and hostname. The token protects the web UI and the full REST API: unauthenticated requests are refused (HTTP 403), and only the admin token is accepted. The token is written to /root/krill-credentials.txt (mode 0600, root only). No shared secret and no default login ever ships inside the image.
What is included:
-
Krill (latest stable release from the official NLnet Labs apt repository), run under systemd as the unprivileged
krillsystem user -
The Krill daemon and its embedded store bound to loopback only (
127.0.0.1:3000) — never directly network exposed -
nginx terminating TLS on :443 and reverse proxying the web UI and REST API to the Krill upstream
-
Port
:80returns a301redirect to HTTPS for every path except an unauthenticated/healthzendpoint (HTTP 200) for load balancer probes -
A unique admin token generated on first boot and stored in
/root/krill-credentials.txt(0600) — no shared secret, no default login -
A self signed TLS certificate regenerated per VM on first boot (SAN includes the VM public IP + hostname) — replace it with your own CA signed certificate for production
-
Ubuntu 24.04 LTS base with latest security patches applied at build time and unattended security upgrades enabled
-
Azure Linux Agent for seamless cloud integration and SSH key injection
-
24/7 cloudimg support with guaranteed 24 hour response SLA
Prerequisites
-
Active Azure subscription, SSH public key, VNet + subnet in target region
-
Subscription to the Krill listing on Azure Marketplace
-
To issue live ROAs: a parent CA (typically your Regional Internet Registry, over RFC 8183) and a publication repository. This image runs the CA out of the box; connecting a parent and a repository is done from the web UI (Step 8) when you are ready.
Recommended virtual machine size: Standard_B2s (2 vCPU, 4 GB RAM) is ample for a Certificate Authority managing your own resources and ROAs. Krill is CPU light and needs no GPU.
Step 1: Deploy from the Azure Portal
Search Krill in Marketplace, select the cloudimg publisher, click Create. NSG rules: TCP 22 (admin), TCP 443 (web UI and REST API over HTTPS) and TCP 80 (HTTP to HTTPS redirect and health probe) from the networks you manage the CA from.
Step 2: Deploy from the Azure CLI
RG="krill-prod"; LOCATION="eastus"; VM_NAME="krill-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/krill-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name krill-vnet --address-prefix 10.100.0.0/16 --subnet-name krill-subnet --subnet-prefix 10.100.1.0/24
az network nsg create -g "$RG" --name krill-nsg
az network nsg rule create -g "$RG" --nsg-name krill-nsg --name allow-ssh --priority 100 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name krill-nsg --name allow-https --priority 110 \
--destination-port-ranges 443 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name krill-nsg --name allow-http --priority 120 \
--destination-port-ranges 80 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
--size Standard_B2s --storage-sku StandardSSD_LRS \
--admin-username azureuser --ssh-key-values "$SSH_KEY" \
--vnet-name krill-vnet --subnet krill-subnet --nsg krill-nsg --public-ip-sku Standard
Step 3: Verify the Krill and nginx services
Connect over SSH, then confirm both services are active and that Krill is bound to loopback only. In the listener output, 127.0.0.1:3000 (Krill) is loopback only, while nginx listens publicly on :443 and :80:
sudo systemctl is-active krill.service nginx.service
krill --version
sudo ss -tlnH '( sport = :443 or sport = :3000 )'
Both services report active, Krill reports its version, and the listener table shows Krill on 127.0.0.1:3000 (loopback only) with nginx public on :443 and :80.

Step 4: Retrieve your per instance admin token
The unique admin token, the service URL and quick start commands are generated on the first boot and stored in a root only file. Read it over SSH:
sudo ls -l /root/krill-credentials.txt
sudo cat /root/krill-credentials.txt
The ls -l confirms the file is 0600 and owned by root. The file contains the service URL (https://<your public ip>/), the per instance krill.admin_token, and quick start commands. Store the token somewhere safe: it is unique to this VM and protects the web UI and the full API.

Step 5: Log in to the Krill web interface
Open https://<your public ip>/ in a browser. Because the image ships a self signed certificate, your browser will warn on first visit — accept it to proceed (or install your own certificate, see Step 9). Krill shows a sign in page that asks for a password: paste the admin token from Step 4 and click Sign In.

Once signed in, Krill shows the Certificate Authority dashboard. The example below shows a Certificate Authority named cloudimg-demo with tabs for ROAs, ASPAs, Parents and Repository, and the controls to add and analyse ROAs. This is where you manage the RPKI objects for your resources.

Step 6: Create and read back a Certificate Authority from the CLI
The krillc client talks to the local Krill daemon. Point it at the loopback endpoint and authenticate with your admin token, then create a Certificate Authority and read it back. Creating a CA and confirming it persists is the core round trip this image verifies before it ships:
export KRILL_CLI_SERVER=https://127.0.0.1:3000/
export KRILL_CLI_TOKEN=$(sudo grep '^krill.admin_token=' /root/krill-credentials.txt | cut -d= -f2-)
sudo -E krillc add --ca my-first-ca
sudo -E krillc list
sudo -E krillc show --ca my-first-ca
krillc list now includes my-first-ca, and krillc show prints the CA with its identity certificate and resources — proving the Certificate Authority was created and persisted in Krill's embedded store.

Step 7: Confirm the API rejects anonymous access
The /health and /healthz endpoints are served without authentication (ideal for load balancer and Azure health probes) and return HTTP 200. The REST API, in contrast, requires the admin token: an anonymous request is refused, and only the token returns 200. The self signed certificate lives at /etc/nginx/tls/krill.crt, so we pass it with --cacert:
CA=/etc/nginx/tls/krill.crt
TOKEN=$(sudo grep '^krill.admin_token=' /root/krill-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'public /health -> HTTP %{http_code}\n' --cacert $CA https://127.0.0.1/health
echo "# Anonymous REST API request (no token) is refused:"
curl -s -o /dev/null -w 'anonymous /api/v1/cas -> HTTP %{http_code}\n' --cacert $CA https://127.0.0.1/api/v1/cas
echo "# Authenticated request with the admin token:"
curl -s -o /dev/null -w 'authenticated /api/v1/cas -> HTTP %{http_code}\n' -H "Authorization: Bearer $TOKEN" --cacert $CA https://127.0.0.1/api/v1/cas
The health endpoint returns HTTP 200, the anonymous API request is refused (HTTP 403), and the authenticated request returns HTTP 200. There is no anonymous access to the management API.

Step 8: Connect a parent and a publication repository
A newly created CA holds no resources until you connect it to a parent (the authority that delegates your IP space and AS numbers to you, typically your Regional Internet Registry) and a publication repository (where your certificate and ROAs are published for relying party software to fetch). Both are configured from the CA in the web interface.
On the Parents tab, add your parent authority using the RFC 8183 exchange your registry provides:

On the Repository tab, configure the publication server where Krill will publish your RPKI objects:

Once a parent and repository are configured, add ROAs from the ROAs tab (or with sudo -E krillc roas update --ca my-first-ca ...), and relying party software will start fetching your certificate and ROAs from the repository.
Step 9: Use your own domain and a trusted certificate (production)
The self signed certificate is a convenience for getting started. For production, front Krill with your own DNS name and a CA signed certificate so browsers and clients trust it without any manual step. Point a DNS record (for example rpki.example.com) at the VM public IP; replace /etc/nginx/tls/krill.crt and /etc/nginx/tls/krill.key with your CA signed certificate and key (for example from Let's Encrypt or your internal CA); update service_uri in /etc/krill.conf to https://rpki.example.com/; then reload nginx and restart Krill with sudo nginx -t && sudo systemctl reload nginx && sudo systemctl restart krill.service. Note that the service_uri is embedded in the RFC 8183 identity exchanges, so set it before you connect a parent or repository.
Step 10: How Krill is wired
The Krill configuration at /etc/krill.conf binds the daemon to loopback only, sets the per VM service URL, and points at the embedded data store. nginx is the only network facing surface: it terminates TLS and reverse proxies to the Krill HTTPS upstream.
sudo grep -E '^ip|^port|^service_uri|^data_dir|^log_type' /etc/krill.conf
grep -nE 'listen 443 ssl|ssl_certificate|proxy_pass|proxy_ssl_verify|return 301' /etc/nginx/sites-available/cloudimg-krill
The output shows Krill on ip = 127.0.0.1, port = 3000, the service_uri set to your VM public URL, the embedded data_dir under /var/lib/krill, and the nginx TLS + reverse proxy directives (listen 443 ssl, ssl_certificate, proxy_pass to https://127.0.0.1:3000, proxy_ssl_verify off, and return 301 on :80).
Step 11: Server components
| Component | Version / Detail |
|---|---|
| RPKI CA | Krill (latest stable release, baked reproducibly, see /opt/krill/VERSION) |
| Daemon binding | 127.0.0.1:3000 (loopback only, own TLS behind the proxy) |
| Front proxy | nginx (TLS on :443, reverse proxy to the Krill HTTPS upstream, :80 -> 301 https) |
| Data store | embedded store under /var/lib/krill (no external database) |
| CLI | krillc (authenticated with the admin token, KRILL_CLI_SERVER / KRILL_CLI_TOKEN) |
| Credential | per instance admin token, generated first boot, /root/krill-credentials.txt (0600) |
| TLS certificate | self signed, regenerated per VM first boot (SAN = public IP + hostname), /etc/nginx/tls/ |
| Operating system | Ubuntu 24.04 LTS (patched at build) |
| License | MPL-2.0 (Krill, Copyright (c) NLnet Labs) |
Step 12: Managing the Krill service
sudo systemctl status krill.service --no-pager
sudo systemctl restart krill.service
sudo journalctl -u krill.service -n 20 --no-pager
sudo systemctl reload nginx
CA state, keys and ROAs live under /var/lib/krill and persist across service restarts and VM reboots.
Step 13: Security recommendations
-
Restrict the NSG. Allow TCP 443 (and 22 for admin) only from the networks you manage the CA from. The management interface does not need to be reachable from the whole internet.
-
Guard the admin token. It grants full control of the CA. Keep
/root/krill-credentials.txtroot only, and rotate the token by updatingadmin_tokenin/etc/krill.confand restarting Krill. -
Use a trusted certificate (Step 9) for production so browsers and clients validate the interface without a manual trust step.
-
Set
service_uribefore connecting a parent. It is embedded in the RFC 8183 exchanges; changing it afterwards means re-doing them. -
Keep the OS patched. Unattended security upgrades remain enabled on the running VM.
-
Back up
/var/lib/krillto preserve your CA identity, keys, delegations and ROAs.
Step 14: Support and Licensing
Krill is distributed under the Mozilla Public License 2.0 (Copyright (c) NLnet Labs). This cloudimg image installs the unmodified official release from the NLnet Labs apt repository. cloudimg provides the packaging, hardening, per instance admin token and TLS automation, and 24/7 support with a guaranteed 24 hour response SLA. Krill is an independent open source project of NLnet Labs, and this image is not affiliated with or endorsed by NLnet Labs.
Deploy on Azure
Find Krill on Ubuntu 24.04 on the Azure Marketplace, published by cloudimg. Deploy from the Portal or the Azure CLI as shown above.
Need Help?
cloudimg provides 24/7 support with a guaranteed 24 hour response SLA. Contact us through the Azure Marketplace listing or at cloudimg.co.uk for deployment assistance, configuration guidance or production RPKI questions.