Authgear 2026-10-23.0 on AWS User Guide
Overview
This guide covers launching and using the Authgear 2026-10-23.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg identity, secrets and PKI listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
Authgear is an open source authentication server and identity provider. Your web and mobile applications send their users to Authgear to sign in with OpenID Connect / OAuth 2 and get standard tokens back. Authgear supports passwords, passwordless one-time codes, passkeys, biometric login, social and enterprise single sign-on and multi-factor authentication, and comes with an admin portal for users, applications, login methods, branding and security policy, plus a GraphQL Admin API.
The cloudimg image is a hardened, ready-to-use Authgear server:
- Two projects, created on your instance.
projectis your default project: its sign-in pages and OIDC endpoints are what your applications use, athttps://<your instance name>/.accountsis the project the admin portal itself signs its administrators in with. Every signing key, Admin API key, session and CSRF secret of both projects is generated on your instance's first boot. - nginx in front, everything else on the loopback address. nginx is the only public listener (ports 80 and 443). The Authgear server and portal run as unmodified upstream containers whose ports are published on
127.0.0.1only. - The admin portal is not on the internet. The portal (
https://localhost:8443) and its sign-in project (https://localhost:8444) are served on the loopback address only; you reach them through an SSH tunnel. - HTTPS from the first boot. nginx serves a certificate generated on your instance (an end-entity certificate, not a certificate authority) and port 80 redirects to HTTPS. The project's public address is set from the instance's EC2 public DNS name at first boot and can be changed to your own domain.
- No default login, no open sign-up. First boot creates ONE administrator,
admin@authgear.local, with a random password unique to your instance, before anything is reachable. Public signup is off on both projects, so nobody can create the first account or any other; you add users yourself. - Real client addresses, fixed host name. nginx hands Authgear the real TCP peer and the configured name, never a client's own
X-Forwarded-FororHost, so sessions, audit logs and rate limits record the real address and the OIDC issuer cannot be changed by a request. - Databases on sockets only. PostgreSQL 16 listens only on its Unix socket with peer authentication: there is no database password at all, and the cluster is created on its own data volume at first boot. Valkey (the Redis-compatible session store) listens only on a Unix socket, keeps everything in memory only, and requires a per-instance password.
- Locked-down containers. The Authgear containers run as an unprivileged account with a read-only file system, no Linux capabilities and no new privileges, and are pinned by image digest (nothing is downloaded at boot).
- Metadata access blocked. Authgear calls URLs you configure (webhooks, social login providers). Every service and both containers are blocked from the instance metadata service, and the AMI requires IMDSv2.
- Fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on (including Docker Engine).
What is on the image: Authgear 2026-10-23.0 (the upstream authgear-server and authgear-portal release images), Docker Engine, nginx, PostgreSQL 16 with pg_partman, Valkey 7.2, 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 (Authgear needs outbound HTTPS for social login providers, webhooks and mail or SMS providers you add).
- A security group that allows TCP 22 (SSH, also used for the admin portal tunnel) from your own networks only, and TCP 443 (sign-in pages and OIDC endpoints) and TCP 80 (redirects to HTTPS) from your users. Never open 8443, 8444, 3000-3003, 5432 or 6379: they listen on the loopback address only.
- For production, a DNS name you control (for example
auth.example.com) and a certificate from a certificate authority for it.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Authgear 2026-10-23.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair, and a security group as above.
With the AWS CLI (replace the values in angle brackets):
aws ec2 run-instances --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=authgear}]'
The image has two disks: the 8 GiB system disk and a 20 GiB gp3 data volume mounted at /var/lib/postgresql, which holds the PostgreSQL cluster (users, credentials, OAuth grants, the audit log and the project configuration). Grow it at launch if you expect many users. First boot takes about half a minute after the instance is running. If you want your own name from the start, put it in /etc/cloudimg/authgear-domain with user data (for example a cloud-init write_files entry); otherwise first boot uses the instance's public DNS name, or its public IP address if it has none.
Step 2: Connect to your instance
| Operating system | SSH user |
|---|---|
| Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<public-ip>
Step 3: Check the services
systemctl is-active authgear-firstboot docker postgresql@16-main valkey-server authgear-server authgear-portal nginx
active
active
active
active
active
active
active
Only SSH, HTTP and HTTPS are public. The Authgear server (main 3000, Admin API 3002), the portal (3003) and the portal's tunnel listeners (8443, 8444) are on the loopback address; PostgreSQL and Valkey have no TCP listener at all:
sudo ss -tlnp | grep -E ':(22|80|443|3000|3002|3003|5432|6379|8443|8444) ' | awk '{print $4}' | sort -u
0.0.0.0:22
0.0.0.0:443
0.0.0.0:80
127.0.0.1:3000
127.0.0.1:3002
127.0.0.1:3003
127.0.0.1:8443
127.0.0.1:8444
[::]:22
[::]:443
[::]:80
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:3000/healthz
200
Step 4: Get your sign-in and the certificate fingerprint
sudo cat /root/authgear-credentials.txt
The file (root only, mode 0600) gives the administrator (the password is shown here as <redacted>), the project's public address, its OIDC discovery URL and the SHA-256 fingerprint of the instance's certificate, followed by a short how-to:
# Authgear 2026-10-23.0 - written on first boot by authgear-firstboot.service. Unique to this instance.
AUTHGEAR_ADMIN_EMAIL=admin@authgear.local
AUTHGEAR_ADMIN_PASSWORD=<redacted>
AUTHGEAR_PORTAL_VIA_SSH_TUNNEL=https://localhost:8443/
AUTHGEAR_PROJECT_ID=project
AUTHGEAR_PROJECT_URL=https://ec2-52-23-159-80.compute-1.amazonaws.com/
AUTHGEAR_OIDC_DISCOVERY=https://ec2-52-23-159-80.compute-1.amazonaws.com/.well-known/openid-configuration
AUTHGEAR_TLS_SHA256=D7:B5:37:F3:18:96:79:05:E1:66:2D:0F:5F:F7:53:CF:83:45:75:0D:44:CC:23:57:06:26:62:9A:5D:55:65:7F
The name, every address the instance answers to and the certificate fingerprint are also shown at login and by:
authgear-set-hostname --show
name: ec2-52-23-159-80.compute-1.amazonaws.com
project URL: https://ec2-52-23-159-80.compute-1.amazonaws.com/
OIDC: https://ec2-52-23-159-80.compute-1.amazonaws.com/.well-known/openid-configuration
portal: https://localhost:8443/ (SSH tunnel)
also served: ec2-52-23-159-80.compute-1.amazonaws.com 52.23.159.80 172.31.80.45
certificate: SHA-256 D7:B5:37:F3:18:96:79:05:E1:66:2D:0F:5F:F7:53:CF:83:45:75:0D:44:CC:23:57:06:26:62:9A:5D:55:65:7F
The certificate is self-signed and unique to your instance, so your browser warns about it. Open the certificate details in the browser, check that its SHA-256 fingerprint matches AUTHGEAR_TLS_SHA256, and only then continue. Do not add the certificate to your operating system's trust store; for production, use your own domain and a certificate from a certificate authority (Step 10).
Step 5: Open the admin portal through an SSH tunnel
From your computer, forward the two loopback listeners:
ssh -i <your-key.pem> -L 8443:127.0.0.1:8443 -L 8444:127.0.0.1:8444 ubuntu@<public-ip>
and browse to https://localhost:8443/. The portal sends you to its own sign-in project at https://localhost:8444. There is no sign-up link: the only portal account is the one first boot created.

