Protect Internal Sites With OAuth2 Proxy and Google Auth

OAuth2 Proxy with Google OAuth: create Google credentials, install NGINX, run OAuth2 Proxy, and use auth_request and Let's Encrypt TLS to protect internal apps.

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_request to 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.

  1. A browser requests https://auth.example.com/.
  2. NGINX sends an auth_request subrequest to OAuth2 Proxy (/oauth2/auth).
  3. No valid session cookie: OAuth2 Proxy returns 401 and NGINX shows OAuth2 Proxy’s sign-in page, which sends the user to Google.
  4. After sign-in, OAuth2 Proxy sets a session cookie; the next subrequest returns 202 and 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 with apt).
  • 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

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).

  1. Open Google Cloud Console and select (or create) a project.
  2. Go to APIs & Services → OAuth consent screen. Configure the consent screen if you haven’t (e.g. User type: External, App name, support email).
  3. Go to APIs & Services → Credentials → Create Credentials → OAuth client ID.
  4. Application type: Web application. Give it a name (e.g. “OAuth2 Proxy”).
  5. Authorized JavaScript origins: https://auth.example.com (replace with your OAuth2 Proxy host).
  6. Authorized redirect URIs: https://auth.example.com/oauth2/callback (must match exactly).
  7. 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.

comments powered by Disqus
Citizix Ltd
Built with Hugo
Theme Stack designed by Jimmy