Security AWS

Rauthy 0.37.2 on AWS User Guide

| Product: Rauthy 0.37.2 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Rauthy 0.37.2 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.

Rauthy is an open source OpenID Connect (OIDC) provider and single sign-on server. Your applications send their users to Rauthy to sign in, with a password, a passkey or both, and Rauthy hands them back an ID token and an access token signed with this instance's keys. It manages clients, users, groups, roles and scopes, can federate sign-in to an upstream identity provider, and has an admin console and a self-service account page built in.

The cloudimg image is a hardened, ready-to-use Rauthy server:

  • nginx in front, Rauthy behind it. nginx is the only public listener (ports 80 and 443). Rauthy listens on 127.0.0.1:8080, loopback only, and its embedded database, Hiqlite (SQLite replicated with Raft), on 127.0.0.1:8100 and 127.0.0.1:8200, loopback only.
  • HTTPS from the first boot. First boot names the instance after its EC2 public DNS name and serves HTTPS with a certificate generated on your instance for that name (plus the public and private addresses). The OIDC issuer is https://<that name>/auth/v1/. Port 80 only redirects to HTTPS and serves the Let's Encrypt challenge path. One command moves Rauthy to your own domain and certificate.
  • Your instance's own secrets. The image has no configuration secret, no database, no admin and no key. First boot generates this instance's Hiqlite Raft and API secrets and the AES-256 key that encrypts every client secret, MFA secret and signing key in the database; Rauthy's first start then creates the database and this instance's token signing keys (JWKS). The one admin account gets a random password, which Rauthy stores only as an Argon2id hash.
  • The admin console requires a passkey. The admin signs in with the password, registers a passkey on the account page, and from then on the admin console asks for the password and the passkey (Rauthy's admin_force_mfa, on). Open self-registration is off: nobody can create an account without the admin.
  • The real client address, everywhere. Rauthy trusts only the local nginx as a proxy (proxy_mode, trusted_proxies = ['127.0.0.1/32']), and nginx sends it the address of the TCP connection, never a header a client could forge. Rauthy's failed-login counter, its sessions and its logs all see the real client.
  • Sign-in limits per client. nginx limits each client address on the sign-in route (30 a minute, bursts of 30), the token endpoint (10 a second, bursts of 50) and everything else (20 a second, bursts of 100); the web interface's own files are never limited.
  • Server-side connections can't reach inside. Rauthy connects out by itself when an admin adds an upstream identity provider (its discovery document and JWKS), for e-mail and for webhooks. Below the application, a firewall table refuses the instance metadata service and every private, link-local, shared and reserved address to the Rauthy account, including this VPC's own ranges, unless you open a host.
  • Browser hardening. Content-Security-Policy with frame-ancestors 'none', X-Frame-Options: DENY, Strict-Transport-Security, X-Content-Type-Options, Referrer-Policy and Permissions-Policy. The access log records paths only, never a query string, so an authorization code never reaches a log.
  • Locked down. TLS 1.2 and 1.3 only; the AMI requires IMDSv2, and Rauthy and nginx are blocked from the instance metadata service. Rauthy runs as its own account with NoNewPrivileges, core dumps off (LimitCORE=0) and info-level logging. No mail server is installed and nothing listens on port 25.
  • Pinned and verified, on a fully patched Ubuntu 24.04 LTS at build time, with unattended security upgrades switched on. See Step 13 for exactly what is installed.

What is on the image: Rauthy 0.37.2 (upstream's own release build: the single binary from the official ghcr.io/sebadob/rauthy:0.37.2 image, pinned by digest, in /usr/local/bin/rauthy; no container runtime is installed), nginx and nftables from Ubuntu 24.04, cloud-init and the AWS Systems Manager agent.

Settings that differ from Rauthy's defaults: Rauthy listens on 127.0.0.1 instead of 0.0.0.0; proxy_mode is on and trusts only 127.0.0.1/32; Hiqlite's API and Raft ports listen on 127.0.0.1 instead of 0.0.0.0; Rauthy's check for new versions (a call to GitHub) is off. Settings pinned at Rauthy's own defaults: admin_force_mfa = true, open registration off, ATProto sign-in off, the Prometheus metrics listener off and the Swagger UI off.

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 that gives the instance a public IPv4 address (or reach it over your VPN or Direct Connect).
  • A security group that allows TCP 22 (SSH) from your own networks only, and TCP 443 (Rauthy) and TCP 80 (redirects to HTTPS) from the networks whose users and applications sign in with Rauthy. To get a certificate from Let's Encrypt, port 80 must be reachable from the internet while it runs.
  • A browser with a passkey: a phone, a security key, or the passkey store of your computer or password manager.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Rauthy 0.37.2 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> \
  --associate-public-ip-address \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=rauthy}]'

