Jump to content

Services/Authentik

From Stronghold Wiki
Revision as of 18:16, 5 July 2026 by Wikibot (talk | contribs) (Migrated from services/AUTHENTIK.md)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

As of: 2026-07-05 — migrated from `services/AUTHENTIK.md`

Authentik — Identity & SSO Provider

Complete setup & operations guide for Authentik (identity provider, OAuth2/OIDC, LDAP directory service).

Criticality: CRITICAL — The lynchpin. Everything on Stronghold depends on Authentik for authentication. Without it, most other services are inaccessible or in an inconsistent state. Must be recovered first in any disaster scenario.


Overview

Authentik is a self-hosted identity provider that serves as the single source of truth for authentication across Stronghold. It provides:

  • OAuth2 / OIDC providers for web apps (Immich, Memos, Linkwarden, Forgejo, Vaultwarden, Grafana, n8n, Matrix, Cloudflare Access)
  • LDAP directory for apps that don't support OAuth (Nextcloud, Jellyfin, Stalwart mail server)
  • Group membership for role-based access (authentik Admins, family-and-friends, email provisioning groups)
  • User management — all users are defined here; other services sync from Authentik
  • API tokens for programmatic access (n8n, CI/CD, automation)

Current Users:

  • marcus (primary admin user, member of authentik Admins)
  • akadmin (backup admin user)
  • Service accounts: stalwart-svc, nextcloud-svc (for LDAP directory access)

Current Version: 2025.10.3

Access: https://key.wilsoz.com/if/admin/


Service Information

Property Value
Compose file /library/authentik-app/docker-compose.yml
Containers authentik-app_server_1, authentik-app_worker_1, authentik-app_postgresql_1
Database PostgreSQL 16 (in-container, persisted to Docker volume database)
HTTP Port 9000 (external via 9443 HTTPS via reverse proxy)
HTTPS Port 9443 (internal, for direct connections)
LDAP Outpost Port 389 (via LDAP Outpost container; internal only, reaches stronghold via http://172.17.0.1:9000)
Data volumes /library/authentik-app/media (custom templates, branding)
Database volume authentik-app_database (PostgreSQL data)
Networks authentik-app_default (internal Docker network, MTU 1280)
Image versions server/worker: ghcr.io/goauthentik/server:2025.10.3
Restart policy unless-stopped

Initial Setup from Scratch

Prerequisites

  • Docker and docker-compose installed
  • Network access to PostgreSQL (internal container or external)
  • Cloudflare DNS configured for key.wilsoz.com
  • VPS reverse proxy (nginx) configured to forward to stronghold:9000

1. Create Directory & Files

<syntaxhighlight lang="bash"> mkdir -p /library/authentik-app cd /library/authentik-app </syntaxhighlight>

2. Create docker-compose.yml

<syntaxhighlight lang="yaml"> version: "3.8"

services:

 postgresql:
   image: docker.io/library/postgres:16-alpine
   restart: unless-stopped
   healthcheck:
     test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
     interval: 30s
     timeout: 5s
     retries: 5
     start_period: 20s
   volumes:
     - database:/var/lib/postgresql/data
   env_file:
     - .env
   environment:
     POSTGRES_DB: ${PG_DB:-authentik}
     POSTGRES_USER: ${PG_USER:-authentik}
     POSTGRES_PASSWORD: ${PG_PASS:?database password required}
 server:
   image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.10.3}
   restart: unless-stopped
   command: server
   ports:
     - "${COMPOSE_PORT_HTTP:-9000}:9000"
     - "${COMPOSE_PORT_HTTPS:-9443}:9443"
   env_file:
     - .env
   environment:
     AUTHENTIK_POSTGRESQL__HOST: postgresql
     AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
     AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
     AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
     AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
   volumes:
     - ./media:/media
     - ./custom-templates:/templates
   depends_on:
     postgresql:
       condition: service_healthy
 worker:
   image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2025.10.3}
   restart: unless-stopped
   command: worker
   user: root
   env_file:
     - .env
   environment:
     AUTHENTIK_POSTGRESQL__HOST: postgresql
     AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
     AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
     AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
     AUTHENTIK_SECRET_KEY: ${AUTHENTIK_SECRET_KEY:?secret key required}
   volumes:
     - /var/run/docker.sock:/var/run/docker.sock
     - ./media:/media
     - ./certs:/certs
     - ./custom-templates:/templates
   depends_on:
     postgresql:
       condition: service_healthy

