Request header
> Proxy-Authorization: Basic dXNlcjpwYXNzProxy-Authorization Header
Learn how Proxy-Authorization provides credentials to access resources through a proxy server. Understand proxy authentication schemes and security.
- Direction
- Request
- Category
- Authentication
- Spec
- RFC 9110 §11.7.2
- JS can set it
- No: forbidden header name
- Hop-by-hop
- Yes: not forwarded by proxies
On this page
TL;DR:
Proxy-Authorizationsends your credentials to a forward proxy, not to the site you’re visiting. The proxy asks for them with a407andProxy-Authenticate. In curl, use--proxy-user(or--proxy-headerfor a custom field). Credentials go out before the HTTPS tunnel exists, so with a plainhttp://proxy they cross the network unencrypted.
A proxy that wants credentials replies 407 with a Proxy-Authenticate challenge, and the client answers with this header. Clients configured with proxy credentials often send it up front. It’s meant for the next proxy on the path that requires authentication; RFC 9110 §11.7.2 has the details.
How Proxy-Authorization Works
Here’s a trimmed HTTP/1.1 CONNECT request with the example credentials user:pass:
CONNECT api.example.com:443 HTTP/1.1
Host: api.example.com:443
Proxy-Authorization: Basic dXNlcjpwYXNz
The proxy checks the credentials before opening the tunnel. If they’re missing or wrong, it sends a 407. If they’re good, the client negotiates TLS with the destination inside the tunnel. Credentials for the site itself go in Authorization on the request inside that tunnel.
Syntax
Proxy-Authorization: scheme credentials
What follows the scheme depends on the scheme. For Basic it’s base64 of username:password, which anyone can decode. It’s encoding, not encryption.
Encoding Credentials
To compute it locally with example credentials:
printf '%s' 'user:pass' | base64
You get dXNlcjpwYXNz. A Basic username can’t contain a colon, but a password can, so split at the first colon and keep everything after it. See RFC 7617 §2.
Testing
Let curl prompt you for the proxy password:
curl --proxy https://proxy.example.com:8443 \
--proxy-basic --proxy-user proxyuser \
https://example.com/ -o /dev/null
--proxy-digest and --proxy-anyauth work too, if both your proxy and your curl build support the scheme. To send the header yourself with example credentials:
curl --proxy https://proxy.example.com:8443 \
--proxy-header 'Proxy-Authorization: Basic dXNlcjpwYXNz' \
https://example.com/ -o /dev/null
--proxy-header sends the header to the proxy, on the CONNECT. A plain -H goes to the destination instead, which is the wrong place. Swap in your real proxy and credentials, of course.
Security Considerations
TLS to the destination only starts once CONNECT succeeds. With an http:// proxy URL, the CONNECT, and the Basic credentials on it, cross the network in plain text. Use an https:// proxy when that hop needs protecting, and verify the proxy’s certificate on its own terms, separately from the destination’s.
Keep Proxy-Authorization out of logs. Verbose output from curl and other clients can include it, so redact traces before you share them.
Getting Proxy-Authorization right
Browser JavaScript can’t set Proxy-Authorization; the Fetch Standard reserves Proxy- headers. Browsers handle proxy authentication through their own or the operating system’s proxy settings.
Node’s built-in fetch takes a dispatcher, not the agent option from node:http. Snippets written for other fetch packages often pass an agent, which the built-in client ignores. See Node’s Fetch documentation.
Node with an Undici dispatcher
Install undici with pnpm add undici and save this as proxy.mjs. Pass PROXY_URL, PROXY_USER, and PROXY_PASSWORD through the environment or your secrets tooling; the script never prints them:
import { ProxyAgent, fetch } from 'undici'
const { PROXY_URL, PROXY_USER, PROXY_PASSWORD } = process.env
if (!PROXY_URL || !PROXY_USER || !PROXY_PASSWORD) {
throw new Error('Set the proxy URL and credentials')
}
const dispatcher = new ProxyAgent({
uri: PROXY_URL,
token: `Basic ${Buffer.from(`${PROXY_USER}:${PROXY_PASSWORD}`).toString('base64')}`
})
try {
const response = await fetch('https://example.com/', { dispatcher })
console.log('origin status', response.status)
await response.arrayBuffer()
} finally {
await dispatcher.close()
}
The ProxyAgent API takes the ready-made credential in token and sends it to the proxy. Keep it out of the request’s own headers, or the destination receives your proxy password too. For a remote proxy, use an HTTPS proxy URL with a trusted certificate. Reading the body lets the client clean up properly, and finally closes the dispatcher even if something throws.
When the proxy rejects CONNECT, the tunnel never opens, so many clients throw an error instead of giving you a normal Response with status 407. How that error looks varies by client. Reproduce it with curl to see the proxy’s actual response before you debug your response.status handling.
Axios with an HTTPS proxy Agent
Axios on Node can route through https-proxy-agent. Install both with pnpm add axios https-proxy-agent, save this as axios-proxy.mjs, and set PROXY_URL to a proxy URL that includes credentials:
import axios from 'axios'
import { HttpsProxyAgent } from 'https-proxy-agent'
if (!process.env.PROXY_URL) throw new Error('Set PROXY_URL')
const proxy = new URL(process.env.PROXY_URL)
const agent = new HttpsProxyAgent(proxy)
try {
const response = await axios.get('https://example.com/', {
adapter: 'http',
httpsAgent: agent,
proxy: false,
timeout: 10000
})
console.log('origin status', response.status)
} finally {
agent.destroy()
}
The ten-second timeout is just a sensible value for testing. proxy: false turns off Axios’s built-in proxy handling so the agent controls the route; see the Axios request configuration. HttpsProxyAgent opens the CONNECT tunnel, and if the proxy URL contains a username and password, it sends them as Basic proxy authentication. Percent-encode special characters in them, and keep that URL out of logs. This agent works with Node’s HTTP adapter; it isn’t a Fetch dispatcher.
If your proxy uses a private certificate authority, check that trust on its own in curl:
curl --proxy https://proxy.example.com:8443 \
--proxy-cacert proxy-ca.pem --proxy-user proxyuser \
https://example.com/ -o /dev/null
proxy-ca.pem holds the CA certificates for the proxy. curl’s --proxy-cacert is separate from --cacert, which verifies the destination, so trusting your proxy’s CA has no effect on which destination certificates curl accepts.
Common Patterns
The proxy and the origin can each require their own credentials, so configure them separately. A proxy normally consumes the credentials meant for it, but RFC 9110 also lets proxies pass them along to another proxy that’s cooperating on authentication. So another proxy on the path may see them.
Independent proxy and origin authentication
curl can handle both in one request, prompting for each password separately:
curl --proxy https://proxy.example.com:8443 \
--proxy-basic --proxy-user proxyuser \
--user originuser https://api.example.com/protected -o /dev/null
proxyuser authenticates the CONNECT with the proxy; originuser authenticates the request inside the TLS connection to the destination. A 401 after the tunnel is up comes from the origin, which means proxy authentication worked. Fix the origin credentials; sending the proxy password to the origin won’t help and leaks it.
Bypassing the proxy for a comparison
If your network allows a direct connection, bypass the proxy to compare:
curl --noproxy '*' -I https://example.com/
To force a test through the proxy, pass --noproxy '' alongside --proxy. Both override curl’s bypass environment variables. Comparing the two tells you whether the problem is the proxy route or the destination, though a direct success tells you nothing about the proxy account.
In diagnostics, be clear which address is the proxy and which is the origin. A 502 or 504 from a proxy means it couldn’t reach the upstream, not that it rejected your password. A failed CONNECT may surface as a client error before any origin response exists. Report errors as they are, rather than turning every exception into “invalid credentials.”
To sum up: Authorization is for the site, Proxy-Authorization is for the proxy, and curl keeps them apart as --user and --proxy-user. Neither header encrypts anything; that’s TLS’s job.
Related Headers
Frequently asked questions
What is Proxy-Authorization?
It is the request header that carries your credentials for a proxy, as opposed to the website behind it. Clients usually send it after the proxy answers 407, or up front if they are configured with proxy credentials.
How is Proxy-Authorization different from Authorization?
Authorization authenticates with the origin server. Proxy-Authorization authenticates with an intermediate proxy. Both can be present in the same request.
What format does Proxy-Authorization use?
Same format as Authorization: scheme followed by credentials. For Basic: "Proxy-Authorization: Basic base64(user:pass)". For other schemes, follow their specification.
Is Proxy-Authorization secure?
Only as secure as the connection to the proxy. Basic credentials are just base64, and they travel before the HTTPS tunnel to the destination exists. Use an https:// proxy URL so the hop to the proxy is encrypted too.
Sources
Related
Cookie Header
Learn how the Cookie header sends stored cookies to servers with each request. Understand cookie transmission, session management, and security considerations.
Proxy-Authenticate Header
Learn how the Proxy-Authenticate header challenges clients for credentials when accessing resources through a proxy. Understand proxy authentication schemes.
Authentication-Info Header
Learn how Authentication-Info provides additional authentication data in responses to successful requests. Covers digest authentication and session info.
Authorization Header: Bearer, Basic and 401 Fixes
Authorization header carries credentials as scheme plus token. Bearer and Basic formats, WWW-Authenticate, why proxies strip it, redirects, CORS and storage.