First boot takes about a minute after the instance is running: it generates the secrets and the certificate, starts Rauthy on its empty database (which creates the signing keys and the admin account), proves the admin sign-in on the loopback address and only then opens the web port.

Step 2: Connect to your instance

Operating system SSH user
Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

The login message shows the address of Rauthy and the commands this guide uses.

Step 3: Check the services

systemctl is-active rauthy nginx cloudimg-rauthy-metadata
sudo ss -tln | grep -E ':(22|25|80|443|8080|8100|8200) '
sudo rauthy-selftest

All three services report active. nginx is the only service on ports 80 and 443; Rauthy (8080) and its Hiqlite database (8100 and 8200) listen on 127.0.0.1 only, and nothing listens on port 25:

active
active
active
LISTEN 0      1024       127.0.0.1:8100      0.0.0.0:*
LISTEN 0      1024       127.0.0.1:8080      0.0.0.0:*
LISTEN 0      511          0.0.0.0:443       0.0.0.0:*
LISTEN 0      511          0.0.0.0:80        0.0.0.0:*
LISTEN 0      4096         0.0.0.0:22        0.0.0.0:*
LISTEN 0      1024       127.0.0.1:8200      0.0.0.0:*
LISTEN 0      511             [::]:443          [::]:*
LISTEN 0      511             [::]:80           [::]:*
LISTEN 0      4096            [::]:22           [::]:*
rauthy-selftest: issuer https://ec2-203-0-113-10.compute-1.amazonaws.com/auth/v1/, 4 JWKS keys; wrong password 401; issuer=https://ec2-203-0-113-10.compute-1.amazonaws.com/auth/v1/ alg=EdDSA kid=Xk3pQ9vT2mLs8RwY1cJd4HnB sig=verified(tampered refused) aud=rauthy nonce=ok userinfo=200 code_replay=401 admin_api=406/MfaRequired; stranger registration 403; 4 admin endpoints 401 anonymous
RAUTHY_SELFTEST_OK

rauthy-selftest signs in with a throwaway client and user over the public HTTPS name, checks the ID token's signature against the JWKS, checks that a tampered token, a replayed code and a wrong password are refused, that the admin API requires MFA and that a stranger cannot register, then deletes the client and the user again.

Step 4: Read the credentials file and the certificate fingerprint

sudo cat /root/rauthy-credentials.txt

The file (root only, mode 0600) holds this instance's addresses, the SHA-256 fingerprint of its certificate, the admin's sign-in name and password:

