Deployment guide

KrakenD

Overview

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.

This guide covers deploying and using the KrakenD Community Edition 3.0.2 on Ubuntu 24.04 LTS image from SecureAnyCloud on Microsoft Azure. KrakenD Community Edition is the open source KrakenD (Apache-2.0). KrakenD Enterprise is a separate commercial product from KrakenD and is not included.

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

What you get:

  • 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. 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. 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. 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. KrakenD's anonymous usage reporting is 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). It raises 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, license 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.
1

Deploy the VM

In the Azure portal, open Create a resource. Search for KrakenD Community Edition on Ubuntu 24.04 LTS by SecureAnyCloud and select Create.

On the Basics tab, choose Standard_B2s and SSH public key authentication with azureuser as the user name. KrakenD is light, so size up only for high request rates. On the Networking tab, allow 22 and 443 as described above.

To deploy with the Azure CLI instead, replace the values in angle brackets:

az vm image terms accept --urn sac1647283583153:krakend-ubuntu-24-04:default:latest
az vm create --resource-group <resource-group> --name krakend \
  --image sac1647283583153: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 and generates the certificate, the JWT signing key and a demo token. Then KrakenD and nginx start.

2

Connect to your VM

ssh azureuser@<public-ip>
3

Check the services

systemctl is-active krakend-address krakend nginx
sudo test -f /var/lib/sac/data/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.

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.
# sac 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-sac.json KRAKEND_JWT_ISSUER=sac-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://secureanycloud.com/stacks/krakend/

The URL uses the VM's public IP address. krakend-address.service reads it from the Azure Instance Metadata Service at every boot. Step 11 explains why only that small unit may reach the service.

The certificate also names the private IP address. To serve your own DNS name, see Step 10.

5

Send requests through KrakenD

The shipped configuration declares three endpoints in front of the demo backend. /v1/hello proxies one backend call. /v1/users/{id} calls the backend twice, for the user record and the user's orders, and merges both answers into one response.

From the VM itself, send requests and trust 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": "sac-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

KrakenD only answers the endpoints you declare:

  • Any other path returns 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.
  • 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.

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-sac.json, generated on first boot), issued by sac-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":"sac-krakend-demo","sub":"alice","roles":["demo"]} new token -> 200

sudo krakend-rotate-jwt-key replaces the signing key, so every older token stops working. It also 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.

7

Health and internal endpoints

Load balancers and monitoring can use the public /health probe, for example an Azure Load Balancer or Application Gateway health probe on /health over HTTPS. nginx answers ok only while KrakenD's own /__health reports healthy.

KrakenD's internal endpoints are never reachable from the network. 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. Never open it in a network security group.

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, this endpoint 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", because nginx is the public entry point. 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/sac-krakend. Whatever you route to, KrakenD cannot reach the Azure metadata service or the WireServer (Step 11).

To move a 2.x configuration, note these changes in KrakenD 3.0:

  • It needs "version": 4.
  • It has no Go plugin support in the Community Edition. Lua scripts cover many of the same needs.
  • It dropped the OpenCensus, InfluxDB and separate metrics port components in favor of OpenTelemetry.

See KrakenD's upgrade notes and run krakend check --lint on the result. The previous SecureAnyCloud image (KrakenD 2.13) listened on port 8080 in plain HTTP with a public sample backend. This image listens on 443 only.

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). It does so 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 the issue 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. Ubuntu's ca-certificates update (20260816ubuntu0.24.04.1) also 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 two positive controls are ordinary public roots that every Ubuntu trust store carries. They prove the check really read the store.

Run the check 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. To avoid this, set disable_system_ca_pool with an explicit ca_certs list, as KrakenD's advisory recommends.

10

Your own DNS name and certificate

Point a DNS record at the VM, for example the public IP's Azure DNS label, <label>.<region>.cloudapp.azure.com. Then set the name the gateway serves. This command 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
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 is readable 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-sac.json /etc/krakend/krakend.json /root/krakend-credentials.txt
subject=CN = <public-ip>, O = sac 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-sac.json 640 root:krakend /etc/krakend/krakend.json 600 root:root /root/krakend-credentials.txt

The client address comes from the connection. Suppose a request claims to come from somewhere else in X-Forwarded-For and names another host in X-Forwarded-Host. nginx and KrakenD log it 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, because 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. That metadata is 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 addresses and their IPv6-mapped forms. Only these keep their access:

  • krakend-address.service, a small sandboxed unit that runs no KrakenD code and only writes this VM's addresses to /run/sac-krakend/address.env.
  • The Azure Linux agent.
  • cloud-init.
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

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. As 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. It does not report usage to KrakenD's servers. It 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-sac.conf). Whether SSH accepts passwords is the choice you made in the Azure portal, key only by default. The image does not override it:

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

Build provenance

SecureAnyCloud compiled KrakenD on this image 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. 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.

13

Logs

KrakenD writes its startup messages and one line per request, including the client address and HTTP method, 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]. Update any log parsing you carried over from KrakenD 2.x.

14

Server components

Each component and its path:

KrakenD binary: /usr/local/bin/krakend

Gateway configuration: /etc/krakend/krakend.json

JWT signing key (demo endpoint): /etc/krakend/jwk-sac.json

HTTPS front end: /etc/nginx/sites-available/sac-krakend

Certificate and key: /etc/nginx/tls/

Demo backend content: /usr/share/sac-krakend/demo-backend/

Service units: krakend.service, krakend-firstboot.service, krakend-address.service, nginx.service

Metadata block: /etc/systemd/system/krakend.service.d/50-sac-imds-deny.conf (and nginx, first boot)

This VM's addresses (every boot): /run/sac-krakend/address.env

Helpers: krakend-set-hostname, krakend-demo-token, krakend-rotate-jwt-key and krakend-cve-2026-111943-check in /usr/local/sbin

License and build provenance: /usr/share/doc/krakend/

VM details: /root/krakend-credentials.txt (mode 0600)

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, then 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. Run sudo krakend-cve-2026-111943-check after a replacement. SecureAnyCloud publishes updated versions of this image as new KrakenD releases ship.

Support

SecureAnyCloud provides 24/7 support for this image, the Ubuntu OS and deployment. Support for KrakenD itself comes from the KrakenD community, and commercial support is available from KrakenD. Contact SecureAnyCloud at sales@secureanycloud.com.

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

Deploy on Azure

Launch KrakenD Community Edition 3.0.2 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