Sign in with AUTHGEAR_ADMIN_EMAIL and AUTHGEAR_ADMIN_PASSWORD. The portal lists the two projects: accounts (the portal's own sign-in project, at https://localhost:8444) and project, your default project at the instance's name:

Open project:

Leave the accounts project as it is: it controls who can administer the portal.
Step 6: Register your application
Under Applications > Client Applications add an application (the Start Integration button on Getting Started opens the same form), give it a name and pick your application's type (single-page app, server-side web app, mobile app, or Other OIDC/SAML for any standard OpenID Connect client). Then set the application's redirect URIs to the addresses in your application that Authgear returns users to.

Your application uses the project's discovery document, which names the instance's HTTPS address as the issuer:
D=$(cat /etc/cloudimg/authgear-domain)
curl -s --cacert /etc/ssl/authgear/authgear.crt "https://$D/.well-known/openid-configuration" | python3 -c 'import json,sys; d=json.load(sys.stdin); [print(k+":", d[k]) for k in ("issuer","authorization_endpoint","token_endpoint","userinfo_endpoint","jwks_uri")]'
issuer: https://ec2-52-23-159-80.compute-1.amazonaws.com
authorization_endpoint: https://ec2-52-23-159-80.compute-1.amazonaws.com/oauth2/authorize
token_endpoint: https://ec2-52-23-159-80.compute-1.amazonaws.com/oauth2/token
userinfo_endpoint: https://ec2-52-23-159-80.compute-1.amazonaws.com/oauth2/userinfo
jwks_uri: https://ec2-52-23-159-80.compute-1.amazonaws.com/oauth2/jwks
Most OIDC libraries need only the issuer, the client ID from the portal and your redirect URI. Use your own domain (Step 10) before you register production redirect URIs: the issuer changes with the name.
Step 7: Add users
Because public signup is off, you create the accounts. In the portal use User Management > Users > Create User, or script it with the Admin API. The image's authgear-admin-api helper signs each call with the project's own Admin API key; the query and the variables are files, so a password never appears on a command line. This creates jane@example.com with a random password, which it keeps in a root-only file for you to hand over:
T=$(sudo mktemp -d /run/ag-guide.XXXXXX)
printf '%s\n' 'mutation ($email: String!, $password: String!) { createUser(input: { definition: { loginID: { key: "email", value: $email } }, password: $password }) { user { id standardAttributes } } }' | sudo tee $T/q.graphql >/dev/null
sudo python3 -I -c 'import json,secrets,os; pw="Jn-"+secrets.token_urlsafe(18); os.umask(0o077); open("/root/jane-password.txt","w").write(pw+"\n"); print(json.dumps({"email":"jane@example.com","password":pw}))' | sudo tee $T/v.json >/dev/null
sudo authgear-admin-api project $T/q.graphql $T/v.json | python3 -c 'import json,sys; u=json.load(sys.stdin)["data"]["createUser"]["user"]; print("created", u["standardAttributes"]["email"])'
sudo find $T -type f -exec shred -u {} +; sudo rm -rf $T
created jane@example.com
List the project's users the same way:
T=$(sudo mktemp -d /run/ag-guide.XXXXXX)
printf '%s\n' 'query { users(first: 10) { totalCount edges { node { standardAttributes } } } }' | sudo tee $T/q.graphql >/dev/null
sudo authgear-admin-api project $T/q.graphql | python3 -c 'import json,sys; d=json.load(sys.stdin)["data"]["users"]; print("users:", d["totalCount"]); [print(" ", e["node"]["standardAttributes"].get("email")) for e in d["edges"]]'
sudo rm -rf $T
users: 1
jane@example.com

Choose how users sign in (email, phone, username, passkeys, social and enterprise providers, 2FA) under Authentication > Login Methods:

No mail server or SMS gateway is configured, so email verification, one-time codes and password resets by email or SMS do not work until you add your own provider under Advanced > Custom Email Provider and Custom SMS Gateway.
Step 8: What your users see
Your users sign in at the project's public address (your application normally sends them there through the OIDC authorize endpoint):

An address that has no account is refused; the visitor is never taken to a page where they could set a password and create one:

Step 9: Check the server from the command line
One command proves the whole chain on the instance: the discovery document and signing keys over HTTPS, a signed Admin API call (and an unsigned one refused), the portal and its sign-in project on the tunnel listeners, public signup refused on both projects, the administrator's password accepted and a wrong one refused, Valkey's password, and PostgreSQL's socket-only, passwordless set-up:
sudo /usr/local/sbin/authgear-cred-roundtrip.sh
AUTHGEAR_ROUNDTRIP_OK discovery=200 jwks=RSA adminapi=signed-ok,unsigned-refused portal=200 accounts=200 signup=refused(project,accounts) admin-login=finished wrong-password=InvalidCredentials auth=PONG anonymous=NOAUTH wrong=WRONGPASS config=NOPERM tcp=none pg=socket-only,no-passwords
Step 10: Use your own domain and certificate
Point a DNS name at the instance (an Elastic IP is a good idea), then:
sudo authgear-set-hostname auth.example.com
The default project's public address becomes https://auth.example.com (also its OIDC issuer), nginx answers to that name (the instance's addresses redirect to it) and the self-signed certificate is re-issued for it. Update your applications' issuer and redirect URIs. To serve a certificate from a certificate authority, replace /etc/ssl/authgear/authgear.crt (full chain) and /etc/ssl/authgear/authgear.key (mode 0600, root) and run sudo systemctl reload nginx; authgear-set-hostname never overwrites a certificate that is not its own. Keep localhost in the certificate's names, or keep using the self-signed one for the 8443/8444 tunnel listeners.
Step 11: Public signup
Public signup is off on both projects. The project's configuration lives in PostgreSQL; authgear-config exports it to a root-only directory on /run (memory) and imports it back. Check the setting:
T=$(sudo mktemp -d /run/ag-cfg.XXXXXX)
sudo authgear-config export project $T 2>/dev/null
sudo grep -A1 '^authentication:' $T/authgear.yaml
sudo find $T -type f -exec shred -u {} +; sudo rm -rf $T
authentication:
public_signup_disabled: true
To let anyone who can reach the project create their own account, export the configuration as above, change public_signup_disabled to false in $T/authgear.yaml, run sudo authgear-config import $T, then shred the directory. The server picks the change up within a few seconds. Only do this for the project project; never for accounts, which would let anyone sign up to your admin portal. With signup on, consider bot protection and a mail provider for email verification first.
Step 12: Check the security posture
The certificate is an end-entity certificate for the instance's name and addresses (plus localhost for the tunnel), which cannot sign other certificates:
openssl x509 -in /etc/ssl/authgear/authgear.crt -noout -subject -ext basicConstraints,keyUsage,extendedKeyUsage,subjectAltName
subject=CN = ec2-52-23-159-80.compute-1.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name:
DNS:ec2-52-23-159-80.compute-1.amazonaws.com, IP Address:52.23.159.80, IP Address:172.31.80.45, DNS:localhost, IP Address:127.0.0.1
X509v3 Basic Constraints: critical
CA:FALSE
X509v3 Key Usage: critical
Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Server Authentication
TLS 1.0 and 1.1 are refused, and a request for a host name the instance does not serve gets no answer:
echo | openssl s_client -tls1_1 -cipher 'DEFAULT@SECLEVEL=0' -connect 127.0.0.1:443 2>&1 | grep -o 'alert protocol version' | head -1
code=$(curl -sk -o /dev/null -w '%{http_code}' -H 'Host: evil.example' "https://$(hostname -I | awk '{print $1}')/login" || true); echo "$code"; [ "$code" = 000 ]
alert protocol version
000
nginx hands Authgear the configured name and the real client address, never values from the request:
grep -E 'proxy_set_header +(Host|X-Forwarded-For|X-Real-IP|X-Forwarded-Host) ' /etc/nginx/cloudimg/authgear-proxy-project.conf
proxy_set_header Host ec2-52-23-159-80.compute-1.amazonaws.com;
proxy_set_header X-Forwarded-Host ec2-52-23-159-80.compute-1.amazonaws.com;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
The containers publish their ports on the loopback address only:
sudo docker ps --format '{{.Names}} {{.Ports}}'
authgear-server 127.0.0.1:3000->3000/tcp, 3001/tcp, 127.0.0.1:3002->3002/tcp, 13000-13002/tcp
authgear-portal 127.0.0.1:3003->3003/tcp, 13003/tcp
Metadata access is blocked for the host services (a systemd rule) and for the containers (a firewall rule in Docker's DOCKER-USER chain); a connection from inside each container never gets through:
systemctl show -p IPAddressDeny nginx authgear-firstboot valkey-server | sort -u | grep .
sudo iptables -S DOCKER-USER | grep DROP
for c in authgear-server authgear-portal; do sudo docker exec $c timeout 8 bash -c 'exec 3<>/dev/tcp/169.254.169.254/80' 2>/dev/null && echo "$c: metadata REACHED" || echo "$c: metadata blocked"; done
IPAddressDeny=168.63.129.16/32 169.254.169.254/32 ::ffff:169.254.169.254/128 fd00:ec2::254/128
-A DOCKER-USER -d 168.63.129.16/32 -j DROP
-A DOCKER-USER -d 169.254.169.254/32 -j DROP
authgear-server: metadata blocked
authgear-portal: metadata blocked
Authgear's own SSRF protection also refuses webhooks to private and link-local addresses; the image's build validation proved that even with that protection lifted, a webhook to the metadata service is dropped by the network while one to an ordinary receiver is delivered.
PostgreSQL has no TCP listener and no role has a password; Valkey refuses anonymous and wrong-password access and every administrative command:
sudo -u postgres psql -X -qAt -c "SHOW listen_addresses;" -c "SELECT count(*) FROM pg_authid WHERE rolpassword IS NOT NULL;" | sed 's/^$/(none)/'
sudo authgear-valkey-password check
(none)
0
VALKEY_PASSWORD_OK auth=PONG anonymous=NOAUTH wrong=WRONGPASS config=NOPERM tcp=none
Core dumps are off for every service and SSH accepts keys only:
systemctl show -p LimitCORE --value nginx authgear-server authgear-portal valkey-server docker | tr '\n' ' '; echo
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|x11forwarding) '
0 0 0 0 0
permitrootlogin no
passwordauthentication no
x11forwarding no
Step 13: Passwords and secrets
- The administrator password: the portal administrator is a user of the
accountsproject. Set a new password with the Admin API'sresetPasswordmutation on that project, with the query and the variables in files exactly as in Step 7:
graphql
mutation ($id: ID!, $pw: String!) { resetPassword(input: { userID: $id, password: $pw }) { user { id } } }
run as sudo authgear-admin-api accounts <query-file> <variables-file> (find the user's id with a users(searchKeyword: "admin@authgear.local") query). Then update /root/authgear-credentials.txt yourself, or delete it.
* The Valkey password (Authgear's session store) can be rotated at any time; Valkey keeps sessions in memory only, so every signed-in user is signed out:
bash
sudo authgear-valkey-password rotate
- Signing keys: the projects' OIDC signing keys, Admin API keys and session secrets were generated on this instance and are stored in PostgreSQL; none of them exists on any other instance.
Step 14: Your data and backups
Everything Authgear stores (users, credentials, sessions of record, OAuth grants, the audit log and both projects' configuration and keys) is in the PostgreSQL database authgear on the data volume. Back it up with pg_dump over the local socket (no password is needed or exists):
sudo -u postgres pg_dump -Fc authgear | sudo tee /root/authgear-$(date +%F).dump >/dev/null
sudo chmod 600 /root/authgear-$(date +%F).dump; sudo ls -l /root/authgear-$(date +%F).dump | awk '{print $1, $3}'
-rw------- root
Treat the dump like a password store: it holds password hashes and the projects' private signing keys. Valkey holds only short-lived data (sessions, one-time codes, rate-limit counters) in memory and needs no backup. The audit log is partitioned by month; a daily authgear-audit-maintain timer keeps 180 days of audit log entries (90 days of audit metrics) and removes older partitions.
Step 15: Licences and source
Authgear is licensed under the Apache License 2.0. The image carries the licence and a source record that names the release tag, the image digests and the sha256 of each binary:
ls /usr/share/doc/cloudimg-authgear/
sudo docker image ls --digests --format '{{.Repository}} {{.Digest}}'
LICENSE-authgear-Apache-2.0.txt
SOURCE.txt
quay.io/theauthgear/authgear-server sha256:03266e1425c7b56c9d455e1c20df5473ee04d1bfc7bc47f5873b05d9ae131399
quay.io/theauthgear/authgear-portal sha256:141ef347c83bebf8335b834eb7336cab3962764de1448c5841d88e403be4b667
- Source: github.com/authgear/authgear-server at tag 2026-10-23.0 (commit
dd7188dc6334). The server and the admin portal are built from that one repository. cloudimg ships the unmodified upstream release images for that tag (quay.io/theauthgear/authgear-serverandauthgear-portal), pinned by their linux/amd64 manifest digests; 2026-10-23.0 is the 2026-10-22.0 release rebuilt by upstream on Go 1.26.10. - PostgreSQL, pg_partman, Valkey and nginx are Ubuntu 24.04 packages; Docker Engine comes from Docker's own repository.
Step 16: Security notes
- Keep port 22 open to your own networks only. Never open the loopback ports in a security group; the portal is meant to be reached through SSH.
- Public signup stays off unless you turn it on for
project(Step 11). Never turn it on foraccounts. - Use your own domain and a certificate from a certificate authority in production, and register your applications' redirect URIs against that name.
- No mail or SMS provider is configured: password resets and verification messages need one.
- Known residual advisories (reviewed for this release, none reachable from the network):
github.com/go-jose/go-jose/v44.1.4 in the server image (CVE-2026-116587, fixed in 4.1.6 after this release): linked only through the Google Cloud Storage client's SPIFFE/xDS code, which parses operator-supplied bundles, never a remote party's; Authgear's own token and JWK handling uses a different library.- Valkey 7.2.13 from Ubuntu universe (USN-8771-1: CVE-2026-56684, CVE-2026-63639, fixed only in Ubuntu Pro ESM): the first needs Valkey's TLS listener (off), the second the
RESTOREcommand (refused for Authgear's account); Valkey listens on a Unix socket only. follow-redirects1.16.0 (HIGH) andip-address10.7.1 (MEDIUM) appear in the web front ends' package lists: the first is the Node-only HTTP adapter of a library and is not in the browser bundles, the second is used only in the admin portal's IP-blocklist form, in the administrator's own browser.libbrotli1andzlib1ginside the server image (two MEDIUM Ubuntu advisories): used only by the image-processing service, which this appliance does not run.- govulncheck reports no reachable advisory in either Go binary.
Support
cloudimg provides 24/7 technical support for this image by email and live chat: support@cloudimg.co.uk. Authgear is a trademark of its respective owner; cloudimg is not affiliated with or endorsed by it.