Repository: oauth2-proxy/oauth2-proxy (2026-03-14T22:41:24Z)

Issue:
[Bug]: oauth2-proxy tries to obtain source ip when listening on socket
### OAuth2-Proxy Version

v7.14.3

### Provider

None

### Expected Behaviour

When listening on a socket, there is no source ip, so oauth2-proxy should no try to obtain it.

### Current Behaviour

For every request, I get `[oauthproxy.go:622] Error obtaining real IP for trusted IP list: unable to get ip and port from http.RemoteAddr (@)` in the log. 

### Steps To Reproduce

TL;DR:
1. Run oauth2-proxy like like documented [here](https://oauth2-proxy.github.io/oauth2-proxy/7.8.x/configuration/systemd_socket/)
2. Make requests
3. Check the logs 

Long version:
1. Run oauth2-proxy with env `OAUTH2_PROXY_HTTP_ADDRESS=fd:3` (should work the same with file path)
2. Make sure `OAUTH2_PROXY_REVERSE_PROXY` is not set or set to false
3. Make requests to the socket (probably using a reverse proxy like caddy or nginx)
4. Check logs
5. See errors
6. Full configurations in configuration details

### Possible Solutions

- Silence the error when trusted ips is not set
- When trusted ips is set with socket listener, give error like "Trusted ips setting doesn't work with socket listener, unless --reverse-proxy=true is set"

### Configuration details or additional information

Systemd socket:

`/etc/systemd/system/oauth2-proxy.socket`
```
[Unit]
Description=oauth2-proxy socket

[Socket]
ListenStream=/run/oauth2-proxy.sock
```

I'm running in podman with the following quadlet file:

`/etc/containers/systemd/oauth2-proxy.container`:
```
[Container]
ContainerName=oauth2-proxy
Environment=OAUTH2_PROXY_HTTP_ADDRESS=fd:3
Environment=OAUTH2_PROXY_PROVIDER=oidc
Environment=OAUTH2_PROXY_OIDC_ISSUER_URL=https://---
Environment=OAUTH2_PROXY_CLIENT_ID=---
Environment=OAUTH2_PROXY_CODE_CHALLENGE_METHOD=S256
Environment=OAUTH2_PROXY_SKIP_PROVIDER_BUTTON=true
Environment='OAUTH2_PROXY_EMAIL_DOMAINS=*'
Exec=--client-secret-file /run/secrets/client-secret --cookie-secret-file /run/secrets/cookie-secret
Image=quay.io_oauth2-proxy_oauth2-proxy:latest-alpine.image
Network=oauth2-proxy.network
Secret=oauth2-proxy-cookie-secret,type=mount,target=cookie-secret
Secret=oauth2-proxy-client-secret,type=mount,target=client-secret
AutoUpdate=registry

[Unit]
Description=Container oauth2-proxy
StartLimitIntervalSec=30
StartLimitBurst=3
[Service]
OOMPolicy=stop

[Service]
Restart=always

[Install]
WantedBy=multi-user.target
```


Discussion:
<!-- Answer -->
This is a known limitation in how oauth2-proxy handles `RemoteAddr` with socket listeners. The error occurs in the [`isTrustedIP` function](https://github.com/oauth2-proxy/oauth2-proxy/blob/e59f7c1549aeee85e96f771694ac55cf8a594edb/oauthproxy.go#L613-L632) which calls `net.SplitHostPort()` expecting a "host:port" format, but socket connections provide `@` instead.

There's actually [special handling for unix sockets when `req.RemoteAddr == "@"`](https://github.com/oauth2-proxy/oauth2-proxy/blob/e59f7c1549aeee85e96f771694ac55cf8a594edb/oauthproxy.go#L614-L616), which should auto-trust such connections when no explicit trusted IP list is configured. However, the error suggests the code path is still attempting IP parsing after this check.

**Workaround**: Set `OAUTH2_PROXY_REVERSE_PROXY=true` along with `OAUTH2_PROXY_REAL_CLIENT_IP_HEADER=X-Real-IP` (or `X-Forwarded-For`). This makes oauth2-proxy [read the client IP from HTTP headers first](https://github.com/oauth2-proxy/oauth2-proxy/blob/e59f7c1549aeee85e96f771694ac55cf8a594edb/pkg/ip/realclientip.go#L67-L72), bypassing the problematic `RemoteAddr` parsing. Just ensure your upstream proxy (Caddy/nginx) sets the appropriate header.

Your proposed solutions are reasonable — either silencing the error when no trusted IPs are configured, or providing a clearer error message about the socket + trusted IPs incompatibility would improve the user experience.


<!-- Dosu Comment Footer -->
*To reply, just mention [@dosu](https://go.dosu.dev/dosubot?utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=brand-link).*

---
<sup>How did I do? [Good](https://app.dosu.dev/response-feedback/c34696e8-54e0-4a52-99c7-be02d3235a87?feedback_type=great_response&utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/c34696e8-54e0-4a52-99c7-be02d3235a87?feedback_type=irrelevant_answer&utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/c34696e8-54e0-4a52-99c7-be02d3235a87?feedback_type=incorrect_sources&utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/c34696e8-54e0-4a52-99c7-be02d3235a87?feedback_type=too_verbose&utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/c34696e8-54e0-4a52-99c7-be02d3235a87?feedback_type=hallucination&utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/c34696e8-54e0-4a52-99c7-be02d3235a87?feedback_type=bug_report&utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-bug_report) | [Other](https://app.dosu.dev/response-feedback/c34696e8-54e0-4a52-99c7-be02d3235a87?feedback_type=other&utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=feedback-other)</sup>

[![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/fd8ed416-6c58-4c68-9279-aba1c2fc1890/ask?utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=chat-badge)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20is%20a%20known%20limitation%20in%20how%20oauth2-proxy%20handles%20%60RemoteAddr%60%20with%20socket%20listeners.%20The%20error%20occurs%20in%20the%20%5B%60isTrustedIP%60%20function%5D%28https%3A//github.com/oauth2-proxy/oauth2-proxy/blob/e59f7c1549aeee85e96f771694ac55cf8a594edb/oauthproxy.go%23L613-L632%29%20which%20calls%20%60net.SplitHostPort%28%29%60%20expecting%20a%20%22host%3Aport%22%20format%2C%20but%20socket%20connections%20provide%20%60%40%60%20instead.%0A%0AThere%27s%20actually%20%5Bspecial%20handling%20for%20unix%20sockets%20when%20%60req.RemoteAddr%20%3D%3D%20%22%40%22%60%5D%28https%3A//github.com/oauth2-proxy/oauth2-proxy/blob/e59f7c1549aeee85e96f771694ac55cf8a594edb/oauthproxy.go%23L614-L616%29%2C%20which%20should%20auto-trust%20such%20connections%20when%20no%20explicit%20trusted%20IP%20list%20is%20configured.%20However%2C%20the%20error%20suggests%20the%20code%20path%20is%20still%20attempting%20IP%20parsing%20after%20this%20check.%0A%0A%2A%2AWorkaround%2A%2A%3A%20Set%20%60OAUTH2_PROXY_REVERSE_PROXY%3Dtrue%60%20along%20with%20%60OAUTH2_PROXY_REAL_CLIENT_IP_HEADER%3DX-Real-IP%60%20%28or%20%60X-Forwarded-For%60%29.%20This%20makes%20oauth2-proxy%20%5Bread%20the%20client%20IP%20from%20HTTP%20headers%20first%5D%28https%3A//github.com/oauth2-proxy/oauth2-proxy/blob/e59f7c1549aeee85e96f771694ac55cf8a594edb/pkg/ip/realclientip.go%23L67-L72%29%2C%20bypassing%20the%20problematic%20%60RemoteAddr%60%20parsing.%20Just%20ensure%20your%20upstream%20proxy%20%28Caddy/nginx%29%20sets%20the%20appropriate%20header.%0A%0AYour%20proposed%20solutions%20are%20reasonable%20%E2%80%94%20either%20silencing%20the%20error%20when%20no%20trusted%20IPs%20are%20configured%2C%20or%20providing%20a%20clearer%20error%20message%20about%20the%20socket%20%2B%20trusted%20IPs%20incompatibility%20would%20improve%20the%20user%20experience.)&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot?utm_source=github&utm_medium=bot-comment&utm_campaign=github-assistant&utm_content=join-discord)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/oauth2-proxy/oauth2-proxy/issues/3373)
Fix in latest release as of yesterday v7.15.0
My mistake I forgot to get it merged before the release was finalised 



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5d740cb929..aabc138520 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,8 @@
 # V7.15.0
 
 - [#3382](https://github.com/oauth2-proxy/oauth2-proxy/pull/3382) chore(deps): update gomod and golangci/golangci-lint to v2.11.4 (@tuunit)
+- [#3374](https://github.com/oauth2-proxy/oauth2-proxy/pull/3374) fix: handle Unix socket RemoteAddr in IP resolution (@H1net)
+
 
 ## Release Highlights
 
diff --git a/docs/docs/configuration/systemd_socket.md b/docs/docs/configuration/systemd_socket.md
index 642e6f3f7b..490dabbf80 100644
--- a/docs/docs/configuration/systemd_socket.md
+++ b/docs/docs/configuration/systemd_socket.md
@@ -40,4 +40,12 @@ the listener it created onto the process, starting with file descriptor 3.
     --client-secret=...
 ```
 
+## Trusted IPs
+
+When listening on a Unix socket, Go sets `http.Request.RemoteAddr` to `"@"` instead of the usual `"host:port"` format. This means there is no client IP available from the connection itself.
+
+As a result, `--trusted-ip` entries cannot match against the direct connection address for Unix socket listeners. Requests arriving over a Unix socket will never be considered "trusted" based on their `RemoteAddr`. IP-based trust decisions will still work if a trusted reverse proxy sets `X-Forwarded-For` or `X-Real-IP` headers and `--reverse-proxy=true` is configured.
+
+## TLS
+
 Currently TLS is not supported (but it's doable).
diff --git a/docs/versioned_docs/version-7.15.x/configuration/systemd_socket.md b/docs/versioned_docs/version-7.15.x/configuration/systemd_socket.md
index 642e6f3f7b..490dabbf80 100644
--- a/docs/versioned_docs/version-7.15.x/configuration/systemd_socket.md
+++ b/docs/versioned_docs/version-7.15.x/configuration/systemd_socket.md
@@ -40,4 +40,12 @@ the listener it created onto the process, starting with file descriptor 3.
     --client-secret=...
 ```
 
+## Trusted IPs
+
+When listening on a Unix socket, Go sets `http.Request.RemoteAddr` to `"@"` instead of the usual `"host:port"` format. This means there is no client IP available from the connection itself.
+
+As a result, `--trusted-ip` entries cannot match against the direct connection address for Unix socket listeners. Requests arriving over a Unix socket will never be considered "trusted" based on their `RemoteAddr`. IP-based trust decisions will still work if a trusted reverse proxy sets `X-Forwarded-For` or `X-Real-IP` headers and `--reverse-proxy=true` is configured.
+
+## TLS
+
 Currently TLS is not supported (but it's doable).
diff --git a/oauthproxy.go b/oauthproxy.go
index dc3f8b5795..f260acc658 100644
--- a/oauthproxy.go
+++ b/oauthproxy.go
@@ -613,9 +613,7 @@ func (p *OAuthProxy) isAPIPath(req *http.Request) bool {
 
 // isTrustedIP is used to check if a request comes from a trusted client IP address.
 func (p *OAuthProxy) isTrustedIP(req *http.Request) bool {
-	// RemoteAddr @ means unix socket
-	// https://github.com/golang/go/blob/0fa53e41f122b1661d0678a6d36d71b7b5ad031d/src/syscall/syscall_linux.go#L506-L511
-	if p.trustedIPs == nil && req.RemoteAddr != "@" {
+	if p.trustedIPs == nil {
 		return false
 	}
 
diff --git a/pkg/ip/realclientip.go b/pkg/ip/realclientip.go
index 8bfc7ea34c..db8f25958d 100644
--- a/pkg/ip/realclientip.go
+++ b/pkg/ip/realclientip.go
@@ -73,6 +73,12 @@ func GetClientIP(p ipapi.RealClientIPParser, req *http.Request) (net.IP, error)
 
 // getRemoteIP obtains the IP of the low-level connected network host
 func getRemoteIP(req *http.Request) (net.IP, error) {
+	// Unix domain sockets set RemoteAddr to "@" which has no meaningful IP.
+	// https://github.com/golang/go/blob/0fa53e41f122b1661d0678a6d36d71b7b5ad031d/src/syscall/syscall_linux.go#L506-L511
+	if req.RemoteAddr == "@" {
+		return nil, nil
+	}
+
 	//revive:disable:indent-error-flow
 	if ipStr, _, err := net.SplitHostPort(req.RemoteAddr); err != nil {
 		return nil, fmt.Errorf("unable to get ip and port from http.RemoteAddr (%s)", req.RemoteAddr)
@@ -94,7 +100,7 @@ func GetClientString(p ipapi.RealClientIPParser, req *http.Request, full bool) (
 	}
 
 	var remoteIPStr string
-	if remoteIP, err := getRemoteIP(req); err == nil {
+	if remoteIP, err := getRemoteIP(req); err == nil && remoteIP != nil {
 		remoteIPStr = remoteIP.String()
 	}
 


Test patch:
diff --git a/oauthproxy_test.go b/oauthproxy_test.go
index 38cdccaba9..e06f50e9fc 100644
--- a/oauthproxy_test.go
+++ b/oauthproxy_test.go
@@ -2150,6 +2150,32 @@ func TestTrustedIPs(t *testing.T) {
 			}(),
 			expectTrusted: false,
 		},
+		// Check Unix socket with no trusted IPs configured does not error.
+		{
+			name:               "UnixSocketWithoutTrustedIPs",
+			trustedIPs:         nil,
+			reverseProxy:       false,
+			realClientIPHeader: "X-Real-IP",
+			req: func() *http.Request {
+				req, _ := http.NewRequest("GET", "/", nil)
+				req.RemoteAddr = "@"
+				return req
+			}(),
+			expectTrusted: false,
+		},
+		// Check Unix socket with trusted IPs configured returns false (no IP to match).
+		{
+			name:               "UnixSocketWithTrustedIPs",
+			trustedIPs:         []string{"127.0.0.1"},
+			reverseProxy:       false,
+			realClientIPHeader: "X-Real-IP",
+			req: func() *http.Request {
+				req, _ := http.NewRequest("GET", "/", nil)
+				req.RemoteAddr = "@"
+				return req
+			}(),
+			expectTrusted: false,
+		},
 		// Check using req.RemoteAddr (Options.ReverseProxy == false).
 		{
 			name:               "WithRemoteAddr",
diff --git a/pkg/ip/realclientip_test.go b/pkg/ip/realclientip_test.go
index c56e01702e..3cbca114a6 100644
--- a/pkg/ip/realclientip_test.go
+++ b/pkg/ip/realclientip_test.go
@@ -112,6 +112,8 @@ func TestGetRemoteIP(t *testing.T) {
 		errString  string
 		expectedIP net.IP
 	}{
+		// Unix domain sockets set RemoteAddr to "@"
+		{"@", "", nil},
 		{"", "unable to get ip and port from http.RemoteAddr ()", nil},
 		{"nil", "unable to get ip and port from http.RemoteAddr (nil)", nil},
 		{"235.28.129.186", "unable to get ip and port from http.RemoteAddr (235.28.129.186)", nil},
@@ -155,6 +157,8 @@ func TestGetClientString(t *testing.T) {
 	}{
 		// Should fail quietly, only printing warnings to the log
 		{nil, "", "", "", ""},
+		// Unix domain socket — no IP available
+		{nil, "@", "", "", ""},
 		{p, "127.0.0.1:11950", "", "127.0.0.1", "127.0.0.1"},
 		{p, "[::1]:28660", "99.103.56.12", "99.103.56.12", "::1 (99.103.56.12)"},
 		{nil, "10.254.244.165:62750", "", "10.254.244.165", "10.254.244.165"},