Ka
Developer Tools Azure

KrakenD Community Edition on Ubuntu 24.04 on Azure User Guide

| Product: KrakenD Community Edition 3.0.2 on Ubuntu 24.04 LTS on Azure

Overview

This guide covers deploying and using the KrakenD Community Edition 3.0.2 on Ubuntu 24.04 LTS image from cloudimg on Microsoft Azure.

Which KrakenD this is. KrakenD Community Edition is the open-source KrakenD (Apache-2.0). KrakenD Enterprise is a separate commercial product from KrakenD and is not included.

KrakenD is a stateless, declarative, high performance API gateway. You describe your public API in one JSON file: each endpoint lists the backend calls behind it, and KrakenD routes, aggregates, filters and secures those calls, with JWT validation, rate limiting, circuit breakers and response manipulation built in. It keeps no database, so a gateway is just a binary and a configuration file.

The cloudimg image gives you a KrakenD that is safe to put on a public address from the first minute:

  • CVE-2026-111943 fixed and provable. KrakenD 3.0.2 is built with Go 1.26.10, which refuses certificate chains through the eight compromised eMudhra intermediate CAs, and the Ubuntu trust store on the image no longer carries the roots those chains end in. sudo krakend-cve-2026-111943-check proves both on your VM (Step 9).
  • HTTPS from the first boot. nginx terminates TLS on port 443 with a certificate generated on your VM's first boot (TLS 1.2 or newer) and hands requests to KrakenD on 127.0.0.1:8080. No certificate or key is baked into the image, and nothing listens on port 80, so an API client pointed at http:// fails instead of sending its token in clear text.
  • Never an open proxy. KrakenD only serves the endpoints declared in /etc/krakend/krakend.json. The shipped endpoints call one fixed demo backend on 127.0.0.1:8081, which you replace with your own services. Requests for other host names, and absolute URLs aimed at another host (such as the Azure metadata address), are dropped.
  • Internal endpoints stay private. KrakenD's /__health answers on loopback only, its /__debug and /__echo endpoints are switched off, no metrics listener is configured, and nginx returns 404 for every /__ path. The public /health probe returns ok only while KrakenD is healthy.
  • A JWT protected endpoint with a per VM key. /v1/secure/users/{id} is protected by KrakenD's JWT validator against an HS256 signing key generated on your VM's first boot; a demo token is in a root only file, and a helper mints more.
  • The real client address. nginx overwrites X-Forwarded-For and X-Real-IP with the connection's address and drops client supplied Forwarded and X-Forwarded-Host headers, and KrakenD trusts forwarding headers from 127.0.0.1 only.
  • Sandboxed and fail closed. KrakenD runs as the unprivileged krakend user with no capabilities under a locked down systemd unit that cannot reach the Azure Instance Metadata Service or the WireServer, with KrakenD's anonymous usage reporting switched off. KrakenD and nginx will not start until first boot has generated the keys, and the configuration is checked against KrakenD's schema before every start.
  • Built from the upstream source. The image compiles the tagged 3.0.2 source with Go 1.26.10 (the Go release KrakenD builds 3.0.2 with), raising one library, golang.org/x/net, to v0.62.0 for two advisories published after KrakenD's release. govulncheck reports 0 reachable advisories, and the binary is reproducible (Step 12).
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on, no root login over SSH, and SSH password logins left to the choice you make in the Azure portal.

What is on the image: KrakenD Community Edition 3.0.2 (/usr/local/bin/krakend, licence and build provenance in /usr/share/doc/krakend), the gateway configuration /etc/krakend/krakend.json (KrakenD 3.0 configuration format, "version": 4), nginx, the krakend.service, krakend-firstboot.service and krakend-address.service units, Ubuntu 24.04 LTS, cloud-init and the Azure Linux agent.

Default user: azureuser (or the name you choose) · Architecture: x64 · Recommended size: Standard_B2s

Prerequisites

  • An Azure subscription, an SSH key pair, and a virtual network in the target region.
  • A network security group that allows TCP 22 (SSH) from your admin network and TCP 443 (HTTPS) from the clients that call your API. Nothing else needs to be open: KrakenD and the demo backend listen on loopback only, and there is no port 80 listener.

Step 1: Deploy the VM