volumes:

 database:

</syntaxhighlight>

3. Create .env File

<syntaxhighlight lang="bash">

  1. PostgreSQL

PG_DB=authentik PG_USER=authentik PG_PASS=<generate-strong-password-32-chars>

  1. Authentik

AUTHENTIK_SECRET_KEY=<generate-strong-secret-key-50-chars> AUTHENTIK_IMAGE=ghcr.io/goauthentik/server AUTHENTIK_TAG=2025.10.3

  1. Ports

COMPOSE_PORT_HTTP=9000 COMPOSE_PORT_HTTPS=9443

  1. Bootstrap (initial admin)

AUTHENTIK_BOOTSTRAP_PASSWORD=<temporary-init-password> AUTHENTIK_BOOTSTRAP_TOKEN=<temporary-init-token> </syntaxhighlight>

Important: Generate secure random values for passwords and secrets: <syntaxhighlight lang="bash"> openssl rand -base64 32 # For PG_PASS and AUTHENTIK_SECRET_KEY openssl rand -hex 16 # For AUTHENTIK_BOOTSTRAP_TOKEN </syntaxhighlight>

4. Start Authentik

<syntaxhighlight lang="bash"> cd /library/authentik-app docker compose up -d </syntaxhighlight>

Wait for PostgreSQL to be healthy and server to start (~30-60 seconds):

<syntaxhighlight lang="bash"> docker compose logs -f server

  1. Watch for "Application startup complete"

</syntaxhighlight>

5. Initial Admin User

After startup, navigate to https://key.wilsoz.com/if/admin/:

  • Username: akadmin
  • Password: value of AUTHENTIK_BOOTSTRAP_PASSWORD

First steps in Admin UI:

  1. Go to Directory → Users → Create

Groups: Add to authentik Admins**

  1. Go to Directory → Groups → Create

authentik Admins** (if not already created) family-and-friends** (for app access) craniumslows-email** (for email provisioning) wilsoz-email** (for email provisioning) ldap-search** (for LDAP bind accounts)

  1. Add service users:

Directory → Users → Create**

    • Username: stalwart-svc, Email: stalwart-svc@internal

Groups: Add to ldap-search**

    • Username: nextcloud-svc, Email: nextcloud-svc@internal

Groups: Add to ldap-search**

6. Set Up LDAP Outpost

LDAP is required for Stalwart (mail server), Nextcloud (CalDAV/CardDAV), and Jellyfin (media server) to authenticate.

In Authentik Admin UI:

  1. Go to Infrastructure → Outposts → Create
    • Name: authentik-ldap
    • Type: LDAP
  1. In Directory → LDAP → Configure LDAP
    • Base DN: dc=ldap,dc=goauthentik,dc=io
    • User base: ou=users,dc=ldap,dc=goauthentik,dc=io
  1. Assign the LDAP outpost to the LDAP provider

The LDAP server will be reachable at 172.17.0.1:389 from other containers.

7. Configure OAuth2 Providers

For each OAuth application (Immich, Memos, Linkwarden, etc.), create an OAuth2 provider in Authentik:

Immich example:

  1. Go to Applications → Providers → Create OAuth2/OpenID Provider
    • Name: immich-oauth
    • Authorization flow: default-authentication-flow
    • Client type: Confidential
    • Redirect URIs:
    ```
    https://photos.wilsoz.com/auth/login/oauth2/callback
    https://photos.wilsoz.com/auth/login/oauth2/mobile-redirect
    ```
  1. Save — copy the generated Client ID and Client Secret
  2. Add property mappings:
    • authentik default OAuth Mapping: OpenID 'openid'
    • authentik default OAuth Mapping: OpenID 'email'
    • authentik default OAuth Mapping: OpenID 'profile'
  1. Enable: include_claims_in_id_token
  2. Assign a signing key (see Critical Gotchas section below)

