# Proxy-Authorization Header

> Learn how Proxy-Authorization provides credentials to access resources through a proxy server. Understand proxy authentication schemes and security.

Source: https://howhttpworks.com/headers/proxy-authorization
Last reviewed: 2026-10-05

> **TL;DR:** `Proxy-Authorization` sends your credentials to a forward proxy, not to the site you're visiting. The proxy asks for them with a `407` and `Proxy-Authenticate`. In curl, use `--proxy-user` (or `--proxy-header` for a custom field). Credentials go out before the HTTPS tunnel exists, so with a plain `http://` 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](https://www.rfc-editor.org/rfc/rfc9110#section-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`:

```http
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

```text
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:

```bash
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](https://www.rfc-editor.org/rfc/rfc7617#section-2).

## Testing

Let curl prompt you for the proxy password:

```bash
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:

```bash
curl --proxy https://proxy.example.com:8443 \
  --proxy-header 'Proxy-Authorization: Basic dXNlcjpwYXNz' \
  https://example.com/ -o /dev/null
```

[`--proxy-header`](https://curl.se/docs/manpage.html#--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](https://fetch.spec.whatwg.org/#forbidden-request-header). 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](https://nodejs.org/api/globals.html#fetch).

### 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:

```javascript
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](https://github.com/nodejs/undici/blob/main/docs/docs/api/ProxyAgent.md) 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:

```javascript
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](https://github.com/axios/axios#request-config). [HttpsProxyAgent](https://github.com/TooTallNate/proxy-agents/tree/main/packages/https-proxy-agent) 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:

```bash
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`](https://curl.se/docs/manpage.html#--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:

```bash
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:

```bash
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

- [Authorization](https://howhttpworks.com/headers/authorization)
- [Proxy-Authenticate](https://howhttpworks.com/headers/proxy-authenticate)
- [WWW-Authenticate](https://howhttpworks.com/headers/www-authenticate)
- [Via](https://howhttpworks.com/headers/via)
