RAuth is a lightweight, high-performance authentication proxy and user management system written in Go. It is specifically architected to provide a centralized, secure access control layer for self-hosted infrastructure via the Nginx auth_request module.
RAuth eliminates the complexity of full-scale identity providers while maintaining enterprise-grade security standards like Passkey (WebAuthn) support, AES-256-GCM session encryption, and real-time Prometheus observability.
- π Core Features
- ποΈ System Architecture
- π‘οΈ Security Architecture
- π¦ Technical Stack
- π§ Nginx Integration
- π Monitoring & Observability
- π Screenshots
- βοΈ Configuration
- π Deployment
- π» Development
- β Troubleshooting FAQ
|
|
|
|
Note
Browser & Device Support: Passkeys are supported on Chrome 67+, Edge 79+, Firefox 60+, and Safari 13+. Hardware keys (YubiKey) work across all platforms, while platform authenticators (TouchID, Windows Hello) require OS-level support.
RAuth integrates seamlessly into your existing Nginx proxy stack using the auth_request module.
%%{init: {'theme': 'dark', 'themeVariables': { 'primaryColor': '#dc3545', 'primaryTextColor': '#fff', 'lineColor': '#ef4444', 'nodeBorder': '#dc3545', 'mainBkg': '#1f1f1f', 'actorBkg': '#1f1f1f' }}}%%
graph TD
User((User)) -->|HTTPS Request| Nginx[Nginx Reverse Proxy]
Nginx -->|1. auth_request| RAuth[RAuth Auth Service]
RAuth <-->|2. Session Check| Redis[(Redis Store)]
RAuth -->|3. Return 200/401| Nginx
Nginx -->|4. If 200: Forward| Backend[Your App Backend]
Nginx -->|5. If 401: Redirect| Login[RAuth Login UI]
RAuth is built with a "Security-First" mindset, implementing advanced system-wide hardening:
- Authenticated Encryption: All session tokens stored in cookies are encrypted using AES-256-GCM.
- At-Rest Secret Encryption: User TOTP secrets are encrypted with the
SERVER_SECRETbefore being stored in Redis, protecting against database exposure. - Enumeration & Timing Attack Mitigation: Uniform response times for login and session checks using fully valid, pre-computed 60-character dummy bcrypt hashes, completely neutralizing username enumeration timing attacks.
- Brute-Force Protection & Rate Limiting: Atomic Redis-backed rate limiting per IP and per username, protecting login pages, registration endpoints, and sensitive profile-level operations (e.g. 2FA verification, password change).
- Hardened CSRF & CSP: Strictly configured CSRF cookies (HTTPOnly, Secure, SameSite=Lax) and a robust Content Security Policy (CSP).
- Clone Detection: WebAuthn signature counter persistence allows the detection of cloned or tampered hardware security keys.
- Hardened Redirects: Built-in protection against Open Redirects, including protocol-relative URL bypasses.
- Injection-Safe Emails: All automated emails are hardened against Header (CRLF) Injection and HTML/XSS attacks.
- Custom Error Interception: Branded 404, 403, and 500 error pages prevent technical leakage and provide a unified UX.
- Background Hardening: Automatic daily Geo-IP database updates and session cleanup tasks.
- Runtime: Go 1.26+ (High-concurrency, memory-safe)
- Web Framework: Echo v4
- Identity Store: Redis 8.0+
- MFA Core: go-webauthn & pquerna/otp
- Geo-IP: Native Go MMDB integration via geoip2-golang
- Monitoring: Prometheus Client
- Frontend: Native Bootstrap 5 with a custom Glassmorphism "Matrix" theme.
RAuth acts as an "Authorizer" for Nginx. When a request hits your proxy, Nginx performs a lightweight subrequest to RAuth to verify the user's session.
# 1. Define the RAuth validation endpoint
location = /rauth-verify {
internal;
proxy_pass http://rauth-auth-service/rauthvalidate;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
# Forward the real client IP on the subrequest so rauth logs/acts on it
# instead of nginx's own address. Pair with TRUST_X_REAL_IP=true (or
# TRUST_X_FORWARDED_FOR=true) on the rauth container.
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 2. Protect your application
location / {
auth_request /rauth-verify;
# Propagate user identity to your backend
auth_request_set $user $upstream_http_x_rauth_user;
proxy_set_header X-User $user;
# REQUIRED for Automatic Token Rotation:
# Propagate Set-Cookie from RAuth back to the client
auth_request_set $new_cookie $upstream_http_set_cookie;
add_header Set-Cookie $new_cookie;
# Handle unauthorized users
error_page 401 = @error401;
proxy_pass http://your-app-backend;
}
location @error401 {
return 302 https://auth.yourdomain.com/rauthlogin?rd=$scheme://$http_host$request_uri;
}Important
The RAuth host must NOT be behind auth_request. Serve auth.yourdomain.com (its /rauthlogin, /rauthmgmt, and static asset routes) as a plain proxy_pass to RAuth. If the login page itself requires authentication, every unauthenticated visit returns 401 and redirects back to the login page β an infinite loop the user can never escape. Only protect your other applications with the auth_request subrequest.
RAuth's only peer is your proxy (e.g. the Docker bridge 172.x.x.x), so the real client IP must arrive in a header nginx sets. With no TRUST_* flag, RAuth uses smart mode, which rejects private forwarded IPs β so a direct LAN client (192.168.x/10.x) is dropped and you "only see the Docker IP".
If your service is reachable both through Cloudflare and directly (LAN/Tailscale bypassing Cloudflare), do not set TRUST_CLOUDFLARE_IP β RAuth can't distinguish the two paths (its peer is always nginx), so a direct client could spoof CF-Connecting-IP. Instead, let nginx resolve the visitor and forward a single authoritative X-Real-IP:
# Scope realip to Cloudflare's ranges ONLY (https://www.cloudflare.com/ips/),
# so direct LAN/Tailscale connections keep their real $remote_addr while
# Cloudflare traffic is rewritten to the true visitor IP.
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
# ... (full IPv4 + IPv6 list from cloudflare.com/ips) ...
set_real_ip_from 2c0f:f248::/32;
real_ip_header CF-Connecting-IP;
real_ip_recursive on;
# Forward the resolved client IP to RAuth on the auth subrequest:
# proxy_set_header X-Real-IP $remote_addr;Then set TRUST_X_REAL_IP=true on the RAuth container. This is safe because nginx overwrites X-Real-IP with $remote_addr, which a client cannot spoof through the proxy. The full annotated config is in nginx-proxy-example.conf.
Caddy features a built-in forward_auth directive that makes integrating RAuth incredibly straightforward.
# Protect app.example.com using RAuth
app.example.com {
# 1. Forward validation request to RAuth's endpoint
forward_auth http://rauth-auth-service:5980 {
uri /rauthvalidate
copy_headers X-RAuth-User
}
# 2. Reverse proxy to your backend service if authorized
reverse_proxy http://your-app-backend:8080
}Traefik uses a ForwardAuth middleware layer. You define the middleware on the RAuth service and reference it on the router of the application you want to protect.
services:
# 1. Define the RAuth service and middleware labels
rauth:
image: ghcr.io/arumes31/rauth-auth:latest
container_name: rauth-auth-service
labels:
- "traefik.http.middlewares.rauth-auth.forwardauth.address=http://rauth-auth-service:5980/rauthvalidate"
- "traefik.http.middlewares.rauth-auth.forwardauth.trustForwardHeader=true"
- "traefik.http.middlewares.rauth-auth.forwardauth.authResponseHeaders=X-RAuth-User"
# 2. Reference the middleware to protect your application
your-app:
image: your-app-image:latest
labels:
- "traefik.http.routers.your-app.rule=Host(`app.example.com`)"
- "traefik.http.routers.your-app.middlewares=rauth-auth"RAuth can protect multiple apps across different subdomains using a single deployment. Set your COOKIE_DOMAIN to the root domain (e.g., example.com) to share session state between app1.example.com and app2.example.com.
RAuth exposes real-time metrics in Prometheus format at /metrics.
rauth_login_success_total: Cumulative count of successful logins.rauth_login_failed_total: Cumulative count of failed attempts.rauth_active_sessions: Gauge showing the current number of valid sessions in Redis.rauth_rate_limit_hits_total: Count of requests blocked by the internal throttler.rauth_audit_logs_total: Counter categorized by action (e.g.,USER_CHANGE_PASSWORD,ADMIN_DELETE_USER).
By default, the /metrics endpoint is restricted to:
- Localhost (
127.0.0.1) - Private Subnets (
10.0.0.0/8, etc.) - Tailscale IP ranges (
100.64.0.0/10)
RAuth is configured via Environment Variables.
π View Configuration Options (Environment Variables)
| Category | Variable | Description | Default |
|---|---|---|---|
| Secret | SERVER_SECRET |
32+ char key for AES encryption | REQUIRED |
| Admin | INITIAL_USER |
Initial admin username | admin |
| Admin | INITIAL_PASSWORD |
Initial admin password | (None) |
| Admin | INITIAL_EMAIL |
Initial admin email address | admin@example.com |
| Admin | INITIAL_2FA_SECRET |
Optional: Pre-set Base32 2FA secret | (None) |
| Redis | REDIS_HOST |
Hostname of the Redis instance | rauth-auth-redis |
| Redis | REDIS_PORT |
Port of the Redis instance | 6379 |
| Redis | REDIS_PASSWORD |
Password for Redis auth | (None) |
| Auth | COOKIE_DOMAIN |
Domain for the auth cookie | example.com |
| Auth | ALLOWED_HOSTS |
Redirect whitelist (Comma-separated) | localhost,127.0.0.1 |
| Auth | TOKEN_VALIDITY_MINUTES |
Session duration in minutes | 2880 (2 days) |
| Auth | ALLOWED_COUNTRIES |
List of allowed country codes (e.g. US,DE) |
(Any) |
| WebAuthn | WEBAUTHN_ORIGINS |
Allowed origins for Passkeys (Comma-separated) | (Auto-generated) |
| URL | PUBLIC_URL |
Base URL for email links (e.g., https://auth.example.com) |
http://localhost:5980 |
| Session | TOKEN_ROTATION_MINUTES |
Frequency of automatic session token rotation (0 = disabled) | 0 |
| Network | AUTH_PORT |
Port to expose the auth service | 5980 |
| Proxy | TRUST_X_FORWARDED_FOR |
Trust leftmost IP in X-Forwarded-For |
false |
| Proxy | TRUST_X_REAL_IP |
Trust IP in X-Real-IP |
false |
| Proxy | TRUST_CLOUDFLARE_IP |
Trust IP in CF-Connecting-IP |
false |
| Policy | PWD_MIN_LENGTH |
Minimum required password length | 8 |
| Policy | PWD_REQUIRE_UPPER |
Require uppercase in passwords | true |
| Policy | PWD_REQUIRE_LOWER |
Require lowercase in passwords | true |
| Policy | PWD_REQUIRE_NUMBER |
Require numbers in passwords | true |
| Policy | PWD_REQUIRE_SPECIAL |
Require special chars in passwords | true |
| Security | METRICS_ALLOWED_IPS |
CIDR list for /metrics access |
(Private + Tailscale) |
| Geo-IP | MAXMIND_ACCOUNT_ID |
Your Account ID for Geo-IP updates | REQUIRED |
| Geo-IP | MAXMIND_LICENSE_KEY |
Your License Key for Geo-IP updates | REQUIRED |
| Geo-IP | GEOIP_EDITION_IDS |
Databases to download | GeoLite2-Country |
| Geo-IP | MAXMIND_DB_PATH |
Path to local MaxMind .mmdb file | /app/geoip/GeoLite2-Country.mmdb |
| Geo-IP | GEO_API_HOST |
Hostname of local Geo-IP service | rauth-geo-service |
| Geo-IP | GEO_API_PORT |
Port of local Geo-IP service | 3000 |
SMTP_HOST |
SMTP server hostname | (None) | |
SMTP_PORT |
SMTP server port (e.g., 587) | 587 |
|
SMTP_USER |
SMTP username | (None) | |
SMTP_PASS |
SMTP password | (None) | |
SMTP_FROM |
Sender email address | (None) | |
| Throttle | RATE_LIMIT_LOGIN_MAX |
Max authentication attempts per IP | 30 |
| Throttle | RATE_LIMIT_LOGIN_DECAY |
Reset window for auth (seconds) | 300 |
| Throttle | RATE_LIMIT_REG_MAX |
Max registrations per IP | 10 |
| Throttle | RATE_LIMIT_REG_DECAY |
Reset window for reg (seconds) | 300 |
| Throttle | RATE_LIMIT_VALIDATE_MAX |
Max validation attempts per IP | 1000 |
| Throttle | RATE_LIMIT_VALIDATE_DECAY |
Reset window for validation (seconds) | 60 |
| Throttle | RATE_LIMIT_LOGIN_ACCESS_MAX |
Max GET/POST requests to login per IP | 300 |
| Throttle | RATE_LIMIT_LOGIN_ACCESS_DECAY |
Reset window for login access (seconds) | 60 |
| Throttle | RATE_LIMIT_LOGIN_FAIL_USER_MAX |
Max fails per account before lockout | 10 |
| Throttle | RATE_LIMIT_LOGIN_FAIL_USER_DECAY |
Account lockout duration (seconds) | 300 |
| Throttle | RATE_LIMIT_LOGIN_FAIL_IP_MAX |
Max global IP failures before block | 50 |
| Throttle | RATE_LIMIT_LOGIN_FAIL_IP_DECAY |
Global IP block duration (seconds) | 600 |
| Regional | TZ |
Container Timezone (e.g., Europe/Berlin) |
UTC |
- Docker and Docker Compose installed on your host system.
- A registered domain name (e.g.
example.com) with DNS records pointing to your reverse proxy. - An SSL Certificate (WebAuthn / Passkeys require a secure context).
RAuth requires MaxMind GeoLite2 databases for advanced session geo-fencing and security logs.
- Sign up for a free account at MaxMind.
- Create and generate a License Key from the account panel.
- Save your Account ID and License Key to use in the environment config.
- Clone the repository:
git clone https://github.com/arumes31/rauth.git cd rauth - Prepare the environment configuration file:
cp example.env .env
- Configure the
.envsettings: Open.envin an editor and set these minimum required values:# Cryptographic Security Key (generate a random 32+ character string) SERVER_SECRET=your_32char_secure_random_key_here # Domain & Cookie configurations COOKIE_DOMAIN=example.com ALLOWED_HOSTS=auth.example.com,app.example.com PUBLIC_URL=https://auth.example.com # Geo-IP credentials MAXMIND_ACCOUNT_ID=your_maxmind_account_id MAXMIND_LICENSE_KEY=your_maxmind_license_key # Seed Administrative User INITIAL_USER=admin INITIAL_PASSWORD=temporary_secure_password INITIAL_EMAIL=admin@example.com
By default, RAuth uses Smart IP Detection to find the real client IP if the connection comes from a private network (like Docker). If your reverse proxy is on a public IP or you want explicit control, set these in .env:
TRUST_X_FORWARDED_FOR=true: If your proxy sends the standardX-Forwarded-Forheader.TRUST_X_REAL_IP=true: If your proxy sends a single IP inX-Real-IP.TRUST_CLOUDFLARE_IP=true: If you are using Cloudflare.
RAuth provides two Docker Compose configurations to suit your needs:
Use the pre-compiled, official multi-architecture image directly from the GitHub Container Registry (GHCR) with docker-compose.ghcr.yml. You do not need to clone the repository or compile anything from source.
Create a docker-compose.ghcr.yml file or run:
docker compose -f docker-compose.ghcr.yml up -dHere is the docker-compose.ghcr.yml configuration:
services:
rauth-auth-service:
image: ghcr.io/arumes31/rauth-auth:latest
container_name: rauth-auth-service
ports:
- "${AUTH_PORT:-5980}:80"
environment:
- REDIS_HOST=rauth-auth-redis
- REDIS_PORT=6379
- REDIS_PASSWORD=${REDIS_PASSWORD:-rauthsecurepassword}
- INITIAL_USER=${INITIAL_USER:-admin}
- INITIAL_PASSWORD=${INITIAL_PASSWORD}
- INITIAL_EMAIL=${INITIAL_EMAIL:-admin@example.com}
- INITIAL_2FA_SECRET=${INITIAL_2FA_SECRET}
- TOKEN_VALIDITY_MINUTES=${TOKEN_VALIDITY_MINUTES:-2880}
- COOKIE_DOMAIN=${COOKIE_DOMAIN:-example.com}
- ALLOWED_HOSTS=${ALLOWED_HOSTS:-localhost,127.0.0.1}
- ALLOWED_COUNTRIES=${ALLOWED_COUNTRIES}
- SERVER_SECRET=${SERVER_SECRET}
- TZ=${TZ:-UTC}
- MAXMIND_ACCOUNT_ID=${MAXMIND_ACCOUNT_ID}
- MAXMIND_LICENSE_KEY=${MAXMIND_LICENSE_KEY}
- MAXMIND_DB_PATH=/app/geoip/GeoLite2-Country.mmdb
- GEOIP_EDITION_IDS=${GEOIP_EDITION_IDS:-GeoLite2-Country}
- GEOIP_DATABASE_DIRECTORY=/app/geoip
- WEBAUTHN_ORIGINS=${WEBAUTHN_ORIGINS}
- PUBLIC_URL=${PUBLIC_URL:-http://localhost:5980}
- PWD_MIN_LENGTH=${PWD_MIN_LENGTH:-8}
- PWD_REQUIRE_UPPER=${PWD_REQUIRE_UPPER:-true}
- PWD_REQUIRE_LOWER=${PWD_REQUIRE_LOWER:-true}
- PWD_REQUIRE_NUMBER=${PWD_REQUIRE_NUMBER:-true}
- PWD_REQUIRE_SPECIAL=${PWD_REQUIRE_SPECIAL:-true}
- METRICS_ALLOWED_IPS=${METRICS_ALLOWED_IPS:-127.0.0.1,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,100.64.0.0/10}
- RATE_LIMIT_LOGIN_MAX=${RATE_LIMIT_LOGIN_MAX:-30}
- RATE_LIMIT_LOGIN_DECAY=${RATE_LIMIT_LOGIN_DECAY:-300}
- RATE_LIMIT_REG_MAX=${RATE_LIMIT_REG_MAX:-10}
- RATE_LIMIT_REG_DECAY=${RATE_LIMIT_REG_DECAY:-300}
- RATE_LIMIT_VALIDATE_MAX=${RATE_LIMIT_VALIDATE_MAX:-1000}
- RATE_LIMIT_VALIDATE_DECAY=${RATE_LIMIT_VALIDATE_DECAY:-60}
- RATE_LIMIT_LOGIN_ACCESS_MAX=${RATE_LIMIT_LOGIN_ACCESS_MAX:-300}
- RATE_LIMIT_LOGIN_ACCESS_DECAY=${RATE_LIMIT_LOGIN_ACCESS_DECAY:-60}
- RATE_LIMIT_LOGIN_FAIL_USER_MAX=${RATE_LIMIT_LOGIN_FAIL_USER_MAX:-10}
- RATE_LIMIT_LOGIN_FAIL_USER_DECAY=${RATE_LIMIT_LOGIN_FAIL_USER_DECAY:-300}
- RATE_LIMIT_LOGIN_FAIL_IP_MAX=${RATE_LIMIT_LOGIN_FAIL_IP_MAX:-50}
- RATE_LIMIT_LOGIN_FAIL_IP_DECAY=${RATE_LIMIT_LOGIN_FAIL_IP_DECAY:-600}
- SMTP_HOST=${SMTP_HOST}
- SMTP_PORT=${SMTP_PORT:-587}
- SMTP_USER=${SMTP_USER}
- SMTP_PASS=${SMTP_PASS}
- SMTP_FROM=${SMTP_FROM}
depends_on:
- rauth-auth-redis
volumes:
- ./geoip-data:/app/geoip
networks:
- auth-network
rauth-auth-redis:
image: redis:7.4-alpine
container_name: rauth-auth-redis
hostname: rauth-auth-redis
command: redis-server --requirepass ${REDIS_PASSWORD:-rauthsecurepassword}
volumes:
- ./redis-data:/data
networks:
- auth-network
networks:
auth-network:
driver: bridgeIf you are modifying the codebase or prefer to compile the application locally, use docker-compose.yml (which includes a local build context):
docker compose up -d --buildRAuth will automatically pull the secure image from GHCR, download and update the latest Geo-IP database, boot the Redis memory store, and seed your initial admin user.
- Open your browser and navigate to RAuth's administration portal at
https://auth.example.com/rauthmgmt(orhttp://localhost:5980/rauthmgmtfor local development). - Authenticate using your
INITIAL_USERandINITIAL_PASSWORD. - Follow the secure prompt to register your Multi-Factor Authentication keys (Passkey / TOTP) to fully secure the admin account.
To start protecting your applications, configure your reverse proxy to forward user verification requests to RAuth's /rauthvalidate endpoint:
- Nginx: See the Nginx Integration section to set up the
auth_requestsubrequest logic. - Caddy: See the Caddy Integration section to configure Caddy's built-in
forward_authblock. - Traefik: See the Traefik Integration section to enable the
ForwardAuthmiddleware layer.
- Go 1.26+
- Redis (or miniredis for testing)
We use a combination of unit tests, integration tests, and fuzzing to ensure core security logic remains robust.
go test -v ./...β View Troubleshooting FAQ Solutions
Q: Why am I stuck in a 401 Redirect Loop?
A: There are two common causes:
- Cookie domain mismatch β the
COOKIE_DOMAINin RAuth doesn't match the domain of the application you are protecting. Ensure the cookie domain is set to a common root domain (e.g.example.com) to allow cookie sharing across subdomains (e.g.app1.example.comandauth.example.com). - The login page itself is behind auth β the RAuth host (e.g.
auth.yourdomain.com) and its/rauthlogin,/rauthmgmt, and static asset routes must be served directly and reachable WITHOUT anauth_request. If you place the auth domain behind RAuth's ownauth_request, every visit to the login page returns 401 and redirects back to the login page β an infinite loop, since the user can never reach the form to authenticate. Exposeauth.yourdomain.comas a plain reverse-proxyproxy_passto RAuth (noauth_request), and only protect your other applications with the subrequest.
Q: Why is my session immediately invalidated when using a tablet or rotating my device?
A: Large tablets (like the Samsung Galaxy Tab series) default to requesting the "Desktop site" (spoofing a desktop Linux UA) but dynamically switch back to a mobile UA when rotated, resized in split-screen/pop-up views, or during background/Service-Worker requests. Using User-Agent Client Hints (UA-CH) under secure contexts (HTTPS) and the lenient browser-engine fallback for other browsers (like Safari/Firefox) and non-secure contexts reduce false invalidations but cannot eliminate them across all device mode, rotation, split-screen, or background/request context changes.
Q: WebAuthn/Passkey registration fails or gets rejected?
A: WebAuthn requires a secure origin (HTTPS or localhost for development). Ensure your Nginx proxy is serving over SSL and forwarding the correct host headers (proxy_set_header Host $host;). Additionally, check that WEBAUTHN_ORIGINS is configured with the exact origin scheme and port (e.g., https://auth.example.com).
Q: How do I resolve signature counter/verification mismatches?
A: This can happen if the hardware key was cloned or the local state fell out of sync. For security reasons, RAuth detects this as a potential clone attack. Reset the user's WebAuthn key in the administrative dashboard to register the device afresh.
Q: Why am I getting "403 Forbidden" geo-blocking errors for legitimate requests?
A: This happens if the user's IP address maps to an unlisted country, or the local MaxMind database is stale or missing. Verify that MAXMIND_ACCOUNT_ID and MAXMIND_LICENSE_KEY are correct, check container logs for geo-download status, or adjust the ALLOWED_COUNTRIES environment variable.
Q: Why are active sessions not displaying the correct client IP addresses in the admin audits?
A: Ensure your Nginx configuration passes proxy_set_header X-Real-IP $remote_addr; and proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; to RAuth. RAuth includes Smart IP Detection which automatically trusts these headers if the immediate connection originates from a private IP range (like a Docker network). For complex multi-hop setups or if your proxy is on a public IP, you must explicitly enable TRUST_X_FORWARDED_FOR=true in your .env.
Q: How does Automatic Token Rotation work and what are the requirements?
A: When TOKEN_ROTATION_MINUTES is set, RAuth will automatically issue a new session token periodically during the background validation subrequest. This significantly reduces the risk of session hijacking. Requirement: Your reverse proxy (Nginx/Traefik) must be configured to forward the Set-Cookie header from the auth subrequest back to the client.
Q: Why are users experiencing "Rate Limit Exceeded" blockages during normal dashboard use?
A: The default session validation threshold might be too aggressive for heavy single-page apps making hundreds of concurrent resource subrequests. Increase RATE_LIMIT_VALIDATE_MAX (e.g. to 5000) or adjust the decay window RATE_LIMIT_VALIDATE_DECAY to accommodate high-volume internal reverse-proxied traffic.
Q: "Redis connection refused" in Docker?
A: Ensure RAuth and Redis are on the same Docker network. If using the default Compose file, use REDIS_HOST=rauth-auth-redis.
Q: How do I resolve "SMTP connection timed out" or authentication errors for security emails?
A: Ensure RAuth's container can reach the SMTP host (check DNS and outbound firewall rules). Common ports are 587 (StartTLS) or 465 (SSL). Verify SMTP_HOST, SMTP_PORT, SMTP_USER, and SMTP_PASS are set correctly.
Q: How do I recover if the administrator loses their 2FA / TOTP key or gets locked out?
A: You can easily boot RAuth in a recovery mode. Set INITIAL_USER, INITIAL_PASSWORD, and INITIAL_EMAIL environment variables inside your .env file and restart the container. On startup, RAuth automatically verifies if the admin credentials are valid or updates them. Alternatively, if you have access to your Redis CLI, you can manually delete the admin's WebAuthn key using:
redis-cli DEL user:admin:webauthn_credsQ: Why is Prometheus unable to scrape metrics from the /metrics endpoint?
A: By default, RAuth restricts metrics access to localhost and private network CIDR blocks for security. If your Prometheus instance runs on an external network, add its IP address or subnet range to the METRICS_ALLOWED_IPS environment variable (e.g. METRICS_ALLOWED_IPS=127.0.0.1,192.168.1.150).
Q: Why is the authentication cookie not saving when testing in local non-HTTPS development?
A: Modern browsers enforce the Secure attribute on cookies and block them over unencrypted HTTP links. However, browsers treat localhost and 127.0.0.1 as secure contexts even over plain HTTP. For local testing without SSL, ensure your browser URL points to http://localhost:5980 instead of a custom local domain (e.g. http://auth.local) or configure an SSL reverse proxy.
Built with β€οΈ for secure, fast, and private self-hosting.