Proxy error · 407

407 Proxy Authentication Required

The one error on this list a website never sends. A 407 is the proxy gateway saying it did not get a username and password it accepts, so your request never left for the target.
Whose fault is it?

Almost always yours: the credentials, or the way your tool passes them. The target never saw the request.

Usually from
The proxy, about your credentials
Try first
Copy USER:PASS fresh from the dashboard and retry in curl
With a proxy in the path

What a 407 means through a proxy

HTTP gives proxies their own login status. A 401 is a website asking you to sign in to it; a 407 is the proxy in the middle asking. It arrives with a Proxy-Authenticate header naming the scheme the proxy wants, which for a USER:PASS proxy is Basic.

For an HTTPS target your client first sends CONNECT host:443 to the proxy. The 407 answers that CONNECT, so no tunnel opened and nothing reached the site. Most tools turn it into an exception with 407 buried in the message, and hand you no response object at all.

For a plain http:// target the 407 comes back as an ordinary response instead. Scrapy then drops it as an unhandled status, and a script that only prints the body sees an empty page and no error.

The key question

Your credentials, the proxy, or the target?

Almost always yours: the credentials, or the way your tool passes them. The target never saw the request.

  • Your credentials

    The usual cause. A typo, an old password, a special character that broke the URL, or a trailing newline read in from a .env file.

  • The proxy

    Worth suspecting only once the same credentials fail in a plain curl command too. That is the point to ask in Discord.

  • The target site

    Not involved. Websites answer with 401 or 403. A 407 comes from whatever is speaking proxy.

How to tell

  • The status is 407 and the response carries Proxy-Authenticate. Only a proxy sends that pair.
  • In curl, -w '%{http_connect}' prints 407: the proxy refused the tunnel before the site was contacted.
  • The same URL loads without the proxy. That rules the site out.
  • One curl command with the same USER:PASS works while your script fails. The credentials are fine and your tool is passing them wrong.
Cheapest first

Fixes, in the order to try them

  1. Copy the credentials again

    Take USER and PASS fresh from the dashboard and paste them into a single curl command before touching your code. If curl gets through, the login is good.

    Costs nothing
  2. Percent-encode special characters

    A password with @, :, / or # breaks a user:pass@host URL. Encode them (@ becomes %40), or pass the credentials separately: --proxy-user in curl, the username and password fields in Playwright.

    Costs nothing
  3. Strip whitespace from files and env vars

    Credentials read from .env or a secrets file often end in a newline. Use .strip() in Python or .trim() in Node, then print the proxy URL with the password masked to check it.

    Costs nothing
  4. Make sure credentials go with the CONNECT

    Some setups attach credentials to plain HTTP requests and forget the CONNECT used for HTTPS. Chrome ignores credentials written into --proxy-server, which is why Puppeteer needs page.authenticate and Selenium needs an extension. The setup guides cover each tool.

    Costs some time
  5. Match the proxy scheme to the port

    SOCKS5 has its own login handshake and never answers with 407. A 407 means your tool is speaking HTTP to the proxy, so check that the scheme in the proxy URL is the one the dashboard lists for that host and port.

    Costs some time
Per tool

See the real status and headers

Each sample makes the 407 visible and shows whether it came back on the CONNECT (the proxy talking) or as a response.

curl
terminal
# -v prints the proxy's own reply to CONNECT
curl -sv -o /dev/null -x http://USER:[email protected]:8000 https://httpbin.org/ip 2>&1 \
  | grep -iE '^< (HTTP|proxy-authenticate)'

# < HTTP/1.1 407 Proxy Authentication Required

# a password with @ : / or # goes in --proxy-user, outside the URL
curl -x http://resi.proxymonkey.io:8000 --proxy-user 'USER:PASS' https://httpbin.org/ip
Python Requests
check_login.py
import os
from urllib.parse import quote

import requests

user = os.environ["PM_USER"].strip()
password = quote(os.environ["PM_PASS"].strip(), safe="")
proxy = f"http://{user}:{password}@resi.proxymonkey.io:8000"

try:
    r = requests.get("https://httpbin.org/ip", proxies={"http": proxy, "https": proxy}, timeout=30)
    print("the site answered", r.status_code)
except requests.exceptions.ProxyError as err:
    print("the proxy refused the tunnel:", err)
Scrapy
spiders/check.py
import scrapy
from scrapy.core.downloader.handlers.http11 import TunnelError

RESIDENTIAL = "http://USER:[email protected]:8000"