In the Azure portal, open Create a resource, search for KrakenD Community Edition on Ubuntu 24.04 LTS by cloudimg and select Create. On the Basics tab choose Standard_B2s (KrakenD is light; size up for high request rates) and SSH public key authentication with azureuser as the user name, and on the Networking tab allow 22 and 443 as above.

With the Azure CLI (replace the values in angle brackets):

az vm image terms accept --urn cloudimg1647283583153:krakend-ubuntu-24-04:default:latest
az vm create --resource-group <resource-group> --name krakend \
  --image cloudimg1647283583153:krakend-ubuntu-24-04:default:latest \
  --size Standard_B2s --admin-username azureuser --ssh-key-values ~/.ssh/id_rsa.pub \
  --public-ip-sku Standard --nsg-rule SSH
az network nsg rule create --resource-group <resource-group> --nsg-name krakendNSG --name krakend-https \
  --priority 1010 --access Allow --protocol Tcp --destination-port-ranges 443 --source-address-prefixes <your-clients-cidr>

First boot takes well under a minute after the VM is running: it reads the VM's public address, generates the certificate, the JWT signing key and a demo token, then KrakenD and nginx start.

Step 2: Connect to your VM

ssh azureuser@<public-ip>

Step 3: Check the services

systemctl is-active krakend-address krakend nginx
sudo test -f /var/lib/cloudimg/krakend-firstboot.done && echo FIRSTBOOT_DONE
krakend version
active
active
active
FIRSTBOOT_DONE
KrakenD Version: 3.0.2
Go Version: 1.26.10
Glibc Version: STATIC-CGO-DISABLED

nginx listens on port 443 on every address; KrakenD (8080) and the demo backend (8081) listen on loopback only:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | sed -E 's/pid=[0-9]+,fd=[0-9]+/.../g' | sort -u
0.0.0.0:22 users:(("sshd",...),("systemd",...))
0.0.0.0:443 users:(("nginx",...),("nginx",...),("nginx",...))
127.0.0.1:8080 users:(("krakend",...))
127.0.0.1:8081 users:(("nginx",...),("nginx",...),("nginx",...))
127.0.0.53%lo:53 users:(("systemd-resolve",...))
127.0.0.54:53 users:(("systemd-resolve",...))
[::]:22 users:(("sshd",...),("systemd",...))
[::]:443 users:(("nginx",...),("nginx",...),("nginx",...))

(nginx runs one worker per vCPU, so the number of nginx entries matches your VM size.)

The krakend-address, krakend-firstboot, krakend and nginx services active, only SSH and HTTPS on network ports, KrakenD and the demo backend on loopback, and KrakenD Community Edition 3.0.2 built with Go 1.26.10

Step 4: Your VM's URL and demo token

sudo cat /root/krakend-credentials.txt

The file is readable by root only:

# KrakenD Community Edition 3.0.2 - generated on first boot by krakend-firstboot.service
# The JWT signing key and the demo token are unique to this VM; none was baked into the image. Keep this secret.
# KrakenD Community Edition is the open-source KrakenD (Apache-2.0). KrakenD Enterprise is a separate commercial product from KrakenD and is not included.
# cloudimg provides 24/7 support for this image, the Ubuntu OS and deployment. Support for KrakenD itself is from the KrakenD community; commercial support is available from KrakenD.

KRAKEND_URL=https://<public-ip>/
KRAKEND_CERT_SHA256=<sha256-fingerprint>
KRAKEND_CONFIG=/etc/krakend/krakend.json
KRAKEND_JWK_FILE=/etc/krakend/jwk-cloudimg.json
KRAKEND_JWT_ISSUER=cloudimg-krakend-demo
KRAKEND_DEMO_TOKEN=<KRAKEND_DEMO_TOKEN>
#
# nginx (HTTPS) is the only public entry point; KrakenD runs on 127.0.0.1:8080 and the demo backend on 127.0.0.1:8081.
# KRAKEND_DEMO_TOKEN (30 days) opens /v1/secure/users/{id}:  curl -H "Authorization: Bearer <token>" .../v1/secure/users/1
# New demo token:               sudo krakend-demo-token --ttl 3600
# New signing key + token:      sudo krakend-rotate-jwt-key
# Change the name it serves as: sudo krakend-set-hostname <your-dns-name>
# User guide: https://www.cloudimg.co.uk/guides/krakend-on-ubuntu-24-04-azure/