# Rauthy 0.37.2 on this instance (cloudimg). Sign in at RAUTHY_ACCOUNT_URL as RAUTHY_ADMIN_EMAIL with
# RAUTHY_ADMIN_PASSWORD, then register a passkey (MFA): the admin console (RAUTHY_ADMIN_URL) requires one.
# The certificate is self-signed for this instance: compare its SHA-256 fingerprint with RAUTHY_TLS_SHA256.
# Change the password and the e-mail address in the account page after the first sign-in.
RAUTHY_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com/auth/v1/
RAUTHY_ACCOUNT_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com/auth/v1/account
RAUTHY_ADMIN_URL=https://ec2-203-0-113-10.compute-1.amazonaws.com/auth/v1/admin
RAUTHY_ISSUER=https://ec2-203-0-113-10.compute-1.amazonaws.com/auth/v1/
RAUTHY_HOST=ec2-203-0-113-10.compute-1.amazonaws.com
RAUTHY_PASSKEY_RP_ID=ec2-203-0-113-10.compute-1.amazonaws.com
RAUTHY_ADMIN_EMAIL=admin@rauthy.invalid
RAUTHY_TLS_SHA256=4F:2A:91:...:7D:0C (32 hex pairs)
RAUTHY_NAME_SOURCE=public-dns-name
RAUTHY_ADDRESS=203.0.113.10
RAUTHY_ADDRESS_SOURCE=metadata-public-address
GUIDE=https://www.cloudimg.co.uk/guides/rauthy-0-37-2-on-ubuntu-24-04-aws/
RAUTHY_ADMIN_PASSWORD=<a random 28-character password>

The certificate is self-signed for this instance. Before you type the password into a browser, check that the fingerprint your browser shows (click the padlock or the warning, then the certificate details) is the RAUTHY_TLS_SHA256 value from this file. To check from your own computer instead:

openssl s_client -connect <public-ip>:443 -servername <public-dns-name> </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256

The password in this file is the one first boot set. Once you change it in the account page (Step 5), the file no longer holds the current password; Rauthy keeps only its hash.

Step 5: Sign in and register your passkey

