Getting started29 September 20268 min read

Proxy formats explained: which string each tool wants

Proxy format explained: HOST:PORT:USER:PASS, user:pass@host:port and full URLs, which tool wants which, a tested converter, and fixes for special characters.

A proxy format is the order in which four values are written as one string: host, port, username and password. The common ones are HOST:PORT, HOST:PORT:USERNAME:PASSWORD, USERNAME:PASSWORD@HOST:PORT and the full URL http://USERNAME:PASSWORD@HOST:PORT. Code libraries want the URL. Antidetect browsers and bulk importers usually want the colon-separated line.

The values never change between formats; only the punctuation around them does. A correct proxy pasted in the wrong shape fails exactly like a wrong one, so the shape is worth ten minutes. This post lists the formats you will meet, says which tools accept which, and gives you a small converter in Python and JavaScript so you can stop reformatting lists by hand.

Every example uses HOST, PORT, USERNAME and PASSWORD as placeholders. Your own values are on each service's page in the dashboard; there is no shared gateway address to copy from a blog post.

The five proxy formats you will meet

Format Example Where you see it
Host and port HOST:PORT Browser and system settings, allowlisted connections
Colon line, host first HOST:PORT:USERNAME:PASSWORD Provider list exports, antidetect browser imports
Colon line, user first USERNAME:PASSWORD:HOST:PORT Some other providers' exports and older tools
Login before the host USERNAME:PASSWORD@HOST:PORT curl, some bots and config files
Full URL http://USERNAME:PASSWORD@HOST:PORT Python, Node.js, Go, PHP, environment variables

The full URL is the one to keep as your master copy. It carries the scheme, it is a standard, and every other format can be built from it without guessing.

What the http:// at the front means

The scheme says how your program talks to the proxy, not how it talks to the website. http:// means a plain HTTP proxy, and it still reaches https:// sites: your client asks the proxy to open a CONNECT tunnel and the encryption runs through it end to end. Writing https:// in front of the proxy asks for an encrypted connection to the proxy itself, which is a different thing. When we pointed requests at a plain HTTP proxy with an https:// URL, it said so in as many words:

ProxyError('Unable to connect to proxy. Your proxy appears to only use HTTP and not HTTPS, try changing your proxy URL to be HTTP. ...')

http:// is available on every product. For SOCKS5, write socks5h:// instead: residential ports answer both, and ISP and datacenter proxies switch protocol in the dashboard.

Which tools want which proxy string format

We fed each format to curl 8.5, requests 2.34 and undici 8.11 (the fetch library for Node.js) against a local proxy that asks for a login. Here is what each accepted.

curl is the most forgiving. -x "http://USERNAME:PASSWORD@HOST:PORT" works, and so does -x USERNAME:PASSWORD@HOST:PORT without a scheme, since curl assumes http://. You can also keep the login out of the URL with -U:

curl -x HOST:PORT -U "USERNAME:PASSWORD" https://api.ipify.org

The colon line is refused before anything is sent: curl: (5) Unsupported proxy syntax in 'HOST:PORT:USERNAME:PASSWORD': Port number was not a decimal number between 0 and 65535. The cURL guide has the rest of curl's proxy options.

Python requests wants the full URL whenever there is a login. USERNAME:PASSWORD@HOST:PORT without http:// fails with InvalidProxyURL: Please check proxy URL. It is malformed and could be missing the host., and the colon line fails with InvalidURL: Failed to parse. A bare HOST:PORT is accepted, because requests adds the scheme for you.

Node.js is the strictest. undici's ProxyAgent rejects HOST:PORT with TypeError: Invalid URL, and rejects USERNAME:PASSWORD@HOST:PORT with Invalid URL protocol: the URL must start with `http:` or `https:`. Give it the full URL every time, as the Node.js guide does.

Browsers take the host and port in one pair of fields and ask for the username and password in a pop-up when the proxy first asks. Chrome and Edge hand the whole job to the operating system. The browser guide covers Firefox's own panel and the FoxyProxy extension, which has proper login fields. If Chrome refuses to load anything after you change these settings, Chrome proxy errors maps each error code to its cause.

Antidetect browsers offer both: separate Host, Port, Username and Password fields, and a paste or import box that reads one proxy per line as HOST:PORT:USERNAME:PASSWORD. The GoLogin, AdsPower and BitBrowser guides show where each one hides that box.

