How to Use OAuth2-Proxy: A Beginner's Guide to Adding SSO to Any App
CodexFuse Team · 2026-11-01T17:08:35.000Z · 11 min read
Learning how to use OAuth2-Proxy takes one focused session, and by the end you will have a working authentication gate in front of an app that has no login code of its own. This oauth2 proxy tutorial is the missing beginner arc: the mental-model lesson that makes everything else click, a first Docker run with the cookie-secret rule, your first provider (Google, GitHub, or Keycloak), the callback-URL rule that causes nearly every beginner failure, the two deployment modes, and the who-gets-in rules with session storage. One lane line first, drawn once and never blurred: if your need is a full identity provider with its own login page, that is our Authentik beginner guide — Authentik is the IdP behind the gate. OAuth2-Proxy is the gate itself: it performs the OAuth2 dance with any provider, and your app just sees authenticated requests.
What Is OAuth2-Proxy, Exactly?
OAuth2-Proxy is a free, open-source (MIT, CNCF Sandbox) reverse proxy that adds authentication to any app without touching the app's code. You point it at your app as an upstream, you register an OAuth2 client with a provider like Google, GitHub, or Keycloak, and from then on every visitor must log in with that provider before a single request reaches your app. The current stable release is v7.15.5 (published October 1, 2026), and the official Docker image is quay.io/oauth2-proxy/oauth2-proxy — not the old docker.io forks, which are dead remnants of the archived 2018 bitly project.
The login flow is the same every time, and knowing it saves you hours of debugging:
- An anonymous request arrives; OAuth2-Proxy finds no valid session cookie and redirects the browser to
/oauth2/start, which remembers the originally requested URL. - The browser is sent to the provider's login page with OAuth2-Proxy's client ID.
- The user logs in and approves; the provider redirects back to
/oauth2/callbackwith an authorization code. - OAuth2-Proxy exchanges the code for tokens, fetches the user's email, and sets an encrypted session cookie.
- The browser is returned to the original URL, and the request is forwarded to your app with identity headers attached.
How Do You Run OAuth2-Proxy for the First Time?
Docker Compose is the fastest first run, and our Docker beginner guide covers the tooling if you are new to it. This oauth2 proxy docker arc uses the whoami test app as the upstream, so you see the gate working before you point it at anything real.
First, the cookie-secret rule — said loudly and exactly once: the cookie secret must decode to exactly 16, 24, or 32 bytes (AES key sizes), and a wrong-length secret is one of the classic causes of an infinite auth loop. Generate it with the command from the official docs:
python -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())'
Then create an env file. Modern OAuth2-Proxy is configured with OAUTH2_PROXY_ environment variables — flags beat env vars, env vars beat the config file, and repeatable flags take plural env names (--email-domain becomes OAUTH2_PROXY_EMAIL_DOMAINS).
# oauth2-proxy.env
OAUTH2_PROXY_PROVIDER=google
OAUTH2_PROXY_CLIENT_ID=your-client-id.apps.googleusercontent.com
OAUTH2_PROXY_CLIENT_SECRET=your-client-secret
OAUTH2_PROXY_COOKIE_SECRET=paste-the-32-byte-secret-here
OAUTH2_PROXY_REDIRECT_URL=https://internal.example.com/oauth2/callback
OAUTH2_PROXY_EMAIL_DOMAINS=example.com
OAUTH2_PROXY_UPSTREAMS=http://whoami:80/
OAUTH2_PROXY_HTTP_ADDRESS=0.0.0.0:4180
# docker-compose.yml
services:
whoami:
image: traefik/whoami
oauth2-proxy:
image: quay.io/oauth2-proxy/oauth2-proxy:v7.15.5
ports:
- "4180:4180"
env_file:
- oauth2-proxy.env
Run docker compose up, open http://localhost:4180, and you will be bounced to the provider login before whoami ever loads. That first successful gate is the whole product: everything after this is refining which provider, which users, and which deployment mode.
Which Provider Should You Pick First?
Start with whatever identity your users already have. Google is the zero-friction choice for most teams, GitHub for developer tools, and Keycloak when you want to self-host the identity side too.
| Provider | What to create | Callback URL | Restricting users |
|---|---|---|---|
| oauth2 proxy google | GCP console → OAuth client ID → Application type: Web application | https://internal.example.com/oauth2/callback in Authorized redirect URIs | OAUTH2_PROXY_EMAIL_DOMAINS=example.com (or *) |
| oauth2 proxy github | An OAuth App at github.com/settings/developers — NOT a GitHub App | Authorization callback URL field | --github-org, --github-team (org:team slugs), --github-repo |
| oauth2 proxy keycloak | Confidential OpenID Connect client; Valid Redirect URIs set; group-membership mapper for groups | OAUTH2_PROXY_REDIRECT_URL must match Keycloak's Valid Redirect URIs | --allowed-group / --allowed-role; OAUTH2_PROXY_OIDC_ISSUER_URL points at your realm |
The Google provider is the default (OAUTH2_PROXY_PROVIDER=google is optional), and the "Web application" client type is the detail beginners miss — the other client types will not accept a server-side callback. For GitHub, the OAuth-App-versus-GitHub-App distinction is load-bearing: OAuth2-Proxy needs the classic OAuth App, and our GitHub beginner guide covers the GitHub side of that console. For Keycloak, use --provider=keycloak-oidc with your realm's issuer URL; OAUTH2_PROXY_PROVIDER_DISPLAY_NAME controls the label on the sign-in page.
Why Does the Callback URL Have to Match Exactly?
The callback rule is the single biggest beginner trap, so here it is said once and exactly: the redirect_uri OAuth2-Proxy sends to the provider must match a registered redirect URI on the provider's app character-for-character — scheme, host, port, path (/oauth2/callback), letter case, and trailing slash. Google answers a mismatch with Error 400: redirect_uri_mismatch; most other providers just produce an infinite login loop, the exact symptom that also comes from a bad cookie secret.
The debugging order that actually works: first compare OAUTH2_PROXY_REDIRECT_URL against the provider's registered URI byte for byte (the trailing slash is the usual culprit), then verify the cookie secret decodes to 16, 24, or 32 bytes, then check that the URL you are visiting in the browser is the same host you registered — testing on http://localhost:4180 while the provider expects https://internal.example.com will loop forever.
Reverse Proxy or nginx auth_request: Which Deployment Mode?
OAuth2-Proxy has two modes, and the difference is one line: in reverse-proxy mode your traffic passes through OAuth2-Proxy, which authenticates and forwards it to the upstream; in oauth2 proxy nginx mode, nginx stays in front and merely asks OAuth2-Proxy whether each request is allowed. Pick the mode that matches what you already run.
| Reverse-proxy mode | nginx auth_request (subrequest) mode | |
|---|---|---|
| Traffic path | Browser → oauth2-proxy → your app | Browser → nginx → your app; nginx asks oauth2-proxy per request |
| Config | --upstream=http://app:8080/ (repeatable; longest path prefix wins) | auth_request /oauth2/auth; + error_page 401 = /oauth2/sign_in; |
| Behind another proxy | Set --reverse-proxy so X-Forwarded-Proto/Host/Uri drive redirects; add --trusted-proxy-ip for the proxy's CIDR | Not applicable — nginx is the edge |
| Identity to the app | Headers on the forwarded request | --set-xauthrequest passes X-Auth-Request-User, -Email, -Groups |
| Best for | New setups, Docker Compose stacks, Kubernetes sidecars | Sites already running nginx as the edge |
The nginx pattern from the official docs looks like this:
location = /oauth2/auth {
proxy_pass http://127.0.0.1:4180;
}
server {
server_name internal.example.com;
location /oauth2/ {
proxy_pass http://127.0.0.1:4180;
}
location / {
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
proxy_pass http://127.0.0.1:8080;
}
}
OAuth2-Proxy only answers 202 or 401 on /oauth2/auth — it never proxies the app in this mode, which keeps the failure surface small. If you run Caddy instead of nginx, its forward_auth directive is the equivalent pattern; our Caddy beginner guide covers that side of the world. On Kubernetes, the common shape is OAuth2-Proxy as a sidecar or a dedicated auth service in front of in-cluster apps — the oauth2 proxy kubernetes story is the same gate, just deployed as pods.
Who Gets In? Email Domains, Groups, and Session Storage
Authentication proves who someone is; these settings decide whether they are allowed through the gate at all. --email-domain takes a domain (repeatable for several, * for anyone with an email); --authenticated-emails-file takes a file with one email per line for a tight allowlist; --allowed-group gates on provider groups (with generic OIDC it implicitly adds the groups scope, and --oidc-groups-claim sets which claim to read, defaulting to groups).
Sessions default to cookie storage — the session data rides encrypted inside the cookie itself, so there is nothing to operate. If you run multiple replicas behind a load balancer, switch to Redis with OAUTH2_PROXY_SESSION_STORE_TYPE=redis and a connection URL, which keeps sessions server-side with only a ticket in the cookie. Either way, HTTPS is non-negotiable in production: cookies carrying sessions over plain HTTP is how gates get bypassed. Putting this on a public server? Our VPS hosting guide explains the server side.
OAuth2-Proxy vs Authelia: Which Gate Do You Need?
This oauth2 proxy vs authelia comparison gets one section because the lanes are clean. OAuth2-Proxy is a reverse-proxy auth gate that delegates to any identity provider — Google, GitHub, Keycloak, or your own Authentik. Authelia is an authentication provider with its own user database that apps talk to via forward-auth; it belongs in Authentik's IdP sibling lane, not in OAuth2-Proxy's lane. Use OAuth2-Proxy when you want to bolt SSO onto apps with zero code changes and you already have (or will get) an IdP; use Authelia when you want the IdP itself to be the self-hosted user store.
7 Beginner Mistakes to Avoid
| Mistake | Why it hurts | The fix |
|---|---|---|
Pulling oauth2_proxy from docker.io | Those are dead forks of the archived 2018 bitly project — wrong code, no updates | Always quay.io/oauth2-proxy/oauth2-proxy; pin the version tag |
| A cookie secret of the wrong length | Must be exactly 16, 24, or 32 bytes — anything else loops | Generate with the documented command; verify the byte length |
| Callback URL off by one character | Provider rejects or loops; scheme, case, and trailing slash all count | Copy the registered URI into OAUTH2_PROXY_REDIRECT_URL verbatim |
| Creating a GitHub App instead of an OAuth App | OAuth2-Proxy speaks classic OAuth — a GitHub App will never complete the dance | Use github.com/settings/developers OAuth Apps |
| Picking the wrong Google client type | Only "Web application" accepts a server-side callback URI | Choose Web application in the GCP console, register the exact redirect URI |
| Running the gate over plain HTTP | Session cookies travel readable — the gate becomes decoration | Terminate TLS at the edge from day one |
| Forgetting the app sees no login | Your app receives identity headers, not a session — trusting raw headers from the internet is a hole | Only accept identity headers from the proxy; bind upstreams to localhost or private networks |
Sources
Every fact in this guide was verified against live sources on November 1, 2026: the GitHub API for the oauth2-proxy releases (v7.15.5 stable, published October 1, 2026), the project's master CHANGELOG and README (release line, MIT license, quay.io as the canonical image registry from v7.6.0 with the distroless base, repository history: forked from bitly 2018-11-27, renamed from pusher 2020-03-29), the official Configuration Overview (flag > env var > config-file precedence, OAUTH2_PROXY_ naming, the 16/24/32-byte cookie-secret rule with the Python generation command, upstream and reverse-proxy options), the official Google provider docs (GCP console OAuth client ID, "Web application" type, authorized redirect URI), the official GitHub provider docs (OAuth App, not GitHub App; org/team/repo restriction flags), the official Keycloak/OIDC provider docs (keycloak-oidc, issuer URL, valid redirect URIs, group membership mapper, allowed-role), the official nginx auth_request configuration guide (/oauth2/auth subrequest, 401 → /oauth2/sign_in, X-Auth-Request headers), the official Session Storage docs (cookie default, Redis via session-store-type), the official Helm installation docs (oauth2-proxy/manifests chart), plus the Bomberbot comprehensive guides (the 9-step authorization-code flow, Keycloak and Kubernetes arcs) and the Joeeey self-hosting SSO series (Nginx Proxy Manager wiring with the cookie-domains and insecure-OIDC notes).
Frequently Asked Questions
Why is OAuth2-Proxy stuck in an infinite login redirect loop?
Almost always one of two things: the redirect URI registered on the provider does not match OAUTH2_PROXY_REDIRECT_URL exactly (check scheme, host, port, path, case, and the trailing slash), or the cookie secret does not decode to exactly 16, 24, or 32 bytes. Fix the URI first, then the secret length.
How do I generate the cookie secret?
Use the documented command: python -c 'import os,base64; print(base64.urlsafe_b64encode(os.urandom(32)).decode())'. Any 16, 24, or 32-byte value works; rotating it logs every user out, so generate it once and keep it in your env file.
Which Google Cloud OAuth client type does OAuth2-Proxy need?
"Web application." Create the OAuth client ID under APIs & Services → Credentials, pick the Web application type, and put your exact https://your-domain/oauth2/callback in Authorized redirect URIs. The other client types cannot complete a server-side callback.
OAuth App or GitHub App — which one for OAuth2-Proxy?
An OAuth App, created at github.com/settings/developers — never a GitHub App. Put the callback URL in the Authorization callback URL field, then restrict with --github-org, --github-team, or --github-repo so it is not open to every GitHub user.
Should I run OAuth2-Proxy as a reverse proxy or with nginx auth_request?
Use reverse-proxy mode when OAuth2-Proxy is the edge: traffic flows through it via --upstream flags. Use nginx auth_request when nginx is already your edge: nginx asks OAuth2-Proxy for a 202/401 on /oauth2/auth per request and never sends it your app traffic.
One focused session is all it takes: generate a proper cookie secret, register one provider, match the callback URL exactly, and put the gate in front of one app. After that, every other app you own gets SSO for the price of one more upstream line.