The URL uses the VM's public IP address, which krakend-address.service reads from the Azure Instance Metadata Service at every boot (Step 11 explains why only that small unit may reach it). The certificate also names the private IP address. To serve your own DNS name, see Step 10.

Step 5: Send requests through KrakenD

The shipped configuration declares three endpoints in front of the demo backend. /v1/hello proxies one backend call, and /v1/users/{id} calls the backend twice (the user record and the user's orders) and merges both answers into one response. From the VM itself, trusting the VM's own certificate:

H=$(sudo sed -n 's#^KRAKEND_URL=https://\(.*\)/$#\1#p' /root/krakend-credentials.txt)
curl -s --cacert /etc/nginx/tls/krakend.crt --resolve "$H:443:127.0.0.1" "https://$H/v1/hello" | jq .
curl -s --cacert /etc/nginx/tls/krakend.crt --resolve "$H:443:127.0.0.1" "https://$H/v1/users/1" | jq .
curl -s -o /dev/null -D - --cacert /etc/nginx/tls/krakend.crt --resolve "$H:443:127.0.0.1" "https://$H/v1/hello" | tr -d '\r' | grep -iE '^(HTTP|content-type|x-krakend|strict-transport|x-content)'
{
  "message": "Hello from the demo backend on 127.0.0.1:8081, routed through KrakenD",
  "service": "cloudimg-krakend-demo-backend"
}
{
  "orders": {
    "orders": [
      {
        "id": 1001,
        "status": "shipped",
        "total": 42.50
      },
      {
        "id": 1002,
        "status": "processing",
        "total": 19.99
      }
    ]
  },
  "user": {
    "email": "ada@example.com",
    "id": 1,
    "name": "Ada Lovelace",
    "plan": "enterprise"
  }
}
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
X-Krakend: Version 3.0.2
X-Krakend-Completed: true
Strict-Transport-Security: max-age=31536000
X-Content-Type-Options: nosniff

From your own machine, call the gateway over the internet (the certificate is self signed, so -k skips the check; Step 10 covers your own name and certificate):

curl -k https://<public-ip>/v1/users/2

The aggregated /v1/users/1 response from KrakenD, opened in a browser over HTTPS: the user record and the user's orders merged into one JSON document

KrakenD only answers the endpoints you declare. Any other path is 404, a host name the VM does not serve is dropped, an absolute URL that tries to use the gateway as a forward proxy to another host (here the Azure metadata address) is dropped, and nothing answers on port 80:

H=$(sudo sed -n 's#^KRAKEND_URL=https://\(.*\)/$#\1#p' /root/krakend-credentials.txt)
for p in /v1/nope /metadata/instance; do curl -s -o /dev/null -w "$p -> %{http_code}\n" --cacert /etc/nginx/tls/krakend.crt --resolve "$H:443:127.0.0.1" "https://$H$p"; done
curl -sk -o /dev/null -w 'unknown host name -> %{http_code}\n' --resolve "other.example:443:127.0.0.1" "https://other.example/v1/hello"
curl -sk -o /dev/null -w 'absolute URL for another host -> %{http_code}\n' --request-target 'http://169.254.169.254/metadata/instance?api-version=2021-02-01' -H "Host: $H" https://127.0.0.1/
curl -s -o /dev/null -w 'port 80 -> %{http_code}\n' http://127.0.0.1/ || true
/v1/nope -> 404
/metadata/instance -> 404
unknown host name -> 000
absolute URL for another host -> 000
port 80 -> 000

000 means the connection was closed without an answer.

Step 6: The JWT protected endpoint

/v1/secure/users/{id} is protected by KrakenD's auth/validator: it accepts only an HS256 token signed with this VM's key (/etc/krakend/jwk-cloudimg.json, generated on first boot), issued by cloudimg-krakend-demo, with the role demo. Without a valid token the request never reaches the backend:

H=$(sudo sed -n 's#^KRAKEND_URL=https://\(.*\)/$#\1#p' /root/krakend-credentials.txt)
T=$(sudo sed -n 's/^KRAKEND_DEMO_TOKEN=//p' /root/krakend-credentials.txt)
curl -s -o /dev/null -w 'no token   -> %{http_code}\n' --cacert /etc/nginx/tls/krakend.crt --resolve "$H:443:127.0.0.1" "https://$H/v1/secure/users/3"
curl -s -o /dev/null -w 'bad token  -> %{http_code}\n' -H 'Authorization: Bearer not.a.token' --cacert /etc/nginx/tls/krakend.crt --resolve "$H:443:127.0.0.1" "https://$H/v1/secure/users/3"
curl -s -K <(printf 'header = "Authorization: Bearer %s"\n' "$T") --cacert /etc/nginx/tls/krakend.crt --resolve "$H:443:127.0.0.1" "https://$H/v1/secure/users/3" | jq .
no token   -> 401
bad token  -> 401
{
  "email": "grace@example.com",
  "id": 3,
  "name": "Grace Hopper",
  "plan": "starter"
}

(curl -K reads the header from a file descriptor, so the token never appears on a command line.) Mint more tokens with krakend-demo-token (--sub, --ttl in seconds, --role); a token with another role gets 403:

T2=$(sudo krakend-demo-token --sub alice --ttl 600)
echo "$T2" | cut -d. -f2 | tr '_-' '/+' | base64 -d 2>/dev/null | jq -c '{iss, sub, roles}'
H=$(sudo sed -n 's#^KRAKEND_URL=https://\(.*\)/$#\1#p' /root/krakend-credentials.txt)
curl -s -o /dev/null -w 'new token  -> %{http_code}\n' -K <(printf 'header = "Authorization: Bearer %s"\n' "$T2") --cacert /etc/nginx/tls/krakend.crt --resolve "$H:443:127.0.0.1" "https://$H/v1/secure/users/2"
{"iss":"cloudimg-krakend-demo","sub":"alice","roles":["demo"]}
new token  -> 200

The JWT protected demo endpoint refusing a request with no token and with a forged token, and answering the per VM demo token

sudo krakend-rotate-jwt-key replaces the signing key (every older token stops working), writes a new demo token and restarts KrakenD. In production you will usually point the validator at your identity provider's JWKS URL (jwk_url, for example your Microsoft Entra ID tenant's keys) instead of a local key; the KrakenD JWT validation documentation covers every option. KrakenD verifies that URL's certificate against the system trust store, which is the store Step 9 checks.