class CheckSpider(scrapy.Spider):
    name = "check"
    handle_httpstatus_list = [407]

    def start_requests(self):
        for url in ["http://httpbin.org/ip", "https://httpbin.org/ip"]:
            yield scrapy.Request(url, meta={"proxy": RESIDENTIAL}, errback=self.failed)

    def parse(self, response):
        self.logger.info("%s %s", response.status, response.headers.get("Proxy-Authenticate"))

    def failed(self, failure):
        if failure.check(TunnelError):
            self.logger.error("CONNECT refused: %s", failure.value)

The http:// URL shows the 407 as a response; the https:// one shows it as a TunnelError.

Playwright
check_login.py
from playwright.sync_api import Error, sync_playwright

PROXY = {"server": "http://resi.proxymonkey.io:8000", "username": "USER", "password": "PASS"}

with sync_playwright() as p:
    browser = p.chromium.launch(proxy=PROXY)
    page = browser.new_page()
    try:
        response = page.goto("https://httpbin.org/ip")
        print(response.status, page.inner_text("body"))
    except Error as err:
        print(err.message.splitlines()[0])
    browser.close()

Credentials go in username and password. Written into server, Chromium ignores them.

Node.js
check-login.mjs
import { fetch, ProxyAgent } from "undici";

const user = process.env.PM_USER.trim();
const pass = process.env.PM_PASS.trim();

const dispatcher = new ProxyAgent({
  uri: "http://resi.proxymonkey.io:8000",
  token: "Basic " + Buffer.from(user + ":" + pass).toString("base64"),
});

try {
  const res = await fetch("https://httpbin.org/ip", { dispatcher });
  console.log("the site answered", res.status);
} catch (err) {
  console.error(err.cause?.message ?? err);
}

// Proxy response (407) !== 200 when HTTP Tunneling

Sending the credentials as a token sidesteps URL encoding altogether.

The samples use the residential gateway, resi.proxymonkey.io:8000. For an ISP or datacenter IP, use USER:PASS@IP:PORT for the address you rented. Your dashboard lists the host and port for every order, and where it differs from this page, the dashboard is right.

Before you buy anything

Will a different proxy line fix it?

When switching helps

No line fixes a 407. It is about the login, and the login is checked before your request goes anywhere.

When it will not

Buying a different line to get away from a 407 spends money on a typo. Get one curl command through first, then pick the line on what the target needs.

The meter

Is a failed request billed?

We bill for request bytes and response bytes, including headers and protocol overhead on the tunnelled connection. Connections that fail before transferring data are not billed. Retries that you initiate are billed and appear as separate entries in your usage log.

From the metering section of our terms of service.

The terms do not single out a rejected login. They do say that a connection which fails before transferring data is not billed. If you find rows for 407s in your usage log, ask in Discord.

Residential is billed per GB of that traffic. ISP and datacenter addresses are charged per IP for their term, and where a plan includes a traffic allowance, traffic past it is billed per GB under the same rule. The usage log in your dashboard has one row per request with bytes in, bytes out and cost, so you can look up the failed request yourself.

407, asked often

Questions people ask about a 407

Why do I get a 407 when my password is right?

Because the password did not reach the proxy intact. A special character in the URL, a newline from a file, or a tool that only sends credentials on plain HTTP requests will each produce a 407 with a correct password.

Test the same pair with curl --proxy-user. If that works, the problem is in how your code passes it.

Is a 407 the same as a 401?

They are cousins. A 401 comes from the website and means it wants you to sign in to it. A 407 comes from the proxy and means it wants proxy credentials. A 401 has nothing to do with your proxy account.

Does a 407 mean my account is blocked or out of balance?

The status code alone cannot tell you that. Check the account in the dashboard. If everything there looks right and one curl command still gets a 407, paste the curl output into the Discord with the password removed.

Why does Chrome pop up a sign-in box for the proxy?

That box is how a browser shows a 407: it got the challenge and nobody answered it. In automation, pass the credentials through the tool: the proxy username and password options in Playwright, page.authenticate in Puppeteer, or an extension in Selenium.

The community layer

Still stuck on a 407?

Paste your error in the Discord: the full message plus the command or the few lines that set up the proxy, with the password taken out. Someone there has seen it before.

Join the Discord

4,200+monkeys in the Discord

  • Help from humans

    Post your error, get an answer. Usually in minutes, usually from someone who has hit the same wall.

  • A status bot that tells on us

    Pool health, incidents and maintenance posted automatically. Including the bad days.

  • Deals and free traffic

    Bonus GB drops, early access to new pools, and the occasional giveaway for a good bug report.

Join the Discord4,200+ monkeys, free to lurk