Open RAUTHY_ACCOUNT_URL (https://<public-dns-name>/auth/v1/account) and accept the certificate once you have checked its fingerprint. Rauthy shows its sign-in page; sign in with admin@rauthy.invalid and RAUTHY_ADMIN_PASSWORD. A wrong password is refused, and Rauthy counts failed sign-ins per client address.

The Rauthy 0.37.2 sign-in page refusing a wrong password

You land on your account page. Open MFA and select Register New Passkey; Rauthy asks for your current password once more, then for a name for the passkey. Select Register: your browser asks your authenticator (phone, security key or the passkey store of your computer) to create a passkey for this site. While you are on the account page, change the password, and the e-mail address to one you read.

The account page with the admin's passkey registered

Passkeys are bound to the name in RAUTHY_PASSKEY_RP_ID. If you move Rauthy to your own domain later (Step 8), register the passkey again after the move; do that before you rely on it.

Step 6: Open the admin console

Open RAUTHY_ADMIN_URL (https://<public-dns-name>/auth/v1/admin). Rauthy asks for the password, then for your passkey, and opens the admin console. A fresh instance has the admin account, the built-in rauthy client (the console and account page themselves) and Rauthy's default groups, roles and scopes:

The admin console: the clients list

Without a passkey on the account, the admin console stays closed: the admin API answers MfaRequired.

Step 7: Connect your first application

In the admin console, open Clients and add a client for your application: its client ID, its redirect URI (for example https://app.example.com/oauth2/callback), and whether it is confidential (a server-side application, which gets a client secret) or public (a browser or mobile application, which must use PKCE). Then add users under Users, or let your users sign in through an upstream provider under Providers.

Your application needs only the issuer. Its discovery document lists every endpoint and the JWKS that verifies Rauthy's tokens:

H="$(sudo sed -n 's/^RAUTHY_HOST=//p' /root/rauthy-credentials.txt)"
sudo curl -s --cacert /etc/ssl/rauthy/rauthy.crt "https://$H/auth/v1/.well-known/openid-configuration" \
  | jq '{issuer, authorization_endpoint, token_endpoint, userinfo_endpoint, jwks_uri, id_token_signing_alg_values_supported}'
sudo curl -s --cacert /etc/ssl/rauthy/rauthy.crt "https://$H/auth/v1/oidc/certs" | jq -c '.keys[] | {kid, alg, kty}'
{
  "issuer": "https://ec2-203-0-113-10.compute-1.amazonaws.com/auth/v1/",
  "authorization_endpoint": "https://ec2-203-0-113-10.compute-1.amazonaws.com/auth/v1/oidc/authorize",
  "token_endpoint": "https://ec2-203-0-113-10.compute-1.amazonaws.com/auth/v1/oidc/token",
  "userinfo_endpoint": "https://ec2-203-0-113-10.compute-1.amazonaws.com/auth/v1/oidc/userinfo",
  "jwks_uri": "https://ec2-203-0-113-10.compute-1.amazonaws.com/auth/v1/oidc/certs",
  "id_token_signing_alg_values_supported": [
    "RS256",
    "RS384",
    "RS512",
    "EdDSA",
    "Ed25519"
  ]
}
{"kid":"Qm7RtV2nWc9LpX4aKd8sHy1E","alg":"RS256","kty":"RSA"}
{"kid":"Ub5JzN0eFq3TgY6wMr2vPa9C","alg":"RS384","kty":"RSA"}
{"kid":"Dh8KsL1xBn4ViR7oTe5yWq0G","alg":"RS512","kty":"RSA"}
{"kid":"Xk3pQ9vT2mLs8RwY1cJd4HnB","alg":"EdDSA","kty":"OKP"}

(sudo only because the certificate under /etc/ssl/rauthy is readable by root; from your applications use the issuer URL with the certificate you trust.)

The signing keys are this instance's own. Rauthy rotates them monthly and keeps publishing the previous keys, so tokens already issued keep verifying.

Step 8: Your domain and a trusted certificate

  1. Create a DNS record for your name (for example an A record id.example.com to the instance's Elastic IP address).
  2. Point Rauthy at it. The first name becomes the issuer's host, the passkey relying party, the name nginx serves and the name the certificate covers; any names after it keep working too:
sudo rauthy-set-domain id.example.com "$(sudo sed -n 's/^RAUTHY_HOST=//p' /root/rauthy-credentials.txt)"
  1. Get a certificate for it. With Let's Encrypt, nginx already serves the HTTP challenge directory, so certbot's webroot mode works without stopping anything (port 80 must be reachable from the internet while it runs):
sudo apt-get install -y certbot
sudo certbot certonly --webroot -w /var/www/cloudimg-acme -d id.example.com --email you@example.com --agree-tos -n \
  --deploy-hook 'rauthy-set-domain --tls-cert /etc/letsencrypt/live/id.example.com/fullchain.pem --tls-key /etc/letsencrypt/live/id.example.com/privkey.pem id.example.com'

The deploy hook installs the certificate now and again after every automatic renewal. With a certificate from another authority, run sudo rauthy-set-domain --tls-cert <fullchain.pem> --tls-key <private-key.pem> id.example.com yourself; it checks that the key matches and that the certificate covers the name. Without --tls-cert, the instance generates a new self-signed certificate for the name.

The issuer changes with the name (https://id.example.com/auth/v1/): change it in your applications, and register your passkey again (Step 5). nginx answers only the names you configured: a request for any other host name, including one forged in the Host header, gets no answer.

Step 9: Sign-in limits and client addresses

nginx limits each client address. Past the limit a client gets HTTP 429 until its allowance refills, while other clients carry on:

grep -E '^limit_req' /etc/nginx/conf.d/cloudimg-rauthy.conf
limit_req_zone $ra_login_key zone=ra_login:10m rate=30r/m;
limit_req_zone $cloudimg_client_key zone=ra_token:10m rate=10r/s;
limit_req_zone $cloudimg_client_key zone=ra_all:10m rate=20r/s;
limit_req_zone $cloudimg_client_key zone=ra_static:10m rate=100r/s;
limit_req_status 429;

Sign-in requests (ra_login) get 30 a minute with a burst of 30, token requests 10 a second with a burst of 50, the web interface's static files 100 a second with a burst of 600, and everything else 20 a second with a burst of 100.

The limit is per IPv4 address or per IPv6 /64, not per account. Rauthy also slows down repeated failed sign-ins from one address by itself. Tune them to your users in /etc/nginx/conf.d/cloudimg-rauthy.conf (the zones) and /etc/nginx/sites-available/rauthy (the bursts), then sudo nginx -t && sudo systemctl reload nginx.

The limits, Rauthy's failed-login counter, its session records and its logs use the address of the TCP connection that reached nginx. If you put a load balancer in front of Rauthy, every client then has the load balancer's address: keep the limits per client by using a Network Load Balancer with client IP preservation, or adjust the nginx configuration to your load balancer.

Step 10: Outbound connections

Rauthy connects out when an admin adds an upstream identity provider (it fetches the provider's discovery document and keys), when it sends e-mail and for webhooks. Internet hosts work as they are. The instance metadata service, private, link-local, shared and reserved ranges, every IPv6 address except public ones, and this VPC's own ranges are refused to the Rauthy account, so an admin (or anyone who can make Rauthy fetch a URL) cannot use it to reach the inside of your network. Rauthy makes no connection at all until an admin configures one: with nothing configured, it sends nothing off the instance.

To let Rauthy reach a private host, for example an internal identity provider or your SMTP relay, list them in /etc/cloudimg/rauthy-egress-allow, one address or CIDR per line (IPv4 ranges no wider than /16; the metadata service can never be opened), and restart:

echo 10.0.1.25 | sudo tee -a /etc/cloudimg/rauthy-egress-allow
sudo systemctl restart cloudimg-rauthy-metadata rauthy

This VPC's ranges are read from the instance metadata service at every boot, and the last good list is kept for a boot where the metadata service does not answer. See them:

cat /run/cloudimg-rauthy/vpc-cidrs
sudo nft list table inet cloudimg_rauthy | grep -E 'set (vpc4|allow4)' -A 4
# written by rauthy-metadata at 2026-11-09T15:34:00Z
source imds
4 172.31.0.0/16
    set allow4 {
        type ipv4_addr
        flags interval
    }
--
    set vpc4 {
        type ipv4_addr
        flags interval
        elements = { 172.31.0.0/16 }
    }

vpc4 holds this VPC's ranges (here the default VPC's 172.31.0.0/16) and allow4 the addresses you listed in /etc/cloudimg/rauthy-egress-allow (empty until you add one).

Step 11: Keys, the database and backups

/etc/rauthy/config.toml is Rauthy's configuration; it holds no secret. /etc/rauthy/secrets.toml holds this instance's Hiqlite Raft and API secrets and the encryption key. Only root and the rauthy service account can read them:

sudo stat -c '%a %U:%G %n' /etc/rauthy /etc/rauthy/config.toml /etc/rauthy/secrets.toml /var/lib/rauthy /var/lib/rauthy/data
750 root:rauthy /etc/rauthy
640 root:rauthy /etc/rauthy/config.toml
640 root:rauthy /etc/rauthy/secrets.toml
700 rauthy:rauthy /var/lib/rauthy
700 rauthy:rauthy /var/lib/rauthy/data

The Hiqlite database is under /var/lib/rauthy/data. The image ships it empty: first boot creates this instance's database, admin, signing keys and secrets, so no two instances share any of them.

Back up secrets.toml with the database, and protect both: without the encryption key the client secrets, MFA secrets and signing keys in a database backup cannot be used, and with it they can. Rauthy writes its own database backup every day at 02:30 UTC to /var/lib/rauthy/data/state_machine/backups and keeps three days of them, on this instance's disk. To copy a backup and the keys off the instance (replace <backup-dir> with a directory only root can read):

sudo install -d -m 0700 <backup-dir>
sudo cp -p /etc/rauthy/config.toml /etc/rauthy/secrets.toml <backup-dir>/
sudo cp -a /var/lib/rauthy/data/state_machine/backups <backup-dir>/

Step 12: Security notes

  • Metadata service and private addresses. The AMI requires IMDSv2. systemd denies Rauthy, nginx and the first-boot service the instance metadata addresses. One small root service, cloudimg-rauthy-metadata, reads this VPC's ranges from the metadata service at every boot and loads them into the firewall table that holds Rauthy and nginx; if it cannot, Rauthy does not start until it can (it retries by itself).
  • Where the secrets live. The Hiqlite secrets and the encryption key are only in secrets.toml (mode 0640, root:rauthy); they are in no environment variable, command line, journal or log. All of them are generated on your instance at first boot. The admin's password reaches Rauthy once, on its first start, and first boot then removes it from the configuration; the instance has no swap, and core dumps are off.
  • Accounts. First boot creates the one admin, admin@rauthy.invalid, before the web port opens. Open self-registration is off. The admin console requires a passkey.
  • Response headers. Content-Security-Policy with frame-ancestors 'none' and object-src 'none', X-Frame-Options: DENY, Strict-Transport-Security: max-age=31536000, X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer and a Permissions-Policy that turns off the camera, microphone, geolocation, payment and USB APIs.
  • Signing algorithms. Rauthy signs each client's tokens with the algorithm set on the client: EdDSA, or RSA (RS256, RS384, RS512). The RSA library Rauthy uses has a published timing advisory (RUSTSEC-2023-0071, "Marvin") without a fixed release; prefer EdDSA for clients that support it.
  • Upgrades. Ubuntu security updates (including nginx) install automatically through unattended-upgrades. Rauthy is a pinned release, so cloudimg publishes new versions of this AMI for new Rauthy releases.

Step 13: Licences and source

Rauthy is licensed under the Apache License 2.0. The image runs upstream's own release binary, unmodified, and records exactly what it was built from:

cat /usr/share/doc/cloudimg-rauthy/SOURCE.txt
Rauthy 0.37.2 (Apache-2.0), unmodified: the release binary /app/rauthy of the official image
  ghcr.io/sebadob/rauthy:0.37.2
  image index     sha256:d733b7580f53c1601c286b5504853b2774e70245f5f9427d9bcda32496b753ee
  linux/amd64     sha256:666e9f54b4deb99d710448b32eb2c18214160744dd3f68f8f7cee140a6cd80ef (config sha256:58c3ffaacc5ae223b036d5bf08a5d98a41b4bacb8c39cf45bc617433569ecec5)
  binary layer    sha256:95cf26fe5fa83a21f27e5a984b30a2f8f194234c7eed8b9e075c2b5296f5cb0c
  /usr/local/bin/rauthy sha256 50f65249a718c84c4eb45e8e15156df8ad07ef466867682844463cc83d4dfcc4 (71371040 bytes)
Source: the release tag v0.37.2 = commit fe6bcb95192e8a83c29e2b525bf4960933bea3bd
  https://github.com/sebadob/rauthy/tree/v0.37.2 (source tarball sha256 7623784ebaf5db642af063b7837a2341c002d595b9a54fd445e0dd556de72226)
  Third-party licences: the crates in Cargo.lock and the frontend's npm modules at that tag (all permissive or
  MPL-2.0; cloudimg's build census found no CCL, BUSL, SSPL, Elastic or Commons Clause licence and none undeclared).
cloudimg changes NO Rauthy source file. The appliance configuration (config template, nginx, systemd units, first-boot
helpers) is in /usr/local/lib/cloudimg/rauthy, /usr/local/sbin/rauthy-* and /etc/nginx.

The licence text is in /usr/share/doc/cloudimg-rauthy/LICENSE-rauthy-Apache-2.0.txt. nginx and nftables are the Ubuntu packages (/usr/share/doc/<package>/copyright).

Support

cloudimg supports this image: email support@cloudimg.co.uk. See the identity, secrets and PKI listing guide for the other versions in this listing.