OpenFGA 1.22.1
Overview
OpenFGA is an open-source authorization server, a Cloud Native Computing Foundation project inspired by Google's Zanzibar paper. Instead of writing permission checks into every application, you describe your permission model once and write relationship tuples as things happen. At request time, you ask OpenFGA whether a user may act on an object.
This guide covers the SecureAnyCloud OpenFGA 1.22.1 on Ubuntu 24.04 AMI on Amazon EC2. It is one version of the SecureAnyCloud identity, secrets and PKI listing on AWS Marketplace. You pick it as the software version when you subscribe and launch.
For example, a model might say "a viewer of a document is anyone it was shared with, or its owner". A tuple might say "user:anne is owner of document:roadmap". You then ask Check (can anne view the roadmap?), ListObjects (which documents can anne view?) or ListUsers. OpenFGA has an HTTP (REST) API and a gRPC API, and SDKs for Go, JavaScript, Python, Java and .NET.
A stock OpenFGA server answers every request without authentication, binds its HTTP, gRPC, metrics and playground ports on every interface, and keeps its data in memory. The SecureAnyCloud image makes it safe to run on the internet.
What you get:
- A key unique to your instance. Preshared-key authentication is on. First boot creates a 64-character API key and writes it to one file,
/root/openfga-credentials.txt, readable by root only. Every API request needsAuthorization: Bearer <key>. Without it, OpenFGA answers401. The key is never on a command line, in a service's environment or in a log. - Nothing answers until first boot is done. The image ships with no key, no database and no certificate. OpenFGA and nginx start only after first boot has created them, and nginx starts only once OpenFGA refuses a request without the key.
- HTTPS from first boot. nginx serves the HTTP API on port 443 with a certificate generated on your instance's first boot. It is an end-entity certificate naming the instance's public address, public DNS name and private address. Port 80 only redirects to HTTPS. OpenFGA itself listens on
127.0.0.1only. - A PostgreSQL datastore of your own. First boot creates a PostgreSQL 16 database for OpenFGA with a password unique to your instance (SCRAM, loopback only), so your stores, models and tuples survive restarts. A starter store with a simple user/document model is created for you.
- Internal ports closed. gRPC (8081), Prometheus metrics (2112) and PostgreSQL (5432) listen on loopback only. The playground and the profiler are switched off.
- Metadata service blocked. OpenFGA, nginx, PostgreSQL and the first-boot services are blocked from the instance metadata service, and the AMI requires IMDSv2. OpenFGA does not fetch user-supplied URLs.
- Pinned and patched. OpenFGA 1.22.1 is the official release binary, pinned by its SHA-256. Ubuntu 24.04 LTS is fully patched at build time, with unattended security upgrades switched on.
What is on the image: OpenFGA 1.22.1 (/usr/local/bin/openfga), PostgreSQL 16, nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.
Default user: ubuntu
Architecture: x86_64
Recommended instance type: m5.large (t3.medium is enough for development and small workloads)
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet.
- A security group that allows TCP 22 (SSH) from your administrators and TCP 443 (the API) from the hosts whose applications ask OpenFGA for decisions. Port 80 is optional, because it only redirects to HTTPS. Never open 8080, 8081, 2112 or 5432. They listen on loopback only and refuse connections from the network even if a security group allows them.
Launch the instance
In the AWS Marketplace listing, choose Continue to Subscribe, then Continue to Configuration. Pick OpenFGA 1.22.1 on Ubuntu 24.04 as the software version, select your Region, and choose Launch through EC2. Then choose your instance type, key pair, subnet and a security group as described above.
To launch with the AWS CLI instead, replace the values in angle brackets:
aws ec2 create-security-group --region <region> --vpc-id <your-vpc> \
--group-name openfga --description "OpenFGA: SSH from admins, HTTPS from applications"
aws ec2 authorize-security-group-ingress --region <region> --group-id <sg-id> --ip-permissions \
'IpProtocol=tcp,FromPort=22,ToPort=22,IpRanges=[{CidrIp=<your-admin-cidr>}]' \
'IpProtocol=tcp,FromPort=443,ToPort=443,IpRanges=[{CidrIp=<your-client-cidr>}]'
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 <sg-id> --subnet-id <your-subnet> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=openfga}]'First boot takes well under a minute. It names the server after the instance's public IPv4 address, or after its private address when it has no public one. The certificate also covers the public DNS name and the private address. To use your own DNS name, see Step 10.
Connect to your instance
Connect over SSH as ubuntu, the default user for the OpenFGA 1.22.1 on Ubuntu 24.04 version:
ssh -i <your-key.pem> ubuntu@<public-ip>Check the services
OpenFGA, nginx and PostgreSQL run as systemd services. Check that they are active and that first boot has left its completion marker:
systemctl is-active openfga nginx postgresql@16-main
sudo test -f /var/lib/sac/data/openfga-firstboot.done && echo FIRSTBOOT_DONEactive active active FIRSTBOOT_DONEThe journal shows the order of events on first boot. The bootstrap-ready marker is written last, after the key, the database and the certificate exist. OpenFGA starts after it, and nginx starts after OpenFGA. nginx refuses to start until OpenFGA rejects both a request without the key and a request with a wrong key. The starter store is created at the end.
sudo journalctl -b -o short-iso -u openfga-firstboot -u openfga -u nginx -u openfga-seed \
| grep -E 'OPENFGA_BOOTSTRAP_READY|OPENFGA_SEED_DONE|Started (openfga|nginx)\.service' | cut -d' ' -f3-openfga-firstboot.sh[849]: [2026-11-08T01:54:10+0000] openfga-firstboot: OPENFGA_BOOTSTRAP_READY 2026-11-08T01:54:10.400074745Z systemd[1]: Started openfga.service - OpenFGA 1.22.1 authorization server (loopback only; nginx is the public door). systemd[1]: Started nginx.service - A high performance web server and a reverse proxy server. openfga-seed[1209]: [2026-11-08T01:54:14+0000] openfga-seed: OPENFGA_SEED_DONE 01M6WDT137H39449MSTCDNXEVNOnly SSH, HTTP (redirect) and HTTPS listen on the network. OpenFGA's HTTP API (8080), gRPC (8081), metrics (2112) and PostgreSQL (5432) listen on 127.0.0.1 only. The openfga-port-check command confirms this:
sudo ss -tlnH | awk '{print $4}' | sort -u sudo openfga-port-check --running0.0.0.0:22 0.0.0.0:443 0.0.0.0:80 127.0.0.1:2112 127.0.0.1:5432 127.0.0.1:8080 127.0.0.1:8081 127.0.0.53%lo:53 127.0.0.54:53 [::]:22 [::]:443 [::]:80 OPENFGA_PORT_CHECK_OKThe openfga-selftest command runs a complete round trip with your key: it creates a store, writes a model and a tuple, runs Check (allowed and denied) and ListObjects, and deletes the store. It also confirms that requests without a key or with a wrong key are refused.
sudo openfga-selftestopenfga-selftest: no key 401, wrong key 401; store + model + tuple; Check allowed/denied; ListObjects; store deleted OPENFGA_SELFTEST_OKRead your instance's details
Everything you need to connect is in /root/openfga-credentials.txt (mode 0600, root only). Print the file without the key line:
sudo grep -v '^OPENFGA_API_TOKEN=' /root/openfga-credentials.txt# OpenFGA 1.22.1 on this instance. Keep this file secret; copy the key into your secrets manager.
# Every API request needs the header: Authorization: Bearer <OPENFGA_API_TOKEN> OPENFGA_API_URL=https://203.0.113.25 OPENFGA_HOST=203.0.113.25 OPENFGA_TLS_SHA256=1E:02:4F:A5:68:52:F3:A0:04:47:44:D1:BD:22:F6:8A:C6:A2:74:EA:39:49:24:22:FC:EA:E9:67:F0:DF:83:6B OPENFGA_ADDRESS_SOURCE=imdsv2-public-ipv4 OPENFGA_STORE_ID=01M6WDT137H39449MSTCDNXEVN GUIDE=https://secureanycloud.com/stacks/openfga-1-22-1/The fields mean the following:
OPENFGA_API_URL: the HTTPS address of the API.
OPENFGA_HOST: the name the server answers to and the certificate is issued for. Requests for any other name are dropped.
OPENFGA_API_TOKEN: your preshared API key (64 hex characters). Send it as Authorization: Bearer <key>.
OPENFGA_TLS_SHA256: the SHA-256 fingerprint of the certificate, so clients can pin it.
OPENFGA_STORE_ID: the starter store that first boot created (sac-starter, with a simple user/document model).
The key is unique to your instance. It lives only in this file and in OpenFGA's configuration, /etc/openfga/config.yaml, which only root and the openfga service can read. Store the key in your secrets manager and never put it on a command line. The examples below read it from a header file instead.
Call the API with your key
The certificate is self-signed and generated on your instance, so clients trust it by its file or by its fingerprint.
On the instance, copy the certificate, write the key into a header file that only you can read, and list the stores:
sudo cat /etc/ssl/openfga/openfga.crt > ~/openfga.crt umask 077
sudo sed -n 's/^OPENFGA_API_TOKEN=/Authorization: Bearer /p' /root/openfga-credentials.txt > ~/.openfga.h
H=$(cat /etc/sac/openfga-domain)
curl -s --cacert ~/openfga.crt --connect-to ::127.0.0.1:443 -H @"$HOME/.openfga.h" "https://$H/stores" \
| jq -c '.stores[] | {name, id}'{"name":"sac-starter","id":"01M6WDT137H39449MSTCDNXEVN"}The --connect-to ::127.0.0.1:443 option sends the request to nginx on this instance while still using the server's name, so the commands work from the instance whatever its public address.
From your workstation, copy the two files over SSH and call the public address directly. The security group must allow port 443 from your address.
scp -i <your-key.pem> ubuntu@<public-ip>:openfga.crt ubuntu@<public-ip>:.openfga.h . chmod 600 .openfga.h curl -s --cacert openfga.crt -H @.openfga.h https://<public-ip>/storesThe OpenFGA SDKs and the fga command-line tool take the same two settings: the API URL (OPENFGA_API_URL) and the API token (OPENFGA_API_TOKEN). Add the certificate to the client's trust store.
For example, with the OpenFGA CLI on your workstation, read the token from your secrets manager into the environment, not onto the command line:
export FGA_API_URL=https://<public-ip>
export FGA_API_TOKEN="$(cat my-openfga-token)"
export SSL_CERT_FILE=$PWD/openfga.crt fga store listModel permissions and write relationships
Create a store for your application, give it an authorization model and write relationship tuples.
The model below has users, folders and documents. A document's viewers are the users it was shared with, its owner, and the viewers of its parent folder.
H=$(cat /etc/sac/openfga-domain) api() { curl -s --cacert ~/openfga.crt --connect-to ::127.0.0.1:443 -H @"$HOME/.openfga.h" -H 'Content-Type: application/json' "$@"; } api -X POST -d '{"name":"docs-demo"}' "https://$H/stores" | jq -r .id > ~/.openfga-store
S=$(cat ~/.openfga-store)
cat > ~/model.json <<'MODEL' { "schema_version": "1.1", "type_definitions": [ { "type": "user" }, { "type": "folder", "relations": { "viewer": { "this": {} } }, "metadata": { "relations": { "viewer": { "directly_related_user_types": [ { "type": "user" } ] } } } }, { "type": "document", "relations": { "parent": { "this": {} }, "owner": { "this": {} }, "viewer": { "union": { "child": [ { "this": {} }, { "computedUserset": { "relation": "owner" } }, { "tupleToUserset": { "tupleset": { "relation": "parent" }, "computedUserset": { "relation": "viewer" } } } ] } } }, "metadata": { "relations": { "parent": { "directly_related_user_types": [ { "type": "folder" } ] }, "owner": { "directly_related_user_types": [ { "type": "user" } ] }, "viewer": { "directly_related_user_types": [ { "type": "user" } ] } } } } ] } MODEL api -X POST -d @"$HOME/model.json" "https://$H/stores/$S/authorization-models" | jq -r '"model: " + (.authorization_model_id | length | tostring) + "-character id"' api -X POST -d '{"writes":{"tuple_keys":[ {"user":"user:anne","relation":"owner","object":"document:roadmap"}, {"user":"user:beth","relation":"viewer","object":"folder:engineering"}, {"user":"folder:engineering","relation":"parent","object":"document:spec"}, {"user":"user:carl","relation":"viewer","object":"document:spec"}]}}' "https://$H/stores/$S/write"; echo api -X POST -d '{}' "https://$H/stores/$S/read" | jq -r '.tuples[].key | "\(.user) \(.relation) \(.object)"' | sortmodel: 26-character id {} folder:engineering parent document:spec user:anne owner document:roadmap user:beth viewer folder:engineering user:carl viewer document:specThe write returns {} on success. The model is the JSON form of the OpenFGA modeling language. The OpenFGA documentation explains how to model your own application, and the fga model transform command converts the DSL to this JSON.
Ask OpenFGA: Check and ListObjects
Now ask the questions your application asks at request time. The tuples written in Step 6 give these results:
- Anne owns the roadmap, so she can view it.
- Beth views the engineering folder, so she can view the spec inside it but not the roadmap.
- Carl was given the spec only.
Run the checks:
H=$(cat /etc/sac/openfga-domain);
S=$(cat ~/.openfga-store) api() { curl -s --cacert ~/openfga.crt --connect-to ::127.0.0.1:443 -H @"$HOME/.openfga.h" -H 'Content-Type: application/json' "$@"; } check() { api -X POST -d "{\"tuple_key\":{\"user\":\"$1\",\"relation\":\"$2\",\"object\":\"$3\"}}" "https://$H/stores/$S/check" \
| jq -r --arg q "$1 $2 $3" '"\($q): \(.allowed)"'; } check user:anne viewer document:roadmap check user:beth viewer document:spec check user:beth viewer document:roadmap check user:carl viewer document:roadmap api -X POST -d '{"type":"document","relation":"viewer","user":"user:beth"}' "https://$H/stores/$S/list-objects" | jq -c '.objects | sort' api -X POST -d '{"object":{"type":"document","id":"spec"},"relation":"viewer","user_filters":[{"type":"user"}]}' \
"https://$H/stores/$S/list-users" | jq -c '[.users[].object.id] | sort'user:anne viewer document:roadmap: true user:beth viewer document:spec: true user:beth viewer document:roadmap: false user:carl viewer document:roadmap: false ["document:spec"] ["beth","carl"]ListObjects answers "which documents can Beth view?". ListUsers answers "who can view the spec?".
Requests without the key are refused
Every API call needs your key. A request without it, or with any other key, gets 401. The health check answers without a key so that load balancers can probe it. The unauthenticated playground is switched off.
H=$(cat /etc/sac/openfga-domain) umask 077; printf 'Authorization: Bearer %s\n' "$(openssl rand -hex 32)" > ~/.wrong.h c() { curl -s -o /dev/null -w '%{http_code}' --cacert ~/openfga.crt --connect-to ::127.0.0.1:443 "$@"; } echo "no key: $(c "https://$H/stores")" echo "wrong key: $(c -H @"$HOME/.wrong.h" "https://$H/stores")" echo "your key: $(c -H @"$HOME/.openfga.h" "https://$H/stores")" echo "health check: $(c "https://$H/healthz")" echo "playground: $(c -H @"$HOME/.openfga.h" "https://$H/playground")" echo "port 3000: $(curl -s -m 3 -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/)" rm -f ~/.wrong.hno key: 401 wrong key: 401 your key: 200 health check: 200 playground: 404 port 3000: 000The same applies to OpenFGA's own HTTP port and to gRPC on loopback. Without the key, OpenFGA answers 401 on 8080 and refuses gRPC calls on 8081 (grpc-status 1010, "bearer token missing"). A wrong key gets 1500, "unauthenticated".
From the network, ports 8080, 8081, 2112 and 5432 refuse connections even when a security group allows them. Port 80 only redirects to HTTPS. nginx drops requests for names other than OPENFGA_HOST and its other configured names.
Reboot: your data stays
Your stores, models and tuples are in PostgreSQL on the instance's disk. The key and certificate do not change on a reboot, because first boot runs only once. Reboot the instance:
sudo rebootWhen the instance is back, confirm that the services are up and that the tuples from Step 6 are still there:
H=$(cat /etc/sac/openfga-domain);
S=$(cat ~/.openfga-store) api() { curl -s --cacert ~/openfga.crt --connect-to ::127.0.0.1:443 -H @"$HOME/.openfga.h" -H 'Content-Type: application/json' "$@"; } systemctl is-active openfga nginx postgresql@16-main api -X POST -d '{}' "https://$H/stores/$S/read" | jq '.tuples | length' api -X POST -d '{"tuple_key":{"user":"user:beth","relation":"viewer","object":"document:spec"}}' "https://$H/stores/$S/check" | jq -c .active active active 4 {"allowed":true,"resolution":""}Use your own name and certificate
The server answers to the name in OPENFGA_HOST. A stop and start (not a reboot) gives the instance a new public IPv4 address, so either attach an Elastic IP or give the server a DNS name.
Point a DNS record at the instance first. Then run openfga-set-domain with the name, plus any other names or IPv4 addresses the server should answer to. The command renders nginx for those names, issues a new certificate for them on the instance and updates the credentials file:
sudo openfga-set-domain openfga.example.com
sudo grep -E '^OPENFGA_(API_URL|HOST|TLS_SHA256)=' /root/openfga-credentials.txt
sudo cat /etc/ssl/openfga/openfga.crt > ~/openfga.crtIf you keep the address-based name without an Elastic IP, run sudo openfga-set-domain <new-public-ip> after each stop and start.
To use a certificate from your own CA instead, replace /etc/ssl/openfga/openfga.crt (the full chain) and /etc/ssl/openfga/openfga.key (mode 0600, root). Then check and reload nginx with sudo nginx -t && sudo systemctl reload nginx. Run openfga-set-domain first so that nginx answers to the name on the certificate.
Operate and harden
Rotate the key. OpenFGA accepts every key in the keys list under authn.preshared in /etc/openfga/config.yaml, so you can rotate without downtime.
Add a new 64-character key (for example from openssl rand -hex 32) next to the old one with sudoedit /etc/openfga/config.yaml, then run sudo systemctl restart openfga. Move your applications to the new key. Then remove the old key, restart OpenFGA again and put the new key in /root/openfga-credentials.txt.
Metadata access. OpenFGA, nginx, PostgreSQL and the first-boot services cannot reach the instance metadata service. IPv4, IPv6 and the IPv4-mapped and translated forms are all denied, and the instance requires IMDSv2 tokens. Check the deny lists:
for u in openfga nginx postgresql@16-main openfga-firstboot openfga-seed; do echo "$u: $(systemctl show -p IPAddressDeny --value "$u" | tr ' ' '\n' | sort | tr '\n' ' ')" doneopenfga: 168.63.129.16/32 169.254.169.254/32 2001::/32 2002::/16 64:ff9b:1::/48 64:ff9b::/96 ::ffff:0.0.0.0/96 fd00:ec2::254/128 nginx: 168.63.129.16/32 169.254.169.254/32 2001::/32 2002::/16 64:ff9b:1::/48 64:ff9b::/96 ::ffff:0.0.0.0/96 fd00:ec2::254/128 postgresql@16-main: 168.63.129.16/32 169.254.169.254/32 2001::/32 2002::/16 64:ff9b:1::/48 64:ff9b::/96 ::ffff:0.0.0.0/96 fd00:ec2::254/128 openfga-firstboot: 168.63.129.16/32 169.254.169.254/32 2001::/32 2002::/16 64:ff9b:1::/48 64:ff9b::/96 ::ffff:0.0.0.0/96 fd00:ec2::254/128 openfga-seed: 168.63.129.16/32 169.254.169.254/32 2001::/32 2002::/16 64:ff9b:1::/48 64:ff9b::/96 ::ffff:0.0.0.0/96 fd00:ec2::254/128OpenFGA itself makes no outbound calls to user-supplied URLs.
Metrics. Prometheus metrics are on 127.0.0.1:2112 only. Scrape them from the instance (for example with the CloudWatch agent or a Prometheus agent), or reach them through an SSH tunnel such as ssh -L 2112:127.0.0.1:2112. To list a few metric names:
curl -s http://127.0.0.1:2112/metrics | grep -oE '^openfga_[a-z_]+' | sort -u | head -n 6openfga_cache_item_count openfga_cachecontroller_cache_hit_count openfga_cachecontroller_cache_invalidation_count openfga_cachecontroller_cache_total_count openfga_check_cache_hit_count openfga_check_cache_invalid_hit_countgRPC. gRPC is on 127.0.0.1:8081 for clients on the instance. To use gRPC from elsewhere, tunnel it over SSH (ssh -L 8081:127.0.0.1:8081) and send the same authorization: Bearer <key> metadata.
Backups. Back up the OpenFGA database with pg_dump and copy the file off the instance:
sudo -u postgres pg_dump -Fc openfga > ~/openfga-$(date +%F).dump ls -l ~/openfga-*.dumpThe database holds your stores, models and tuples. It does not hold the API key, so keep the key in your secrets manager too.
Logs. Read the logs with sudo journalctl -u openfga -u nginx -u openfga-firstboot. nginx logs requests without query strings and logs errors only at crit. OpenFGA never logs the key.
Security group. Allow TCP 443 only from the hosts whose applications ask OpenFGA for decisions, and TCP 22 only from your administrators.
Updates. Unattended security upgrades stay on for Ubuntu, nginx and PostgreSQL. OpenFGA is pinned to the release binary this image was built with, and new OpenFGA releases arrive as new versions of this listing.
To move to a new version, back up the database and launch the new version. Then restore the dump into its database with sudo -u postgres pg_restore --clean --if-exists -d openfga <file>.
Support and licensing
OpenFGA is a Cloud Native Computing Foundation project distributed under the Apache License 2.0. The license text is in /usr/share/doc/sac-openfga/. To confirm the version and the license:
openfga version 2>&1 | grep -o 'OpenFGA version.*' grep -m 2 -E 'Apache License|Version 2.0' /usr/share/doc/sac-openfga/LICENSE-openfga-Apache-2.0.txt | sed 's/^ *//'OpenFGA version `v1.22.1` build from `44054f542f64e9b9589dd26a595081b3d2cfd942` on `2026-10-28T12:53:36Z` Apache License Version 2.0, January 2004SecureAnyCloud provides the packaging, the first-boot automation, the HTTPS front end and the hardening, with 24/7 support by email and live chat at sales@secureanycloud.com.
All product names are trademarks of their respective owners. SecureAnyCloud is not affiliated with or endorsed by the OpenFGA project or the Cloud Native Computing Foundation.
Deploy on AWS
Launch OpenFGA 1.22.1 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com