Proxifier has separate fields for everything. Choose HTTPS as the protocol, which is Proxifier's name for an HTTP proxy that opens CONNECT tunnels; the Proxifier guide walks through it.

The ip:port:user:pass format from the dashboard

The residential page in the dashboard exports proxies in HOST:PORT:USERNAME:PASSWORD order, the same order GoLogin and BitBrowser import. ISP and datacenter services list each static IP with its own port and login, and those four values drop into any of the formats above.

On residential, choose Randomize IP or Sticky IP in the dashboard before you copy anything. With Sticky IP the session option travels at the end of the password, so the password you copy is longer than the plain one. Copy the whole string as shown. A sticky password cut short at a symbol gets a 407, and rotating vs sticky proxies explains when you want that mode at all.

Special characters: when user:pass@host:port breaks

Inside a URL, some characters have jobs: @ ends the login, : separates username from password and host from port, and /, #, ? and % mean something too. A password containing any of them has to be percent-encoded in the URL formats. We tested with the password p@ss:w/rd#1%41:

  • Pasted raw into a URL, curl stopped with Unsupported proxy syntax, requests with InvalidURL, and undici with Invalid URL.
  • Encoded as p%40ss%3Aw%2Frd%231%2541, all three connected.
  • With curl's -U, the @, :, / and # could stay raw, but %41 was still decoded to A, so a literal % must be written %25 there too.

The colon line has the opposite rule: it is never encoded, because tools split it on colons rather than parsing it as a URL. That makes the colon line fragile in its own way. A password with a colon in it only splits correctly when the tool knows the order and treats everything after the third colon as the password. Fixing a 407 goes through the other ways a login gets mangled on the way to the proxy.

IPv6 proxies need brackets

An IPv6 address is full of colons, so in a URL it goes in square brackets: http://[2001:db8::10]:PORT, with the login before it as usual. Without brackets, curl fails with the same "Port number was not a decimal number" error and requests with Failed to parse. With brackets, curl, requests and undici all connected to our test proxy on [::1]. In a colon line, keep the brackets too, [2001:db8::10]:PORT:USERNAME:PASSWORD, or no tool can tell where the address ends.

A proxy format converter in Python

This reads any of the five formats and writes the one you need. Save it as proxyfmt.py:

import re
from urllib.parse import quote, unquote

HOST_RE = r"(?P<host>\[[^\]]+\]|[^:@\[\]]+)"
PORT_RE = r"(?P<port>[^:@/]+)"
URL_FORM = rf"^(?:(?P<scheme>[a-z0-9]+):\/\/)?(?:(?P<user>[^:@]*):(?P<password>.*)@)?{HOST_RE}:{PORT_RE}/?$"
COLON_FORMS = {
    "host_first": rf"^{HOST_RE}:{PORT_RE}:(?P<user>[^:]*):(?P<password>.*)$",
    "user_first": rf"^(?P<user>[^:]*):(?P<password>.*):{HOST_RE}:{PORT_RE}$",
}


def parse_proxy(line, order="host_first"):
    line = line.strip()
    match = re.match(URL_FORM, line)
    decode = unquote
    if not match:
        match = re.match(COLON_FORMS[order], line)
        decode = str
    if not match:
        raise ValueError(f"not a proxy line I recognise: {line!r}")
    p = match.groupdict()
    return {
        "scheme": p.get("scheme") or "http",
        "host": p["host"],
        "port": p["port"],
        "user": decode(p["user"]) if p["user"] is not None else None,
        "password": decode(p["password"]) if p["password"] is not None else None,
    }


def to_url(p):
    auth = ""
    if p["user"] is not None:
        auth = quote(p["user"], safe="") + ":" + quote(p["password"], safe="") + "@"
    return p["scheme"] + "://" + auth + p["host"] + ":" + p["port"]


def to_colon(p):
    parts = [p["host"], p["port"], p["user"], p["password"]]
    return ":".join(part for part in parts if part is not None)


if __name__ == "__main__":
    for line in [
        "HOST:PORT",
        "HOST:PORT:USERNAME:PASSWORD",
        "USERNAME:PASSWORD@HOST:PORT",
        "http://USERNAME:PASSWORD@HOST:PORT",
    ]:
        print(to_url(parse_proxy(line)))
    print(to_colon(parse_proxy("USERNAME:PASSWORD:HOST:PORT", order="user_first")))

