Proxy authentication (407, Basic, Kerberos, NTLM)
407 versus 401, hop-by-hop Proxy-Authorization, Basic, Digest, Kerberos and connection-bound NTLM, plus the Java disabledSchemes trap over CONNECT.
Key points
401means authenticate to the origin server,407means authenticate to the proxy; they use different header fields and the credentials are not interchangeable.Proxy-AuthenticateandProxy-Authorizationare hop-by-hop, so a chain of two authenticating proxies cannot be satisfied by standard HTTP alone.- NTLM authenticates a TCP connection, not a request, which breaks with connection pooling, proxy farms behind a VIP, and HTTP/2 entirely.
- The JVM disables Basic over CONNECT tunnels by default (
jdk.http.auth.tunneling.disabledSchemes=Basic), producingUnable to tunnel through proxyon an otherwise working proxy.
A 407 Proxy Authentication Required response means the proxy wants credentials before it will forward anything; a 401 Unauthorized means the origin server does. They are structurally identical and completely separate: 407 carries Proxy-Authenticate and is answered with Proxy-Authorization, while 401 carries WWW-Authenticate and is answered with Authorization. A request passing through an authenticating proxy to an authenticating origin carries both header fields at once, with different credentials in each.
The proxy variants are hop-by-hop. RFC 9110 specifies that Proxy-Authenticate applies only to the connection on which it was sent, and Proxy-Authorization applies only to the next inbound recipient, which must consume the field and not forward it. That single rule explains most of the surprising behaviour below.
401 versus 407 at a glance#
| Origin authentication | Proxy authentication | |
|---|---|---|
| Challenge status | 401 Unauthorized | 407 Proxy Authentication Required |
| Challenge field | WWW-Authenticate | Proxy-Authenticate |
| Credential field | Authorization | Proxy-Authorization |
| Scope | End to end, forwarded by intermediaries | Single hop, consumed by the recipient |
| Cacheable | 401 is not cached by default | 407 is not cached |
| Applies to CONNECT | No, there is no origin request yet | Yes, the CONNECT itself is challenged |
| Sent on every request | Yes, once the client knows the realm | Yes, except for connection-bound schemes |
If a proxy sends 401 when it means "authenticate to me", clients will store the credentials against the origin's realm and send them to the origin, which is both broken and a credential leak. If a reverse proxy sends 407, it is telling the client that the intermediary needs credentials, which for a reverse proxy is almost always wrong; a reverse proxy fronting an application should return 401 on its own behalf.
The schemes#
Basic (RFC 7617)#
Proxy-Authorization: Basic <base64(user:password)>. Base64 is an encoding, not encryption, so the credential is recoverable by anyone who sees the request. RFC 7617 adds an optional charset="UTF-8" parameter on the challenge to disambiguate non-ASCII passwords, which older implementations ignore.
It is still everywhere for three reasons: it is stateless, so it survives connection pooling and proxy farms without any affinity; it is implemented by every HTTP client that exists, including the ones with 200 lines of HTTP support; and against a proxy reached over the LAN, the threat model many operators accept is "the credential is visible to the network operator, who is us". Basic to a proxy reached over HTTPS proxy:443 (a TLS-protected connection to the proxy itself) is genuinely fine. Basic over plaintext to a proxy across an untrusted path is not.
Digest (RFC 7616)#
Challenge/response over a server nonce, with qop=auth, a client nonce, and a nonce count, hashed with MD5 or (in RFC 7616) SHA-256 or SHA-512/256. It avoids sending the password, but it requires the proxy to hold either the password or the H(A1) digest in a reversible form, which rules out most directory integrations. Support for the SHA-256 variants is patchy in clients, so a proxy that offers only SHA-256 will find that some tooling silently fails to authenticate. In practice Digest occupies a small niche between "Basic over TLS" and "Kerberos".
Negotiate / Kerberos (SPNEGO, RFC 4559)#
Proxy-Authenticate: Negotiate starts a SPNEGO exchange carrying base64-encoded GSS-API tokens. With a Kerberos ticket in the client's credential cache, this is a single extra round trip and no password ever crosses the wire. Requirements that bite:
- A service principal name must exist for the proxy, typically
HTTP/proxy.corp.example@REALM. Clients construct the SPN from the hostname they were configured with, so a proxy reached through a load-balanced VIP needs the SPN registered on the VIP name, not the individual node names. - Clock skew must be within the Kerberos tolerance (MIT Kerberos defaults to 300 seconds).
- The client needs a ticket. On Linux that means
kinitor a keytab; a container without a credential cache falls back to whatever else the proxy offers, usually a407loop. - Because SPNEGO can also carry NTLM, a proxy offering
Negotiatemay end up doing connection-bound NTLM underneath, inheriting all of the problems in the next section.
NTLM#
NTLM over HTTP is a three-message handshake: the client sends a Type 1 negotiate token, the proxy answers 407 with a Type 2 challenge token, and the client sends a Type 3 authenticate token. The critical property is that the resulting authenticated state belongs to the TCP connection, not to the request or the credential. Once established, subsequent requests on that same connection are accepted with no Proxy-Authorization header at all.
Everything that moves requests between connections therefore breaks it:
- Connection pooling. A client that issues the handshake on connection A and then sends the real request on connection B gets another
407. Clients that support NTLM have to pin the exchange to one socket, which is why NTLM-aware libraries disable pooling or mark the connection as owned. - Proxy farms. If a load balancer in front of the proxies is not doing connection-level affinity, the Type 3 message can arrive at a node that never issued the Type 2 challenge. Symptom: authentication succeeds intermittently, roughly one time in N for N proxy nodes.
- HTTP/2. HTTP/2 multiplexes independent requests over one connection and has no concept of connection-scoped authentication state. A scheme that authenticates the connection cannot express "these streams are authenticated and those are not", so NTLM is not usable on HTTP/2 and clients fall back to HTTP/1.1 for proxy hops that require it. This is the main reason enterprise proxy chains stay on HTTP/1.1 long after the rest of the estate moves on, as noted in HTTP/2 and HTTP/3 through proxies.
- Anything that closes the connection between challenge and response. A proxy with an aggressive idle timeout, or an intermediate device that resets, restarts the handshake from the beginning.
RFC 4559 defines a related Proxy-Support: Session-Based-Authentication response header, sent by a proxy to signal that it does not share an authenticated connection between different clients. Its specified use is narrower than the proxy-auth case above: the RFC says a client must not attempt SPNEGO authentication to the origin server through a proxy unless the proxy returns that header with the server's 401.
Scheme comparison#
| Scheme | Credential on the wire | Connection affinity required | Works on HTTP/2 hop | Typical curl flag | Credential storage |
|---|---|---|---|---|---|
| Basic | Password, base64 only | No | Yes | --proxy-basic | Plaintext or hash file at the proxy; often LDAP bind |
| Digest | Hash response only | No | Yes | --proxy-digest | Proxy must hold password or H(A1) |
| Negotiate (Kerberos) | Signed SPNEGO token | No, if genuine Kerberos | Yes | --proxy-negotiate | KDC-issued ticket, client credential cache |
| Negotiate falling back to NTLM | NTLM tokens | Yes | No | --proxy-negotiate | Windows credential store / SSO |
| NTLM | Challenge/response tokens | Yes | No | --proxy-ntlm | Windows credential store; secrets on Linux via keytab-less helpers |
| Bearer | Token | No | Yes | --proxy-header "Proxy-Authorization: Bearer ..." | Wherever the token is minted |
Decision rule: if you can terminate the client-to-proxy hop with TLS, use Basic over an HTTPS proxy. It is stateless, works with every client and every HTTP version, imposes no affinity requirement on your load balancer, and its one weakness (credential visible on the wire) is exactly what the TLS hop removes. Reach for Kerberos when you need single sign-on against a directory and you control DNS and SPNs. Treat NTLM as a compatibility mode you are migrating away from, not a design choice.
Interaction with CONNECT#
For https:// URLs the client's first request to the proxy is a CONNECT, and that is what gets challenged:
CONNECT api.example.com:443 HTTP/1.1
Host: api.example.com:443
HTTP/1.1 407 Proxy Authentication Required
Proxy-Authenticate: Basic realm="corp-proxy"
Proxy-Authenticate: Negotiate
Content-Length: 0
CONNECT api.example.com:443 HTTP/1.1
Host: api.example.com:443
Proxy-Authorization: Basic YWxpY2U6czNjcjN0
HTTP/1.1 200 Connection establishedThree consequences:
- Authentication happens once per tunnel, not per request. Every HTTP request inside the tunnel is invisible to the proxy, so there is nothing to authenticate. A proxy that wants per-request identity for HTTPS traffic has to intercept TLS.
- A
407on CONNECT is the only challenge the client will ever see for that destination. Clients that only implement proxy auth on the plain-HTTP path fail here, which is the classic "http://works,https://returns 407" report. - Connection-bound schemes need the tunnel to be built on the same socket that carried the challenge. With NTLM, the Type 2 challenge arrives on a socket, and the Type 3 message plus the successful CONNECT must go out on that same socket. Any client that opens a fresh connection for the retry never completes.
Multiple Proxy-Authenticate fields, as above, mean the proxy offers several schemes and the client picks. Most clients prefer the strongest they support, which is why a proxy that lists Negotiate first can push a Linux client with no Kerberos ticket into a failure loop even though Basic was available.
Failure modes#
Unable to tunnel through proxy. Proxy returns "HTTP/1.1 407" in Java#
This is the single most confusing proxy-auth error in the JVM, because the proxy, the credentials and the network are all correct. Since JDK 8u111 (and the corresponding 7u121 and 6u131 updates), the system property jdk.http.auth.tunneling.disabledSchemes defaults to Basic, which disables Basic authentication for HTTPS tunnels established with CONNECT. The java.net.Authenticator is simply never consulted for the tunnel, so the client sends no Proxy-Authorization and the proxy's 407 is final.
Symptom to root cause to fix:
- Symptom:
java.io.IOException: Unable to tunnel through proxy. Proxy returns "HTTP/1.1 407 Proxy Authentication Required", whilecurl -x http://user:pass@proxy:3128 https://...on the same host succeeds. - Root cause: the JDK default above. A companion property,
jdk.http.auth.proxying.disabledSchemes, governs plain (non-tunnelled) proxying and defaults to empty, which is why plainhttp://URLs authenticate fine from the same JVM. - Fix, in order of preference: switch the proxy hop to a scheme that is not disabled, or terminate the proxy hop in TLS and re-enable Basic deliberately with
-Djdk.http.auth.tunneling.disabledSchemes="". Setting it to empty restores pre-8u111 behaviour for every scheme, so do it only when the proxy hop is protected.
This trap generalises: the JDK made a security decision about a scheme-plus-transport combination, and the failure surfaces as a proxy error rather than a client policy message. Gradle, Maven, and anything else on the JVM inherits it; see corporate proxies and developer tooling for the equivalent gotchas in other ecosystems.
Credentials in proxy URLs leaking into logs#
https_proxy=http://alice:s3cr3t@proxy.corp.example:3128 is the path of least resistance and it leaks the password into: the process environment (readable via /proc/<pid>/environ by the same user, and by anything that dumps env for diagnostics), CI job logs that echo their environment, container image layers when set with ENV in a Dockerfile, shell history, and crash/support bundles. Some tools additionally log the full proxy URL on error.
Mitigations, roughly in order of effectiveness: use a credential file the client reads (curl's --netrc or -K config file with proxy-user), use Kerberos so there is no password to place anywhere, or run a local unauthenticated proxy on 127.0.0.1 that holds the credential and forwards upstream. Never put credentials in a PAC file: PAC has no credential syntax, and a PAC file is served to every client that asks.
407 loops#
The client answers a 407, gets another 407, and repeats until it gives up. Causes, in the order worth checking:
- Special characters in the password.
@,:,/and#in a proxy URL must be percent-encoded. An unencoded@truncates the username at the wrong place, and the proxy sees a different user each time. - Wrong header field. A client sending
Authorizationinstead ofProxy-Authorization, which the proxy ignores. - Scheme mismatch. The proxy offers only
Negotiate, the client has no ticket, and it retries the same anonymous request. - NTLM across a proxy farm with no connection affinity, which loops for a subset of attempts rather than all of them.
- A second proxy in the chain, per the insight above.
--proxy-anyauth and the extra request#
curl's --proxy-anyauth tells curl to discover which schemes the proxy supports and pick the most secure one. The cost is an extra round trip: curl sends a request without credentials, reads the Proxy-Authenticate list, then retries. For a PUT or POST with a body from a non-rewindable source (a pipe, -T -), that retry can fail or resend data. If you know the scheme, name it explicitly with --proxy-basic, --proxy-digest, --proxy-negotiate or --proxy-ntlm and save the probe. Note also that curl's NTLM_WB winbind helper was removed in curl 8.8.0, so on Linux, NTLM support depends on the build's native implementation and its crypto backend.
Authentication that works in the browser but nowhere else#
Browsers do integrated Windows authentication and PAC evaluation; command-line tools do neither. A developer whose browser reaches the internet through an SSO proxy will find npm, pip, git and docker all failing with 407, because they read only HTTP_PROXY/HTTPS_PROXY and have no Kerberos ticket. See PAC files and WPAD for why the two configuration systems diverge, and curl through a proxy for the flags that make the command line behave.
Frequently asked questions#
What does HTTP 407 mean?#
407 Proxy Authentication Required means an intermediary proxy refuses to forward the request until the client supplies credentials for the proxy itself. The response carries one or more Proxy-Authenticate header fields listing acceptable schemes, and the client retries with a Proxy-Authorization field. It is distinct from 401, which is a challenge from the origin server.
What is the difference between 401 and 407?#
401 challenges the client to authenticate to the origin server using WWW-Authenticate and Authorization. 407 challenges the client to authenticate to the proxy using Proxy-Authenticate and Proxy-Authorization. The proxy fields are hop-by-hop and must be consumed by the recipient rather than forwarded, so credentials for one are never valid for the other.
Is Proxy-Authorization forwarded to the origin server?#
No. It applies to a single hop and the receiving proxy is required to consume it. This is why a chain of two proxies that both demand authentication cannot be satisfied by a standard client, and why proxy credentials do not leak to the destination website in a correctly implemented proxy.
Why does NTLM authentication fail through a load balancer?#
NTLM authenticates a TCP connection rather than a request, so the Type 1, Type 2 and Type 3 messages of the handshake must all be handled by the same proxy instance. A load balancer that distributes without connection-level affinity sends the final message to a node that has no record of the challenge it is answering. Configure connection affinity, or move to Kerberos.
Can I use NTLM with HTTP/2?#
No. HTTP/2 multiplexes independent requests over a single connection and has no mechanism for connection-scoped authentication state, so a scheme that authenticates the connection cannot be expressed. Clients that must use NTLM to a proxy negotiate HTTP/1.1 for that hop.
How do I authenticate to a proxy with curl?#
Use --proxy-user user:password together with an explicit scheme flag such as --proxy-basic, --proxy-digest, --proxy-negotiate or --proxy-ntlm. --proxy-anyauth lets curl detect the scheme at the cost of an extra unauthenticated probe request. Prefer a .netrc or a curl config file over embedding credentials in the proxy URL.
Why does Java fail with "Unable to tunnel through proxy" when curl works?#
Because the JDK disables Basic authentication over CONNECT tunnels by default. Since JDK 8u111, jdk.http.auth.tunneling.disabledSchemes defaults to Basic, so the JVM never sends Proxy-Authorization on the CONNECT and the proxy's 407 is fatal. Use a different scheme, or set the property to an empty string once the proxy hop is TLS-protected.
Is Basic proxy authentication safe?#
Only when the connection to the proxy is encrypted. The credential is base64-encoded, not encrypted, so a plaintext hop exposes the password to anyone on the path. Basic to a proxy reached over TLS (an HTTPS proxy:443 entry, supported by modern browsers) is a reasonable choice and avoids every affinity problem that connection-bound schemes create.
Primary sources#
Every normative claim on this page is checked against the specification or the vendor documentation listed here. Where behaviour is version dependent, the version is named in the text.
- RFC 9110 HTTP Semantics, 407 Proxy Authentication Required
- RFC 9110 HTTP Semantics, Proxy-Authenticate and Proxy-Authorization
- RFC 7617 The Basic HTTP Authentication Scheme
- RFC 7616 HTTP Digest Access Authentication
- RFC 4559 SPNEGO-based Kerberos and NTLM HTTP Authentication
- RFC 9113 HTTP/2
- JDK 8u111 release notes, Disable Basic authentication for HTTPS tunneling
- curl manual, proxy authentication options
- Squid proxy authentication configuration
Found something wrong, or behaviour that differs on your version? Report it with the version number and a primary source. Anything substantive is fixed in the page and logged on the corrections page. See editorial standards for how pages are researched, sourced and reviewed.