Possible missing configuration OAuth2-Proxy - Traefik

Hello,

I just migrated from NPM to Traefik since it is more customisable and authentication friendly.

Also a lot more clear in terms of where configs can be made and no mix of UI + config files.

However, I still have a slight issue setting up authentication with OAuth2-Proxy as middleware between Traefik and Keycloak.

The whole setup works, but when a service is being visited when you are not authenticated, the webpage shows a blank page with a clickable text ‘Found’ which directs to the Keycloak login page.

I struggled setting up the whole authentication flow but all is fine now except this last part.

When I click that link and login to Keycloak, I get redirected fine to the service with the correct cookies and states.

So the only missing step, is probably a configuration that should redirect to the login in the first place without manual interaction.

According to the OAuth2-Proxy logs, there is a 401 returned with the forwardAuth (expected) and a 302 redirect but Traefik only sees a 401 with the Location header to the Keycloak login page and a html content representing the blank page with the ‘Found’ text.

Where am I screwing up? :slight_smile:

Note:

I am not running this in docker containers. I am running a Proxmox host with an LXC container for each service.

So

  • LXC 1 - Traefik - Linux Service
  • LXC 2 - OAuth2-Proxy - Linux Service
  • LXC 3 - Keycloak - Linux Service
  • LXC 4 - Service 1 - Linux Service

I do not have Docker compose files and all is done with the regular config files.

OAuth2-Proxy Logs:

x.x.x.x - 7bb0fbe7-5f05-4fdd-9e4f-d2dea7685605 - - [2025/10/25 11:08:27] oauthproxy.domain.com GET - "/oauth2/auth" HTTP/1.1 "curl/8.14.1" 401 13 0.000
x.x.x.x - b264f7b7-38cc-4fb7-897d-3548d375dda6 - - [2025/10/25 11:08:27] service.domain.com GET - "/oauth2/sign_in?rd=https%3A%2F%2Fservice.domain.com%2F" HTTP/1.1 "curl/8.14.1" 302 455 0.000

Traefik Logs

{"level":"debug","middlewareName":"oauth-auth@file","middlewareType":"ForwardAuth","time":"2025-10-25T11:16:56+02:00","caller":"github.com/traefik/traefik/v3/pkg/middlewares/auth/forward.go:236","message":"Remote error https://oauthproxy.domain.com/oauth2/auth. StatusCode: 401"}

{"level":"debug","middlewareName":"oauth-errors@file","middlewareType":"CustomError","time":"2025-10-25T11:16:56+02:00","caller":"github.com/traefik/traefik/v3/pkg/middlewares/customerrors/custom_errors.go:121","message":"Caught HTTP Status Code 401, returning error page"}

OAuth2-Proxy Config

################################################################################
# OAuth2-Proxy Configuration (system service in LXC)
# Works with Keycloak (OIDC) and a shared proxy for multiple subdomains
################################################################################

# ---------------------------
# Core HTTP settings
# ---------------------------
http_address            = "0.0.0.0:4180"       # Port OAuth2-Proxy listens on
reverse_proxy           = true                 # Expect headers from Traefik
#real_client_ip_header   = "X-Forwarded-For"

# ---------------------------
# Provider: Keycloak (OIDC)
# ---------------------------
provider              = "keycloak-oidc"
oidc_issuer_url       = "https://keycloak.domain.com/realms/domain"
login_url             = "https://keycloak.domain.com/realms/domain/protocol/openid-connect/auth"
redeem_url            = "https://keycloak.domain.com/realms/domain/protocol/openid-connect/token"
validate_url          = "https://keycloak.domain.com/realms/domain/protocol/openid-connect/userinfo"
scope                 = "openid email profile"
code_challenge_method = "S256"
whitelist_domains     = [".domain.com", "domain.com", "*.domain.com"]

# Keycloak "Web (OIDC)" application
client_id             = "oauth2-proxy"
client_secret         = "xxx"

# Where Keycloak redirects users back to after login
redirect_url          = "https://oauthproxy.domain.com/oauth2/callback"
proxy_prefix          = "/oauth2"

# ---------------------------
# Cookie/session settings
# ---------------------------
cookie_name         = "_oauth2_proxy"
cookie_secret       = "xxx"
cookie_secure       = true
cookie_httponly     = true
cookie_expire       = "12h"
cookie_refresh      = "1h"
cookie_domains      = [".domain.com", "domain.com", "*.domain.com"]
cookie_samesite     = "lax"