Step 7: Health and internal endpoints

Load balancers and monitoring (for example an Azure Load Balancer or Application Gateway health probe on /health over HTTPS) can use the public /health probe: nginx answers ok only while KrakenD's own /__health reports healthy. KrakenD's internal endpoints are never reachable from the network, and the debug and echo endpoints are switched off even on loopback:

H=$(sudo sed -n 's#^KRAKEND_URL=https://\(.*\)/$#\1#p' /root/krakend-credentials.txt)
for p in /health /__health /__debug/ /__echo/ /__stats; do curl -s -o /dev/null -w "public $p -> %{http_code}\n" --cacert /etc/nginx/tls/krakend.crt --resolve "$H:443:127.0.0.1" "https://$H$p"; done
curl -s http://127.0.0.1:8080/__health | jq -c '{status}'
curl -s -o /dev/null -w 'loopback /__debug/ -> %{http_code}\n' http://127.0.0.1:8080/__debug/
public /health -> 200
public /__health -> 404
public /__debug/ -> 404
public /__echo/ -> 404
public /__stats -> 404
{"status":"ok"}
loopback /__debug/ -> 404

If you enable KrakenD's OpenTelemetry exporters later, keep any port they open bound to 127.0.0.1 and never open it in a network security group.

Step 8: Put your own services behind KrakenD

Edit /etc/krakend/krakend.json and replace the demo endpoints with your own API. The file uses the KrakenD 3.0 configuration format ("version": 4 and the v3.0 schema); KrakenD 3.0 refuses files that still say "version": 3:

sudo jq -c '{"$schema", version, listen_ip, port}' /etc/krakend/krakend.json
{"$schema":"https://www.krakend.io/schema/v3.0/krakend.json","version":4,"listen_ip":"127.0.0.1","port":8080}
sudo nano /etc/krakend/krakend.json

Each endpoint lists its backends; a backend can be a service on this VM or a private address in your virtual network. For example, an endpoint that proxies an orders service on a private host:

{
  "endpoint": "/v1/orders/{id}",
  "method": "GET",
  "backend": [
    { "host": ["http://10.0.1.25:3000"], "url_pattern": "/orders/{id}" }
  ]
}

Keep "listen_ip": "127.0.0.1" (nginx is the public entry point) and leave debug_endpoint and echo_endpoint off. Check the file against KrakenD's built in schema before you restart, so a typing mistake never takes the gateway down (the unit runs the same check before every start):

sudo krakend check -c /etc/krakend/krakend.json -n
Parsing configuration file: /etc/krakend/krakend.json
Syntax OK!
sudo systemctl restart krakend

The demo backend (127.0.0.1:8081) can stay or go; once your own services are behind KrakenD you can remove its server block from /etc/nginx/sites-available/cloudimg-krakend. Whatever you route to, KrakenD cannot reach the Azure metadata service or the WireServer (Step 11).

Moving a 2.x configuration: KrakenD 3.0 needs "version": 4, has no Go plugin support in the Community Edition (Lua scripts cover many of the same needs), and dropped the OpenCensus, InfluxDB and separate metrics port components in favour of OpenTelemetry. See KrakenD's upgrade notes and run krakend check --lint on the result. The previous cloudimg image (KrakenD 2.13) listened on port 8080 in plain HTTP with a public sample backend; this image listens on 443 only.

Step 9: Check CVE-2026-111943 (compromised eMudhra CAs)

CVE-2026-111943 is a Critical advisory: the private keys of eight eMudhra intermediate certificate authorities were compromised, so anyone holding them could issue a certificate that a vulnerable client trusts for any host name. KrakenD verifies certificates with Go's crypto/x509 against the operating system trust store (/etc/ssl/certs), both for its calls to HTTPS backends, JWK URLs and telemetry exporters and for client certificates when you enable mTLS. KrakenD Community Edition 3.0.2 is the fixed release.

This image closes it twice over: KrakenD is built with Go 1.26.10, whose crypto/x509 rejects any chain through the eight intermediates whatever the trust store says, and Ubuntu's ca-certificates update (20260816ubuntu0.24.04.1) removed the roots those chains end in. The shipped check proves both on your own VM:

sudo krakend-cve-2026-111943-check
cve-2026-111943: ok   KrakenD 3.0.2 (>= 3.0.2)
cve-2026-111943: ok   built with Go 1.26.10 (>= 1.26.10)
cve-2026-111943: ok   Go's distrust list of the 8 eMudhra intermediates is compiled into /usr/local/bin/krakend
cve-2026-111943: ok   the running KrakenD (pid <pid>) uses the system trust store
cve-2026-111943: ok   120 trusted certificates: no eMudhra intermediate and no root of the affected chains
cve-2026-111943: ok   positive controls (Amazon Root CA 1, ISRG Root X1) present
cve-2026-111943: ok   ca-certificates 20260816ubuntu0.24.04.1 (>= 20260816ubuntu0.24.04.1)
cve-2026-111943: RESULT CLOSED

The shipped krakend-cve-2026-111943-check reporting RESULT CLOSED: KrakenD 3.0.2 built with Go 1.26.10 carries the eMudhra distrust list and the trust store holds no affected CA

(The two positive controls are ordinary public roots that every Ubuntu trust store carries; they prove the check really read the store.) Run it again after you add your own CA certificates to the system store or replace the KrakenD binary. If you enable mTLS for clients (enable_mtls), KrakenD also trusts the system store for client certificates unless you set disable_system_ca_pool with an explicit ca_certs list, as KrakenD's advisory recommends.

Step 10: Your own DNS name and certificate