Repeat for each app.

8. Configure VPS Reverse Proxy

On VPS, add nginx server block:

<syntaxhighlight lang="nginx"> server {

   listen 443 ssl http2;
   server_name key.wilsoz.com;
   ssl_certificate /etc/letsencrypt/live/key.wilsoz.com/fullchain.pem;
   ssl_certificate_key /etc/letsencrypt/live/key.wilsoz.com/privkey.pem;
   location / {
       proxy_pass http://100.64.207.155:9000;
       proxy_set_header Host $host;
       proxy_set_header X-Real-IP $remote_addr;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_set_header X-Forwarded-Proto $scheme;
       proxy_http_version 1.1;
       proxy_set_header Upgrade $http_upgrade;
       proxy_set_header Connection "upgrade";
   }

} </syntaxhighlight>

Reload nginx: <syntaxhighlight lang="bash"> sudo systemctl reload nginx </syntaxhighlight>


Disaster Mode A: Configuration Corruption

Symptoms

  • Can't log into admin interface
  • OAuth providers missing or broken
  • LDAP directory returning errors
  • Groups or users deleted accidentally
  • Provider credentials lost

Recovery Steps

⚠️ This mode resets Authentik to a fresh state. User data will be lost unless you have a PostgreSQL backup (see Disaster Mode B).

Option 1: Full Reset (if Disaster Mode B not available)

<syntaxhighlight lang="bash"> cd /library/authentik-app

  1. Stop all containers

docker compose down

  1. Remove the database volume (this deletes all Authentik data)

docker volume rm authentik-app_database

  1. Start from scratch

docker compose up -d

  1. Wait for startup

sleep 30 docker logs server | grep "Application startup complete"

  1. Follow "Initial Setup from Scratch" section above

</syntaxhighlight>

Option 2: Selective Reset (if you only need to fix one app)

<syntaxhighlight lang="bash">

  1. Access the Django shell

docker exec authentik-app_server_1 ak shell

  1. Example: Fix an OAuth provider's signing key

from authentik.providers.oauth2.models import OAuth2Provider from authentik.crypto.models import CertificateKeyPair

p = OAuth2Provider.objects.get(name='immich-oauth') jwt_key = CertificateKeyPair.objects.get(name='authentik Internal JWT Certificate') p.signing_key = jwt_key p.save() print(f'Updated {p.name} with JWT signing key') </syntaxhighlight>


Disaster Mode B: Drive Failure / Full Recovery

If Backups Accessible

Authentik data is backed up in two ways:

  1. PostgreSQL dump at Hetzner (in /dumps/authentik-YYYYMMDD.sql.gz)
  2. Media files at Hetzner (in /data/authentik/)

Recovery procedure:

<syntaxhighlight lang="bash">

  1. 1. Set up fresh Authentik (follow "Initial Setup from Scratch" through step 4)

cd /library/authentik-app docker compose up -d

  1. 2. Wait for PostgreSQL to be ready

sleep 30

  1. 3. Drop the initial empty database

docker exec authentik-app_postgresql_1 psql -U authentik -d postgres -c "DROP DATABASE authentik;"

  1. 4. Create a fresh empty database