# ---------------------------
# Upstream behavior
# ---------------------------
# We’re not using OAuth2-Proxy to forward to any backend directly;
# Traefik handles the real apps.
upstreams = ["static://202"]

# ---------------------------
# Authorization & access
# ---------------------------
email_domains               = ["*"]
pass_access_token           = true
pass_user_headers           = true
pass_authorization_header   = true
set_xauthrequest            = true
ssl_insecure_skip_verify    = true
skip_provider_button        = true

# ---------------------------
# Logging
# ---------------------------
standard_logging      = true
request_logging       = true
auth_logging          = true
logging_max_size      = 50
logging_max_age       = 30
logging_max_backups   = 3
logging_filename      = "oauth-proxy_customlogfile"
show_debug_on_error   = true

Traefik Config - Middlewares + OAuth"2-Proxy

http:
  middlewares:          
    security-headers:
      headers:
        frameDeny: true
        sslRedirect: true
        stsIncludeSubdomains: true 
        stsSeconds: 63072000
        forceSTSHeader: true
        
    auth-headers:
      headers:
        sslRedirect: true
        stsSeconds: 315360000
        browserXssFilter: true
        contentTypeNosniff: true
        forceSTSHeader: true
        sslHost: domain.com
        stsIncludeSubdomains: true
        stsPreload: true
        frameDeny: true
        customRequestHeaders:
          X-Forwarded-Proto: "https"
          X-Forwarded-Ssl: "on"
        
    # Forward auth middleware that asks oauth2-proxy if request is authenticated
    oauth-auth:
      forwardAuth:
        address: "https://oauthproxy.domain.com/oauth2/auth"
        trustForwardHeader: true

    # Errors middleware: on 401/403 redirect to oauth2-proxy sign_in (preserves requested URL)
    oauth-errors:
      errors:
        status:
          - "401-403"
        service: oauthproxy
        query: "/oauth2/sign_in?rd={url}"


  routers:
    oauthproxy:
      rule: "Host(`oauthproxy.domain.com`) || PathPrefix(`/oauth2/`)"
      entryPoints:
        - websecure
      service: oauthproxy
      middlewares:
        - auth-headers
      tls:
        certResolver: letsencrypt
        domains:
        - main: "domain.com"
          sans:
            - "*.domain.com"

  services:
    oauthproxy:
      loadBalancer:
        servers:
          - url: "http://192.168.0.21:4180"

Traefik Config - Service1

http:
  routers:
    service1:
      rule: "Host(`service1.domain.com`)"
      entryPoints:
        - websecure
      service: service1
      middlewares:
        - oauth-errors
        - oauth-auth
      tls:
        certResolver: letsencrypt
        domains:
        - main: "domain.com"
          sans:
            - "*.domain.com"
        
  services:
    service1:
      loadBalancer:
        servers:
          - url: "http://192.168.0.106:80"

When i curl to the service page, I get this response showing a 401 with the location it should redirect to

curl -I https://service1.domain.com => 
HTTP/2 401 
date: Sat, 25 Oct 2025 09:36:37 GMT
content-type: text/html; charset=utf-8
location: https://keycloak.domain.com/realms/domain/protocol/openid-connect/auth?approval_prompt=force&client_id=oauth2-proxy&code_challenge=challenge&code_challenge_method=S256&redirect_uri=https%3A%2F%2Foauthproxy.domain.com%2Foauth2%2Fcallback&response_type=code&scope=openid+email+profile&state=state%3Ahttps%3A%2F%2Fservice1.domain.com%2F
cache-control: no-cache, no-store, must-revalidate, max-age=0
expires: Thu, 01 Jan 1970 01:00:00 CET
nel: {"report_to":"cf-nel","success_fraction":0.0,"max_age":604800}
cf-cache-status: DYNAMIC
report-to: {"group":"cf-nel","max_age":604800,"endpoints":[{"url":"https://a.nel.cloudflare.com/report/v4?s=xxx"}]}
server: cloudflare
set-cookie: _oauth2_proxy_csrf=cookie; HttpOnly; SameSite=Lax; Secure; Path=/; Domain=domain.com; Max-Age=900
cf-ray: 9940bb07fd34ba63-BRU
alt-svc: h3=":443"; ma=86400

I am new to this so I am sure I am doing something stupidly small wrong, but hopefully someone can help me out on this :slight_smile:

The default documentation of both Traefik and OAuth2-Proxy did not help me so I am relying on help of the community.

Thank you in advance!