The two colon lines look identical from the outside, so parse_proxy cannot guess between them. It assumes host first, the dashboard's order, and takes order="user_first" for lists from elsewhere. URL forms are decoded on the way in and encoded on the way out, so a password full of symbols survives a round trip. To use a saved export with requests:

import requests

from proxyfmt import parse_proxy, to_url

with open("proxies.txt") as f:
    proxies = [to_url(parse_proxy(line)) for line in f if line.strip()]

for proxy in proxies:
    r = requests.get("https://api.ipify.org", proxies={"http": proxy, "https": proxy}, timeout=30)
    print(r.status_code, r.text)

The same converter in JavaScript

proxyfmt.mjs, for Node.js 22 or newer:

const HOST_RE = String.raw`(?<host>\[[^\]]+\]|[^:@\[\]]+)`;
const PORT_RE = String.raw`(?<port>[^:@/]+)`;
const URL_FORM = new RegExp(String.raw`^(?:(?<scheme>[a-z0-9]+):\/\/)?(?:(?<user>[^:@]*):(?<password>.*)@)?${HOST_RE}:${PORT_RE}\/?$`);
const COLON_FORMS = {
  hostFirst: new RegExp(String.raw`^${HOST_RE}:${PORT_RE}:(?<user>[^:]*):(?<password>.*)$`),
  userFirst: new RegExp(String.raw`^(?<user>[^:]*):(?<password>.*):${HOST_RE}:${PORT_RE}$`),
};

export function parseProxy(line, order = 'hostFirst') {
  line = line.trim();
  let match = line.match(URL_FORM);
  let decode = decodeURIComponent;
  if (!match) {
    match = line.match(COLON_FORMS[order]);
    decode = (s) => s;
  }
  if (!match) throw new Error(`not a proxy line I recognise: ${line}`);
  const { scheme, host, port, user, password } = match.groups;
  return {
    scheme: scheme ?? 'http',
    host,
    port,
    user: user === undefined ? undefined : decode(user),
    password: password === undefined ? undefined : decode(password),
  };
}

export function toUrl(p) {
  const auth = p.user === undefined ? '' : encodeURIComponent(p.user) + ':' + encodeURIComponent(p.password) + '@';
  return p.scheme + '://' + auth + p.host + ':' + p.port;
}

export function toColon(p) {
  return [p.host, p.port, p.user, p.password].filter((part) => part !== undefined).join(':');
}

And with undici (npm i undici):

import { fetch, ProxyAgent } from 'undici';
import { parseProxy, toUrl } from './proxyfmt.mjs';

const proxy = toUrl(parseProxy('HOST:PORT:USERNAME:PASSWORD'));
const response = await fetch('https://api.ipify.org', { dispatcher: new ProxyAgent(proxy) });
console.log(response.status, await response.text());

We ran both versions against local test proxies with plain passwords, the symbol-heavy password above, IPv6 addresses in brackets and user-first lines. Every line connected, and converting to a URL and back returned the same four values.

When HOST:PORT alone is enough

If you add the IP address you connect from to the service's IP allowlist in the dashboard, the proxy lets that address in without a login. From then on HOST:PORT, or http://HOST:PORT for Node.js, is the whole proxy string, and nobody has to store the password in a config file. That suits a server with a fixed public address. It does not suit a laptop that moves between networks, because the allowlist only knows the addresses you gave it.

Quick answers

What is the ip:port:user:pass format? The colon line with the host first: HOST:PORT:USERNAME:PASSWORD. It is what the dashboard's residential export uses and what most antidetect browsers import.

Is user:pass@host:port the same as the URL format? Nearly. It is the URL without http:// at the front. curl accepts it as is; Python requests and Node.js need the scheme added.

Should the proxy URL start with http or https? http://, including for https websites. The tunnel carries the site's encryption.

Why does my proxy list work in one tool and not another? The field order or the encoding differs. Convert the list with the snippet above and check whether the password has symbols in it.

Next step

Copy one line from your service page, run it through the converter, and send a single request to https://api.ipify.org. If the address that comes back is not yours, the format is right. How to use a proxy covers that first request in more detail, and if a list still refuses to load, paste the error in Discord with the password masked.

Got a follow-up question?

Ask it in Discord. The answer helps whoever reads the thread next.

Join the Discorddiscord.gg/proxypanda
Start with $5Ask in Discord