Application Infrastructure AWS

KrakenD API Gateway 2.13.11 on AWS User Guide

| Product: KrakenD API Gateway 2.13.11 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the KrakenD API Gateway 2.13.11 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg application infrastructure listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

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:

  • HTTPS from the first boot. nginx terminates TLS on port 443 with a certificate generated on your instance'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.
  • 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 instance 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 instance key. /v1/secure/users/{id} is protected by KrakenD's JWT validator against an HS256 signing key generated on your instance'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 instance metadata service, 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 source, with the advisories fixed. The upstream 2.13.11 release binary carries 15 Go security advisories that govulncheck reports as reachable. This image compiles the pinned 2.13.11 source with Go 1.26.10 and patched gRPC, OpenTelemetry, AMQP and x/net modules: govulncheck reports 0 reachable advisories, and the binary is reproducible (Step 11).
  • Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on.

What is on the image: KrakenD 2.13.11 Community Edition (/usr/local/bin/krakend, licence and build provenance in /usr/share/doc/krakend), the gateway configuration /etc/krakend/krakend.json, nginx, the krakend.service and krakend-firstboot.service units, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 (SSH) from your own networks only 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: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick KrakenD API Gateway 2.13.11 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (KrakenD is light; size up for high request rates), your key pair, and a security group as above.

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

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=krakend}]'

Step 2: Connect to your instance

Version SSH user
KrakenD API Gateway 2.13.11 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot generates the certificate, the JWT signing key and a demo token, then KrakenD and nginx start:

systemctl is-active krakend nginx
sudo test -f /var/lib/cloudimg/krakend-firstboot.done && echo FIRSTBOOT_DONE
krakend version
active
active
FIRSTBOOT_DONE
KrakenD Version: 2.13.11
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",...))

Step 4: Your instance's URL and demo token

sudo cat /root/krakend-credentials.txt

The file is readable by root only:

# KrakenD API Gateway 2.13.11 - generated on first boot by krakend-firstboot.service
# The JWT signing key and the demo token are unique to this instance; none was baked into the image. Keep this secret.

KRAKEND_URL=https://ec2-<public-ip-dashed>.compute-1.amazonaws.com/
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-api-gateway-2-13-11-on-ubuntu-24-04-aws/

The URL uses the instance's public DNS name from EC2 instance metadata. The certificate also names the public and private IP addresses, so https://<public-ip>/ works too. To serve your own DNS name, see Step 9.

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 instance itself, trusting the instance'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 2.13.11
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 9 covers your own name and certificate):

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

KrakenD only answers the endpoints you declare. Any other path is 404, a host name the instance does not serve is dropped, an absolute URL that tries to use the gateway as a forward proxy to another host (here the instance 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 /latest/meta-data/; 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/latest/meta-data/' -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
/latest/meta-data/ -> 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 instance'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

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) instead of a local key; the KrakenD JWT validation documentation covers every option.

Step 7: Health and internal endpoints

Load balancers and monitoring 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 metrics or OpenTelemetry exporters later, bind them to 127.0.0.1 and never open their ports in a security group.

Step 8: Put your own services behind KrakenD

Edit /etc/krakend/krakend.json and replace the demo endpoints with your own API. Each endpoint lists its backends; a backend can be a service on this instance or a private address in your VPC:

sudo nano /etc/krakend/krakend.json

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 instance metadata service (Step 10).

Plugins: this image's KrakenD is a static build (CGO_ENABLED=0), so KrakenD 2.x Go plugins (.so files) cannot be loaded; every built in KrakenD Community Edition component works. KrakenD 3.0 removed plugin support upstream.

Step 9: Your own DNS name and certificate

Point a DNS record at the instance, 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 10: Check the security posture

The certificate was generated on this instance 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 = ec2-<public-ip-dashed>.compute-1.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name: 
    DNS:ec2-<public-ip-dashed>.compute-1.amazonaws.com, 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="ec2-<public-ip-dashed>.compute-1.amazonaws.com" host="127.0.0.1" ua="KrakenD Version 2.13.11"

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

KrakenD runs as the krakend user with no capabilities, cannot reach the instance metadata service, 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 IPAddressDeny
systemctl cat krakend | grep -E '^(ExecStartPre|Environment)='
IPAddressDeny=fd00:ec2::254/128 169.254.169.254/32 168.63.129.16/32
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

The metadata 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. nginx and the first boot unit carry the same block.

Step 11: Build provenance

KrakenD on this image was compiled by cloudimg from the unmodified upstream 2.13.11 source. A minimal set of Go modules was raised to clear the advisories in the upstream release binary: gRPC 1.83.2, OpenTelemetry 1.45.0, the RabbitMQ AMQP client 1.14.0 and golang.org/x/net 0.60.0 (plus the modules those require). 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,$5,$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-- 3357 BUILD-PROVENANCE.txt
-rw-r--r-- 11357 LICENSE
-rw-r--r-- 3888 README.md
-rw-r--r-- 15459 go.mod
-rw-r--r-- 123725 go.sum
-rw-r--r-- 301 govulncheck-binary.txt
eff034a0e3f0e20082b19c37728dfd6297c10dfba9b77f8804668d6d9d2ae9e3  /usr/local/bin/krakend
source            https://github.com/krakend/krakend-ce/archive/refs/tags/v2.13.11.tar.gz
source sha256     eb69e9f515922d705d865ee75ee3cc9ea0d5787f6f5062c15b77ccb9bb702215
tag commit        1f83305f3f096b7b75ec9c0cde0ec14b542cb1d6
schema            https://raw.githubusercontent.com/krakend/krakend-schema/779f6028840e7a5b84ee3593889869a86b89c64f/v2.13/krakend.json (sha256 c7afe896df2485ca182cffd257d4562f1c0d66e110d1ea3dee1ab0a10c2acc6b; byte-identical to the schema in upstream's 2.13.11 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, module versions and flags (recorded in BUILD-PROVENANCE.txt) with Go 1.26.10 gives the same SHA-256. The Go toolchain and module cache were used on a temporary in memory filesystem and are not on the image.

Step 12: Logs

KrakenD writes its startup messages and one line per request (client address 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 v2.13.11
[KRAKEND] <time> ▶ INFO [SERVICE: Gin] Listening on port: 8080

Step 13: 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, nginx.service
Metadata block /etc/systemd/system/krakend.service.d/50-cloudimg-imds-deny.conf (and nginx, first boot)
Helpers krakend-set-hostname, krakend-demo-token, krakend-rotate-jwt-key in /usr/local/sbin
Licence and build provenance /usr/share/doc/krakend/
Instance details /root/krakend-credentials.txt (mode 0600)

Step 14: Updating KrakenD

Ubuntu's security updates install automatically. KrakenD is a single binary that is not managed by apt on this image. 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 first: KrakenD 3.0 changes the configuration format and removes plugins, so test a major upgrade on a copy of the instance. cloudimg publishes updated versions of this image as new KrakenD releases ship.

Support

cloudimg provides 24/7 technical support for this image by email and live chat: deployment, HTTPS and certificates, designing endpoints and backends, JWT validation, upgrades and troubleshooting. Contact 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.