Point a DNS record (for example the public IP's Azure DNS label, <label>.<region>.cloudapp.azure.com) at the VM, then set the name the gateway serves. This mints a new self signed certificate for the name, updates the nginx host settings, reloads nginx and keeps the name across reboots:

sudo krakend-set-hostname <your-domain>

To use a certificate from your own certificate authority instead, copy it over the generated pair and reload nginx (running krakend-set-hostname again replaces it with a self signed one):

sudo install -m 0644 <your-domain>.crt /etc/nginx/tls/krakend.crt
sudo install -m 0600 <your-domain>.key /etc/nginx/tls/krakend.key
sudo nginx -t && sudo systemctl reload nginx

Step 11: Check the security posture

The certificate was generated on this VM and names its own addresses; the TLS key and the credentials file are readable only by root, and the JWT signing key only by root and the krakend group:

H=$(sudo sed -n 's#^KRAKEND_URL=https://\(.*\)/$#\1#p' /root/krakend-credentials.txt)
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
sudo stat -c '%a %U:%G %n' /etc/nginx/tls/krakend.key /etc/nginx/tls/krakend.crt /etc/krakend/jwk-cloudimg.json /etc/krakend/krakend.json /root/krakend-credentials.txt
subject=CN = <public-ip>, O = cloudimg
X509v3 Subject Alternative Name: 
    IP Address:<public-ip>, IP Address:<private-ip>, IP Address:127.0.0.1, DNS:localhost
600 root:root /etc/nginx/tls/krakend.key
644 root:root /etc/nginx/tls/krakend.crt
640 root:krakend /etc/krakend/jwk-cloudimg.json
640 root:krakend /etc/krakend/krakend.json
600 root:root /root/krakend-credentials.txt

The client address comes from the connection. A request that claims to come from somewhere else in X-Forwarded-For, and names another host in X-Forwarded-Host, is logged by nginx and KrakenD with its real address, and the backend receives only that address and the configured host name:

H=$(sudo sed -n 's#^KRAKEND_URL=https://\(.*\)/$#\1#p' /root/krakend-credentials.txt)
P=$(hostname -I | awk '{print $1}')
curl -sk -o /dev/null --resolve "$H:443:$P" -H 'X-Forwarded-For: 203.0.113.9' -H 'X-Forwarded-Host: evil.example' "https://$H/v1/users/spoof-test"
sleep 1
sudo grep -h 'spoof-test' /var/log/nginx/access.log | tail -1 | cut -d' ' -f1,6-9
sudo journalctl -u krakend --no-pager -o cat | grep 'GIN.*spoof-test' | tail -1 | tr -s ' '
sudo grep -h 'users/spoof-test' /var/log/nginx/krakend-demo-backend.log | tail -1
<private-ip> "GET /v1/users/spoof-test HTTP/1.1" 500
[GIN] <time> | 500 | <duration> | <private-ip> | GET "/v1/users/spoof-test"
127.0.0.1 [<time>] "GET /users/spoof-test HTTP/1.1" 404 xff="<private-ip>" xfh="<public-ip>" host="127.0.0.1" ua="KrakenD Version 3.0.2"

(The 500 is expected: the demo backend has no user called spoof-test.)

The Azure metadata block. KrakenD routes requests to addresses written in its configuration, so a mistaken or malicious backend entry must never reach the VM's metadata: the Azure Instance Metadata Service (169.254.169.254, which hands out managed identity tokens) and the WireServer (168.63.129.16). KrakenD, nginx and the first boot unit carry a systemd IPAddressDeny for both (and their IPv6 mapped forms). Only krakend-address.service, a small sandboxed unit that runs no KrakenD code and only writes this VM's addresses to /run/cloudimg-krakend/address.env, and the Azure Linux agent and cloud-init themselves keep their access:

for u in krakend nginx krakend-firstboot krakend-address walinuxagent; do printf '%s: ' $u; systemctl show -p IPAddressDeny --value $u.service | tr ' ' '\n' | sort | tr '\n' ' '; echo; done
cg=$(systemctl show -p ControlGroup --value krakend.service); sudo bash -c "echo \$\$ > /sys/fs/cgroup$cg/cgroup.procs; curl -s -m 5 -o /dev/null -w 'inside krakend.service -> Azure IMDS: %{http_code}\n' -H 'Metadata: true' 'http://169.254.169.254/metadata/instance?api-version=2021-02-01'; curl -s -m 5 -o /dev/null -w 'inside krakend.service -> WireServer: %{http_code}\n' 'http://168.63.129.16/?comp=versions'"
curl -s -m 5 -o /dev/null -w 'this VM -> Azure IMDS: %{http_code}\n' -H 'Metadata: true' 'http://169.254.169.254/metadata/instance?api-version=2021-02-01'
krakend: 168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128 
nginx: 168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128 
krakend-firstboot: 168.63.129.16/32 169.254.169.254/32 ::ffff:168.63.129.16/128 ::ffff:169.254.169.254/128 fd00:ec2::254/128 
krakend-address: 
walinuxagent: 
inside krakend.service -> Azure IMDS: 000
inside krakend.service -> WireServer: 000
this VM -> Azure IMDS: 200

KrakenD, nginx and the first boot unit denied the Azure metadata service and the WireServer, refused from inside the krakend service, while the VM, krakend-address and the Azure agent keep their access

000 means the connection was refused inside KrakenD's service before it left the VM. The block covers every connection KrakenD makes, including calls to backends you configure: an endpoint whose backend is 169.254.169.254 cannot reach the metadata service from inside the krakend service, while ordinary internet and private network backends work as before. A consequence: KrakenD itself cannot use the VM's managed identity; give it credentials through its configuration instead.

KrakenD runs as the krakend user with no capabilities and no core dumps, does not report usage to KrakenD's servers, and does not start before first boot has finished or with a configuration that fails the schema check:

systemctl show krakend -p User -p NoNewPrivileges -p ProtectSystem -p ProtectHome -p CapabilityBoundingSet -p LimitCORE
systemctl cat krakend | grep -E '^(ExecStartPre|Environment)='
LimitCORE=0
CapabilityBoundingSet=
User=krakend
ProtectHome=yes
ProtectSystem=strict
NoNewPrivileges=yes
Environment=USAGE_DISABLE=1
ExecStartPre=+/usr/local/sbin/krakend-guard
ExecStartPre=/usr/local/bin/krakend check -c /etc/krakend/krakend.json -n

SSH allows no root login, no empty passwords and no X11 forwarding (/etc/ssh/sshd_config.d/00-cloudimg.conf). Whether SSH accepts passwords is the choice you made in the Azure portal (key only by default), and the image does not override it:

sudo sshd -T | grep -E '^(permitrootlogin|permitemptypasswords|x11forwarding) '
permitrootlogin no
x11forwarding no
permitemptypasswords no

Step 12: Build provenance

KrakenD on this image was compiled by cloudimg from the unmodified upstream 3.0.2 source. The tagged go.mod and go.sum were used as released except for one library: golang.org/x/net was raised to v0.62.0 (with the four golang.org/x libraries it needs) because two advisories in its HTML package, GO-2026-6860 and GO-2026-6866, were published on 27 October 2026, after KrakenD's release, and govulncheck reports both as reachable in the unmodified build. The build record, the resulting go.mod and go.sum, and the govulncheck result ship with the image:

ls -l /usr/share/doc/krakend | awk 'NR>1{print $1,$9}'
sha256sum /usr/local/bin/krakend
sed -n '/^source /,/^toolchain sha256/p' /usr/share/doc/krakend/BUILD-PROVENANCE.txt
grep '^Your code' /usr/share/doc/krakend/govulncheck-binary.txt
-rw-r--r-- BUILD-PROVENANCE.txt
-rw-r--r-- LICENSE
-rw-r--r-- README.md
-rw-r--r-- go.mod
-rw-r--r-- go.sum
-rw-r--r-- govulncheck-binary.txt
a44ee3b23d61ba792177ede1f5c5d0dcff3a68b1fd120dcaffe1ae89b94e21c6  /usr/local/bin/krakend
source            https://github.com/krakend/krakend-ce/archive/refs/tags/v3.0.2.tar.gz
source sha256     8f05f699e3161ec7396011f5eaf0cf4995f0f9f2ff9b925e044e1668d1bdbaac
tag commit        0ecf3bf041136d6a6a44e330ebce2c466485f8d0
go.mod / go.sum   as tagged (sha256 afeea0f8d4267d5e1ae23aa17039e67c0c72965b6f18688f6cca2ba3a81c2fd6 / 9d321168d67c267a5472c3a6a6a81868d436c1a410e25f369cbbce654f5ec824), then
                  go get golang.org/x/net@v0.62.0: GO-2026-6860 (CVE-2026-78661) and GO-2026-6866 (CVE-2026-97045) in
                  golang.org/x/net/html were published on 2026-10-27, after upstream's release; resulting lines:
                  golang.org/x/crypto v0.58.0, golang.org/x/net v0.62.0, golang.org/x/sync v0.24.0, golang.org/x/sys v0.49.0, golang.org/x/text v0.43.0
                  resulting go.mod / go.sum sha256 bd6e4934a282a8324776254b42572d4fecc669a09d32a1857827fc298b98ba0a / af707a6d475826b9b99cfbe0ce011b2354a1a3d7ce0fec70ed80c243371a5664
unmodified graph  compiles to 46fc12a2ec8eb0f4239bee84c954b328c34094bddf7a6c6c6b5bfbf423a9de5b (not shipped: GO-2026-6860/6866 reachable)
schema            https://raw.githubusercontent.com/krakend/krakend-schema/0dab76c651fc6be9e578d7cab8e79674e9840a79/v3.0/krakend.json (sha256 8429674ca1ef8d4519079d36099667653546af7300053adb7c4a0f9a7557d9e6; byte-identical to the schema in upstream's 3.0.2 binary)
toolchain         go1.26.10 linux/amd64, official tarball https://go.dev/dl/go1.26.10.linux-amd64.tar.gz
toolchain sha256  ccfe7ae822d2c2f41f4fe7c956199967a4dac22d0ee0d2b2bfdb440aab3b20c5
Your code is affected by 0 vulnerabilities.

The build is reproducible: compiling the same source archive, the same go get and the same flags (recorded in BUILD-PROVENANCE.txt) with Go 1.26.10 gives the same SHA-256 on any machine. It is a static build (CGO_ENABLED=0). The Go toolchain and module cache were used on a temporary in memory filesystem and are not on the image.

Step 13: Logs

KrakenD writes its startup messages and one line per request (client address and HTTP method included) to the journal; nginx logs public requests to /var/log/nginx/access.log and the demo backend's requests to /var/log/nginx/krakend-demo-backend.log:

sudo journalctl -u krakend --no-pager -o cat | sed 's/\x1b\[[0-9;]*m//g' | grep -E 'Starting KrakenD|Listening on' | tail -2
[KRAKEND] <time> ▶ INFO Starting KrakenD v3.0.2
[KRAKEND] <time> ▶ INFO [SERVICE: Gin] Listening on port: 8080

KrakenD 3.0 also adds the HTTP method to its endpoint log lines (for example [ENDPOINT: GET /v1/users/:id]), so update any log parsing you carried over from KrakenD 2.x.

Step 14: Server components

Component Path
KrakenD binary /usr/local/bin/krakend
Gateway configuration /etc/krakend/krakend.json
JWT signing key (demo endpoint) /etc/krakend/jwk-cloudimg.json
HTTPS front end /etc/nginx/sites-available/cloudimg-krakend
Certificate and key /etc/nginx/tls/
Demo backend content /usr/share/cloudimg-krakend/demo-backend/
Service units krakend.service, krakend-firstboot.service, krakend-address.service, nginx.service
Metadata block /etc/systemd/system/krakend.service.d/50-cloudimg-imds-deny.conf (and nginx, first boot)
This VM's addresses (every boot) /run/cloudimg-krakend/address.env
Helpers krakend-set-hostname, krakend-demo-token, krakend-rotate-jwt-key, krakend-cve-2026-111943-check in /usr/local/sbin
Licence and build provenance /usr/share/doc/krakend/
VM details /root/krakend-credentials.txt (mode 0600)

Step 15: Updating KrakenD

Ubuntu's security updates install automatically, including the ca-certificates trust store KrakenD uses. KrakenD is a single binary that is not managed by apt on this image. KrakenD publishes security fixes for the 3.x line of the Community Edition. To move to a newer KrakenD release, replace /usr/local/bin/krakend with a newer official release (or your own build), check your configuration with the new binary, and restart:

sudo /tmp/krakend-<version>/krakend check -c /etc/krakend/krakend.json -n
sudo install -m 0755 /tmp/krakend-<version>/krakend /usr/local/bin/krakend && sudo systemctl restart krakend

Read the KrakenD release notes and security advisories first, and run sudo krakend-cve-2026-111943-check after a replacement. cloudimg publishes updated versions of this image as new KrakenD releases ship.

Support

cloudimg provides 24/7 support for this image, the Ubuntu OS and deployment. Support for KrakenD itself is from the KrakenD community; commercial support is available from KrakenD. Contact cloudimg at support@cloudimg.co.uk.

KrakenD is a trademark of its respective owner. cloudimg is not affiliated with or endorsed by KrakenD or the KrakenD project.