OAuth2 Proxy is a reverse proxy that adds OAuth2 authentication in front of any HTTP service. Unauthenticated users are redirected to the provider (e.g. Google); after sign-in, they can access the upstream app. In this guide you set up OAuth2 Proxy with Google OAuth 2.0 in front of an internal app (we use Gatus as the example; you can use any service on a local port). Traffic flows: NGINX asks OAuth2 Proxy whether each request is signed in (auth_request), then proxies it to your app (e.g. Gatus on port 8080).
In this guide you’ll:
- Create Google OAuth 2.0 credentials (client ID and secret) and set the redirect URI
- Install NGINX, Docker, Certbot, and the OAuth2 Proxy binary
- Run an example internal app (Gatus) with Docker Compose, then configure OAuth2 Proxy and a systemd service
- Configure NGINX with
auth_requestto enforce sign-in and obtain a Let’s Encrypt certificate
Related: OAuth2 Proxy for central authentication in Kubernetes
Does your app need to support OIDC? No
This pattern is how you put single sign-on in front of a legacy application that has no OAuth or OIDC support of its own. The app is not modified at all: OAuth2 Proxy talks OIDC to Google, and NGINX only lets a request through once OAuth2 Proxy says the user is signed in.
- A browser requests
https://auth.example.com/. - NGINX sends an
auth_requestsubrequest to OAuth2 Proxy (/oauth2/auth). - No valid session cookie: OAuth2 Proxy returns
401and NGINX shows OAuth2 Proxy’s sign-in page, which sends the user to Google. - After sign-in, OAuth2 Proxy sets a session cookie; the next subrequest returns
202and NGINX proxies the request to the app.
The one requirement is that the app is only reachable through NGINX — bind it to 127.0.0.1 (or a private network) so nobody can go around the proxy. If the app can log users in from a trusted header, OAuth2 Proxy can pass the signed-in user’s email to it; see the FAQ.
Prerequisites
- A Linux server (this guide uses RHEL/Rocky/Alma with
dnf; adapt for Ubuntu/Debian withapt). - A domain name pointing to the server (e.g.
auth.example.com) for NGINX and Google redirect URI. - A Google Cloud project and access to Google Cloud Console.
Table of contents
- Does your app need to support OIDC? No
- Prerequisites
- Table of contents
- 1. Google OAuth 2.0 setup
- 2. Update server and install packages
- 3. Set up Docker and Docker Compose
- 4. Example: Run an internal app to protect (Gatus)
- 5. Set up OAuth2 Proxy
- 6. Configure NGINX and TLS
- Frequently Asked Questions (FAQ)
- Conclusion
1. Google OAuth 2.0 setup
You need a Google OAuth 2.0 Client ID and Client Secret. Use the same domain you will use for OAuth2 Proxy (e.g. https://auth.example.com).
- Open Google Cloud Console and select (or create) a project.
- Go to APIs & Services → OAuth consent screen. Configure the consent screen if you haven’t (e.g. User type: External, App name, support email).
- Go to APIs & Services → Credentials → Create Credentials → OAuth client ID.
- Application type: Web application. Give it a name (e.g. “OAuth2 Proxy”).
- Authorized JavaScript origins:
https://auth.example.com(replace with your OAuth2 Proxy host). - Authorized redirect URIs:
https://auth.example.com/oauth2/callback(must match exactly). - Create the credential and copy the Client ID and Client secret; you’ll use them in the OAuth2 Proxy config.
2. Update server and install packages
Update the system and install NGINX, Docker, Certbot (for Let’s Encrypt), and unzip:
sudo dnf -y update
sudo dnf install -y nginx docker certbot python3-certbot-nginx unzip
3. Set up Docker and Docker Compose
Start and enable Docker (required for the example Gatus app; skip if you already use another upstream):
sudo systemctl start docker
sudo systemctl enable docker
Confirm docker is running by checking its status.
$ sudo systemctl status docker
â—Ź docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; enabled; preset: disabled)
Active: active (running) since Sun 2025-03-16 09:20:54 UTC; 33s ago
TriggeredBy: â—Ź docker.socket
Docs: https://docs.docker.com
Main PID: 207133 (dockerd)
Tasks: 8
Memory: 31.2M
CPU: 386ms
CGroup: /system.slice/docker.service
└─207133 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock --default-ulimit nofile=32768:65536
Mar 16 09:20:53 auth.citizix.com systemd[1]: Starting docker.service - Docker Application Container Engine...
Mar 16 09:20:53 auth.citizix.com dockerd[207133]: time="2025-03-16T09:20:53.818123871Z" level=info msg="Starting up"
Mar 16 09:20:53 auth.citizix.com dockerd[207133]: time="2025-03-16T09:20:53.893991019Z" level=info msg="Loading containers: start."
Mar 16 09:20:54 auth.citizix.com dockerd[207133]: time="2025-03-16T09:20:54.475192203Z" level=info msg="Loading containers: done."
Mar 16 09:20:54 auth.citizix.com dockerd[207133]: time="2025-03-16T09:20:54.506292402Z" level=info msg="Docker daemon" commit=71907ca containerd-snapshotter=false storage-driver=overlay2 version=>
Mar 16 09:20:54 auth.citizix.com dockerd[207133]: time="2025-03-16T09:20:54.506447357Z" level=info msg="Daemon has completed initialization"
Mar 16 09:20:54 auth.citizix.com dockerd[207133]: time="2025-03-16T09:20:54.598889450Z" level=info msg="API listen on /run/docker.sock"
Mar 16 09:20:54 auth.citizix.com systemd[1]: Started docker.service - Docker Application Container Engine.
Add your user to the docker group so you can run Docker without root (replace ec2-user with your username):
sudo usermod -aG docker ec2-user
Install Docker Compose (check releases for the latest version):
sudo curl -L https://github.com/docker/compose/releases/download/v2.33.1/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose -v
4. Example: Run an internal app to protect (Gatus)
We use Gatus (a health-check dashboard) as the upstream app. OAuth2 Proxy will sit in front of it; you can replace Gatus with any service listening on a local port (e.g. 8080).
Create the config directory and a minimal Gatus config:
sudo mkdir -p /etc/gatus
sudo chown -R $USER:$USER /etc/gatus
Create /etc/gatus/config.yml:
metrics: true
storage:
type: sqlite
path: data.db
endpoints:
- name: example-healthcheck
url: https://example.com/
interval: 1m
conditions:
- "[STATUS] == 200"
Create /etc/gatus/docker-compose.yml:
services:
gatus:
image: twinproduction/gatus:latest
container_name: gatus
restart: always
volumes:
- "/etc/gatus/config.yml:/config/config.yaml"
ports:
- "127.0.0.1:8080:8080"
Publish the port on 127.0.0.1 only. A bare "8080:8080" listens on every interface, so anyone who can reach http://your-server:8080 gets the app without signing in — NGINX and OAuth2 Proxy are simply skipped. Docker publishes ports by writing its own iptables rules, so a host firewall such as firewalld does not reliably block a port published on all interfaces either.
Start Gatus:
docker-compose -f /etc/gatus/docker-compose.yml up -d
docker-compose -f /etc/gatus/docker-compose.yml ps
The app now listens on 127.0.0.1:8080 only, and NGINX proxies to it once OAuth2 Proxy has authenticated the user. Check that nothing else is exposed: sudo ss -tlnp | grep 8080 should show 127.0.0.1:8080, not 0.0.0.0:8080.
5. Set up OAuth2 Proxy
Download the OAuth2 Proxy binary (see releases for newer versions):
curl -L https://github.com/oauth2-proxy/oauth2-proxy/releases/download/v7.8.1/oauth2-proxy-v7.8.1.linux-amd64.tar.gz -o /tmp/oauth2-proxy.tar.gz
tar -xzvf /tmp/oauth2-proxy.tar.gz -C /tmp
sudo mv /tmp/oauth2-proxy-v7.8.1.linux-amd64/oauth2-proxy /usr/local/bin/oauth2_proxy
Create the config file /etc/oauth2_proxy.cfg. Generate a cookie secret with openssl rand -base64 32 and replace placeholders with your Google Client ID, Client Secret, domain, and upstream (e.g. http://127.0.0.1:8080 for Gatus):
provider = "google"
client_id = "YOUR_GOOGLE_CLIENT_ID"
client_secret = "YOUR_GOOGLE_CLIENT_SECRET"
cookie_secret = "GENERATE_WITH_openssl_rand_-base64_32"
redirect_url = "https://auth.example.com/oauth2/callback"
email_domains = ["*"]
cookie_domains = ".example.com"
http_address = "127.0.0.1:4180"
upstreams = ["http://127.0.0.1:8080"]
Restrict sign-in to a single domain (e.g. your company): set email_domains = ["example.com"]. Use * to allow any Google account.
Create a systemd unit /etc/systemd/system/oauth2_proxy.service (replace ec2-user with the user that should run the proxy):
[Unit]
Description=OAuth2 Proxy Service
After=network.target
[Service]
ExecStart=/usr/local/bin/oauth2_proxy --config=/etc/oauth2_proxy.cfg
Restart=always
User=ec2-user
Group=ec2-user
[Install]
WantedBy=multi-user.target
Reload systemd, start and enable the service:
sudo systemctl daemon-reload
sudo systemctl start oauth2_proxy
sudo systemctl enable oauth2_proxy
sudo systemctl status oauth2_proxy
You should see active (running) and a log line like mapping path "/" => upstream "http://127.0.0.1:8080". For debugging: sudo journalctl -fu oauth2_proxy.
6. Configure NGINX and TLS
NGINX will terminate TLS, use auth_request to enforce OAuth2 Proxy sign-in, and proxy /oauth2/ to OAuth2 Proxy and / to your upstream.
Create /etc/nginx/conf.d/auth.conf (replace auth.example.com with your domain):
server {
listen 80;
server_name auth.example.com;
location /oauth2/ {
proxy_pass http://127.0.0.1:4180;
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;
}
location / {
auth_request /oauth2/auth;
error_page 401 = /oauth2/sign_in;
proxy_pass http://127.0.0.1:8080;
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;
}
}
Reload NGINX and obtain a Let’s Encrypt certificate:
sudo systemctl restart nginx
sudo certbot --nginx --non-interactive --agree-tos --email [email protected] -d auth.example.com
Visit https://auth.example.com. You should be redirected to Google to sign in; after authentication you’ll see the upstream app (e.g. Gatus).
Frequently Asked Questions (FAQ)
What is OAuth2 Proxy?
OAuth2 Proxy is a reverse proxy that adds OAuth2/OIDC login in front of HTTP services. Unauthenticated requests get redirected to the provider (e.g. Google); after login, the proxy sets a cookie and forwards requests to the upstream. It supports many providers (Google, GitHub, Azure AD, etc.).
Why use auth_request with NGINX?
auth_request runs a subrequest to /oauth2/auth for each request. If it returns 401, NGINX serves error_page 401 = /oauth2/sign_in, which sends the user to the OAuth2 Proxy sign-in page. This keeps authentication logic in OAuth2 Proxy and NGINX only enforces it.
How do I restrict sign-in to my organization?
Set email_domains in the OAuth2 Proxy config to your domain (e.g. ["example.com"]) so only Google accounts from that domain can sign in. For Google Workspace you can also use google_admin_email and google_group to restrict by group.
Can I use a different upstream than Gatus?
Yes. In this setup NGINX proxies to the app, so change proxy_pass in the location / block of /etc/nginx/conf.d/auth.conf (e.g. proxy_pass http://127.0.0.1:3000;) and reload NGINX. OAuth2 Proxy only answers the auth_request subrequests, so its upstreams value is not used for normal traffic. You only change upstreams in /etc/oauth2_proxy.cfg if you run OAuth2 Proxy as the reverse proxy itself (NGINX passes everything to port 4180 instead of using auth_request). Either way, keep the app bound to 127.0.0.1 or a private address so it cannot be reached directly.
How do I pass the signed-in user to the app?
Set set_xauthrequest = true in /etc/oauth2_proxy.cfg and restart OAuth2 Proxy. The /oauth2/auth response then carries X-Auth-Request-User and X-Auth-Request-Email. With the Google provider, the “user” value is Google’s numeric account ID, so pass the email as the identity. Add these two lines to the existing location / block, after error_page:
auth_request_set $email $upstream_http_x_auth_request_email;
proxy_set_header X-Email $email;
Many older apps support “header” or “remote user” authentication and will log the user in from such a header. Only trust it when the app is reachable exclusively through NGINX: proxy_set_header overwrites any value a client sends, but a client that reaches the app directly can set the header to anything.
Conclusion
You set up OAuth2 Proxy with Google OAuth 2.0 in front of an internal app: created Google credentials, installed NGINX and OAuth2 Proxy, ran an example app (Gatus) on port 8080, configured the proxy and systemd, and used NGINX auth_request plus Let’s Encrypt so only signed-in users can access the site. For the same pattern in Kubernetes, see OAuth2 Proxy for central authentication in Kubernetes.