docker exec authentik-app_postgresql_1 psql -U authentik -d postgres -c "CREATE DATABASE authentik;"

  1. 5. Restore from backup dump (substitute actual date)
  2. First, extract the backup from Hetzner:
  3. (You'll need ssh-key access to u540853.your-storagebox.de port 23)

ssh -p 23 [email protected] "cat backups/authentik-20260512.sql.gz" | \

 gunzip | docker exec -i authentik-app_postgresql_1 psql -U authentik -d authentik
  1. 6. Restore media files

rsync -av -e "ssh -p 23" [email protected]:backups/data/authentik/ /library/authentik-app/media/

  1. 7. Restart Authentik to pick up restored data

docker compose restart server worker

  1. 8. Verify

docker logs server | grep "Application startup complete" curl -s https://key.wilsoz.com/if/admin/ | grep -q "authentik" && echo "Admin UI responding" </syntaxhighlight>

If Backups NOT Accessible

Fall back to Mode A (full reset). Note: All user data, OAuth provider credentials, and LDAP settings will be lost.

After reset, you'll need to:

  1. Recreate all users and groups
  2. Recreate all OAuth2 providers and get new Client IDs/Secrets
  3. Update all dependent services (.env files) with new credentials
  4. Recreate LDAP configuration and outpost

Data Locations

Authentik Data on Host

<syntaxhighlight lang="text"> /library/authentik-app/ ├── docker-compose.yml (service definition) ├── .env (secrets: PG_PASS, AUTHENTIK_SECRET_KEY) ├── media/ (custom templates, branding, uploads) └── custom-templates/ (HTML templates for login pages, etc.) </syntaxhighlight>

PostgreSQL Database Volume

<syntaxhighlight lang="text"> authentik-app_database (Docker volume) ├── postgresql/ (database files) └── ... </syntaxhighlight>

Contains:

  • All users, groups, group memberships
  • All OAuth2 providers, applications, credentials
  • All LDAP configuration
  • All API tokens
  • All authenticator devices (TOTP, WebAuthn)
  • All event logs

Backup Locations

  • PostgreSQL dump: Hetzner /dumps/authentik-YYYYMMDD.sql.gz (daily at 03:00 CST)
  • Media files: Hetzner /data/authentik/ (daily Restic snapshot)
  • Filesystem snapshot: Timeshift snapshot on /dev/md1 (weekly)

What's NOT Backed Up

  • Container images (re-downloadable from Docker Hub)
  • node_modules and build artifacts (not present in Authentik)

Critical Gotchas

1. OAuth2 Signing Key Assignment — MANDATORY

⚠️ If you don't assign a signing key to an OAuth2 provider, the JWKS endpoint returns an empty object {}, and all clients trying to validate tokens will fail.

Every OAuth2 provider MUST have:

  • A signing key assigned: Use authentik Internal JWT Certificate (HS256 algorithm)
  • NOT authentik Self-signed Certificate (RS256) — some apps like Mealie reject RS256

Assignment code: <syntaxhighlight lang="bash"> docker exec authentik-app_server_1 ak shell -c " from authentik.providers.oauth2.models import OAuth2Provider from authentik.crypto.models import CertificateKeyPair

p = OAuth2Provider.objects.get(name='immich-oauth') jwt_key = CertificateKeyPair.objects.get(name='authentik Internal JWT Certificate') p.signing_key = jwt_key p.save() print(f'Assigned signing key to {p.name}') " </syntaxhighlight>

2. Property Mappings for OAuth Scopes

OAuth providers need explicit property mappings for scopes like email, profile. Without them:

  • OIDC discovery endpoint returns only ["openid"]
  • Apps can't get user email or name from token

Always add these three mappings to every OAuth provider:

  • authentik default OAuth Mapping: OpenID 'openid'
  • authentik default OAuth Mapping: OpenID 'email'
  • authentik default OAuth Mapping: OpenID 'profile'

Also enable: include_claims_in_id_token = True

3. LDAP Outpost Internal-Only

The LDAP outpost (port 389) is internal to Docker networking only. External apps can't reach it directly. Instead:

  • Internal Docker containers reach it at 172.17.0.1:389
  • Other hosts on LAN reach it via Stalwart LDAP passthrough (Stalwart exposes LDAP to port 389 externally)

4. Bootstrap Credentials Not Persisted

The AUTHENTIK_BOOTSTRAP_PASSWORD and AUTHENTIK_BOOTSTRAP_TOKEN in .env are only used on first startup. After that, they're ignored. If you lose them:

  • You still have the akadmin user created from bootstrap
  • If you lose the akadmin password, reset via Django shell:

<syntaxhighlight lang="bash"> docker exec authentik-app_server_1 ak shell -c " from authentik.core.models import User u = User.objects.get(username='akadmin') u.set_password('NewPassword123') u.save() print(f'Reset password for {u.username}') " </syntaxhighlight>

5. Secret Key Rotation (Rare but Important)

The AUTHENTIK_SECRET_KEY is used to encrypt stored secrets (OAuth credentials, etc.). If you change this .env value, all encrypted secrets become unreadable. Only change if:

  • You're migrating to a new server
  • You suspect key compromise

When rotating:

  1. Export all OAuth providers' client secrets before changing
  2. Update AUTHENTIK_SECRET_KEY
  3. Restart (secrets will need to be re-entered)

6. Group Membership Propagation Delay

Changes to group membership (e.g., adding a user to a group) are propagated asynchronously via the worker. If an app isn't seeing the new group:

  • Wait 5-10 seconds
  • Restart the worker: docker compose restart worker
  • Check the worker logs: docker logs worker | tail -20

7. API Token Expiry

API tokens can have an expiry. The current claude-api-token (created 2026-02-16) is set to never expire. Check token settings:

<syntaxhighlight lang="bash"> docker exec authentik-app_server_1 ak shell -c " from authentik.core.models import Token t = Token.objects.get(identifier='claude-api-token') print(f'Token: {t.identifier}') print(f'Expires: {t.expires}') " </syntaxhighlight>

If a token expires, create a new one in the Admin UI (User → Tokens) and update all scripts/services using it.

8. LDAP Bind Accounts Must Be in ldap-search Group

Any user/service that needs to query the LDAP directory must be a member of the ldap-search group. Currently:

  • stalwart-svc — for mail server LDAP lookups
  • nextcloud-svc — for CalDAV/CardDAV user sync

Without group membership, LDAP bind fails with "Access Denied".


Verification Checklist

After recovery, verify:

 ```bash
 curl -s https://key.wilsoz.com/application/o/immich/.well-known/openid-configuration | jq '.scopes_supported'
 # Should include: ["openid", "email", "profile"]
 ```
  • [ ] JWKS endpoint has keys:
 ```bash
 curl -s https://key.wilsoz.com/application/o/immich/jwks/ | jq '.keys | length'
 # Should return a number > 0
 ```
  • [ ] LDAP directory responding:
 ```bash
 ldapsearch -H ldap://172.17.0.1:389 -D "cn=stalwart-svc,ou=users,dc=ldap,dc=goauthentik,dc=io" \
   -w <stalwart-svc-password> -b "ou=users,dc=ldap,dc=goauthentik,dc=io" "uid=*" | grep "numResponses"
 # Should return matches (or at least show successful bind)
 ```
  • [ ] Users present:
 ```bash
 docker exec authentik-app_server_1 ak shell -c "from authentik.core.models import User; print('\n'.join([u.username for u in User.objects.all()]))"
 # Should show: akadmin, marcus, stalwart-svc, nextcloud-svc
 ```
  • [ ] Groups present:
 ```bash
 docker exec authentik-app_server_1 ak shell -c "from authentik.core.models import Group; print('\n'.join([g.name for g in Group.objects.all()]))"
 # Should show all configured groups
 ```
  • [ ] OAuth providers have signing keys:
 ```bash
 docker exec authentik-app_server_1 ak shell -c "
 from authentik.providers.oauth2.models import OAuth2Provider
 for p in OAuth2Provider.objects.all():
   has_key = p.signing_key is not None
   print(f'{p.name}: signing_key={has_key}')
 "
 # All should show: signing_key=True
 ```

Dependencies

What Authentik Depends On

  • PostgreSQL 16 — must be healthy before server/worker start
  • Docker — for container runtime
  • Network access to Cloudflare DNS — for DNS resolution of key.wilsoz.com
  • VPS reverse proxy — nginx on VPS must forward key.wilsoz.com:443 to stronghold:9000

What Depends On Authentik

  • All OAuth2 consumers: Immich, Memos, Linkwarden, Forgejo, Vaultwarden, Grafana, n8n, Matrix
  • All LDAP consumers: Nextcloud, Jellyfin, Stalwart (mail server)
  • Cloudflare Access: For gating n8n form URLs
  • Email provisioning workflow (n8n): Watches Authentik groups for email user provisioning
  • Nginx reverse proxy: Serves key.wilsoz.com → Authentik

Operations

Start/Stop/Restart

<syntaxhighlight lang="bash"> cd /library/authentik-app

  1. Start

docker compose up -d

  1. Stop

docker compose down

  1. Restart

docker compose restart server worker

  1. Full restart (clears caches)

docker compose down && sleep 2 && docker compose up -d </syntaxhighlight>

View Logs

<syntaxhighlight lang="bash">

  1. Server logs

docker logs -f server

  1. Worker logs (for background tasks)

docker logs -f worker

  1. PostgreSQL logs (for DB issues)

docker logs postgresql

  1. All together

docker compose logs -f </syntaxhighlight>

Access Django Shell (Advanced)

For one-off admin tasks:

<syntaxhighlight lang="bash"> docker exec authentik-app_server_1 ak shell

  1. Example: List all users

from authentik.core.models import User for u in User.objects.all():

   print(f"{u.username}: {u.email}, groups: {list(u.groups.values_list('name', flat=True))}")
  1. Example: Create API token

from authentik.core.models import Token t = Token.objects.create(user=User.objects.get(username='marcus'), identifier='my-api-key') print(f"Token: {t.key}")

  1. Exit: Ctrl+D

</syntaxhighlight>

Backup PostgreSQL Manually

<syntaxhighlight lang="bash">

  1. Full database dump

docker exec authentik-app_postgresql_1 pg_dump -U authentik authentik | \

 gzip > /home/mnw/backups/authentik-manual-$(date +%Y%m%d-%H%M%S).sql.gz
  1. Or via Restic (for Hetzner backup)

cd /library/backup-stack && docker compose exec restic-backup bash -c "

 export RESTIC_REPOSITORY=\$HETZNER_REPO
 export RESTIC_PASSWORD=\$HETZNER_PASSWORD
 # Make dump available in /data
 docker exec authentik-app_postgresql_1 pg_dump -U authentik authentik | gzip > /data/dumps/authentik-manual.sql.gz
 restic backup /data/dumps/

" </syntaxhighlight>


Troubleshooting

Can't log into admin UI

Symptoms: 403 Forbidden, or password rejected

Fixes:

  1. Check if server is running: docker ps | grep server
  2. Reset akadmin password via Django shell (see Operations section)
  3. Check logs: docker logs server | grep -i "error\|auth"

OAuth login fails with "invalid_grant"

Symptoms: Client authentication failed, or code exchange error

Causes & fixes:

  • Client secret mismatch: Verify in Authentik provider matches app's .env
  • Provider deleted: Recreate OAuth provider and update app config
  • Redirect URI mismatch: Ensure app's callback URL matches provider's redirect URIs exactly

Debug: <syntaxhighlight lang="bash"> docker logs server | grep -i "oauth\|invalid_grant" </syntaxhighlight>

LDAP bind fails

Symptoms: Stalwart/Nextcloud can't authenticate users

Fixes:

  1. Verify LDAP outpost is running: docker ps | grep outposts
  2. Check user is in ldap-search group: Admin UI → Directory → Groups → ldap-search
  3. Test bind from stronghold:
  ```bash
  ldapsearch -H ldap://172.17.0.1:389 -D "cn=stalwart-svc,ou=users,dc=ldap,dc=goauthentik,dc=io" \
    -w <password> -b "ou=users" "uid=marcus"
  ```

Worker not picking up changes

Symptoms: Group membership not reflected, or other background tasks stuck

Fix: <syntaxhighlight lang="bash"> docker compose restart worker docker logs worker --tail=50 </syntaxhighlight>

Database corruption

Symptoms: PostgreSQL won't start, or "ERROR: relation does not exist"

Fix:

  • Try recovery from backup (Disaster Mode B)
  • If no backup: full reset (Disaster Mode A) — all data lost

Links & References


Related Services

  • OpenBao (secrets store): /library/openbao-app/ — stores API tokens
  • Stalwart (mail server): Uses LDAP for user lookups
  • Nextcloud (file sync): Uses LDAP for CalDAV/CardDAV
  • n8n (automation): Uses OAuth for form access control
  • PostgreSQL (database): Internal to Authentik
  • Nginx (reverse proxy): VPS nginx forwards key.wilsoz.com traffic here