Guide
How to Set Up Country Specific Proxies (With Code)
Learn how to set up country specific proxies for targeted data collection, ad checks, SEO research, and testing with reliable IP location control at scale.

This is the implementation guide: how to actually configure country-specific proxies in the tools you use, verify the targeting took effect, and structure a job that collects from several countries at once without cross-contaminating markets.
The Format
Residential gateways encode targeting into the username rather than using separate parameters, because the proxy protocol has nowhere else to put it. The pattern:
username-country-DE:password@gateway.flameproxies.com:8080
Add city, session, and other dimensions by appending segments:
username-country-DE-city-Berlin-session-job441:password@gateway:8080
Two things to get right from the start:
Use ISO 3166-1 alpha-2 codes. US, DE, FR, JP, BR. The United Kingdom is GB, not UK — this one causes a lot of silent failures.
Build the string programmatically. Hand-concatenation accumulates typos, and a malformed parameter usually returns a 407 that looks like a credential problem rather than a syntax problem.
import os
from urllib.parse import quote
PROXY_HOST = os.environ.get("PROXY_HOST", "gateway.flameproxies.com")
PROXY_PORT = os.environ.get("PROXY_PORT", "8080")
PROXY_USER = os.environ["PROXY_USER"]
PROXY_PASS = os.environ["PROXY_PASS"]
def proxy_url(country=None, city=None, session=None, scheme="http"):
parts = [PROXY_USER]
if country: parts.append(f"country-{country}")
if city: parts.append(f"city-{city}")
if session: parts.append(f"session-{session}")
user = quote("-".join(parts), safe="")
pw = quote(PROXY_PASS, safe="")
return f"{scheme}://{user}:{pw}@{PROXY_HOST}:{PROXY_PORT}"
def proxies_for(country=None, city=None, session=None):
url = proxy_url(country, city, session)
return {"http": url, "https": url}The quote() calls matter. A password containing @, :, or / breaks URL parsing and produces authentication errors with credentials that are demonstrably correct.
Configuring by Tool
curl
curl -x "http://user-country-DE:pass@gateway.flameproxies.com:8080" \
https://ipcheck.flameproxies.comPython requests
import requests
resp = requests.get(
"https://target.com/page",
proxies=proxies_for(country="DE", city="Berlin"),
timeout=30,
)Python httpx
import httpx
with httpx.Client(proxy=proxy_url(country="FR"), timeout=30) as client:
resp = client.get("https://target.com/page")Playwright
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
proxy={
"server": f"http://{PROXY_HOST}:{PROXY_PORT}",
"username": f"{PROXY_USER}-country-DE-city-Berlin",
"password": PROXY_PASS,
}
)
page = browser.new_page()
page.goto("https://target.com")Note that Playwright takes the server, username, and password separately — the targeting goes in the username field, not in the server URL.
Scrapy
# middlewares.py
class CountryProxyMiddleware:
def process_request(self, request, spider):
country = request.meta.get("country", "US")
request.meta["proxy"] = proxy_url(country=country)
# settings.py
DOWNLOADER_MIDDLEWARES = {
"myproject.middlewares.CountryProxyMiddleware": 350,
}Then set country per request:
yield scrapy.Request(url, meta={"country": "DE"})Node (axios + https-proxy-agent)
import axios from "axios";
import { HttpsProxyAgent } from "https-proxy-agent";
function agentFor(country, city) {
const parts = [process.env.PROXY_USER];
if (country) parts.push(`country-${country}`);
if (city) parts.push(`city-${city}`);
const user = encodeURIComponent(parts.join("-"));
const pass = encodeURIComponent(process.env.PROXY_PASS);
return new HttpsProxyAgent(
`http://${user}:${pass}@gateway.flameproxies.com:8080`
);
}
const resp = await axios.get("https://target.com", {
httpsAgent: agentFor("DE", "Berlin"),
});SOCKS5
Same parameter format, different scheme — and use socks5h so the proxy resolves DNS:
curl -x "socks5h://user-country-JP:pass@gateway.flameproxies.com:8080" \
https://ipcheck.flameproxies.comThe h matters for geo work. Without it, many clients resolve hostnames locally, from your location — so a CDN-backed site can return content from the wrong regional edge even though the IP is correct.
Desktop applications
For tools with no proxy support, the Flame Proxy Connector routes them at the OS level, with country and city selected in the interface and saved as switchable profiles.
Verify Before You Collect
Configuration is not confirmation. Check it:
curl -x "http://user-country-DE-city-Berlin:pass@gateway:8080" \
https://ipcheck.flameproxies.comConfirm three things in the response, not just one:
- Country code matches what you requested.
- City matches, if you targeted one.
- Organization looks like a consumer ISP, not a hosting provider — a residential request returning a datacenter organization means you did not get what you asked for, regardless of the location being right.
Build this into the job as a session-start gate:
def open_session(country, city=None, attempts=4):
for _ in range(attempts):
sid = uuid.uuid4().hex[:12]
px = proxies_for(country, city, sid)
try:
data = requests.get("https://ipcheck.flameproxies.com",
proxies=px, timeout=12).json()
except Exception:
continue
if data.get("country_code") != country:
continue
if city and city.lower() not in (data.get("city") or "").lower():
continue
return px, sid, data
raise RuntimeError(f"No verified session for {country}/{city}")This is the step that prevents the worst failure in country-targeted work: a session that silently landed in the wrong market produces wrong-market data with no error anywhere downstream. For a job comparing markets, that failure is total and invisible.
Match the Locale Bundle
An IP alone does not make a coherent visitor. Platforms cross-reference IP geolocation against language headers and timezone, and treat mismatches as a signal.
LOCALE = {
"US": ("en-US,en;q=0.9", "America/New_York"),
"GB": ("en-GB,en;q=0.9", "Europe/London"),
"DE": ("de-DE,de;q=0.9,en;q=0.8", "Europe/Berlin"),
"FR": ("fr-FR,fr;q=0.9,en;q=0.8", "Europe/Paris"),
"BR": ("pt-BR,pt;q=0.9,en;q=0.8", "America/Sao_Paulo"),
"JP": ("ja-JP,ja;q=0.9,en;q=0.8", "Asia/Tokyo"),
}
def headers_for(country):
lang, _ = LOCALE.get(country, ("en-US,en;q=0.9", "UTC"))
return {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/141.0.0.0 Safari/537.36",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": lang,
"Accept-Encoding": "gzip, deflate, br",
}For browser automation, set the timezone too:
context = browser.new_context(
locale="de-DE",
timezone_id="Europe/Berlin",
)A German IP sending en-US with a New York timezone is an incoherent profile, and some targets resolve that incoherence in ways that change what you receive — which defeats the purpose of targeting Germany.
Structuring a Multi-Country Job
The rule that prevents most multi-market bugs: never reuse a session across countries. Encode the country into the session identifier so it is structurally impossible.
from concurrent.futures import ThreadPoolExecutor
MARKETS = [
("US", "New York"),
("GB", "London"),
("DE", "Berlin"),
("BR", "Sao Paulo"),
]
def collect_market(country, city, urls, run_id):
px, sid, info = open_session(country, city)
hdrs = headers_for(country)
out = []
for i, url in enumerate(urls):
if i % 25 == 0 and i: # rotate on a request budget
px, sid, info = open_session(country, city)
try:
r = requests.get(url, proxies=px, headers=hdrs, timeout=30)
out.append({
"url": url,
"market": country,
"verified_city": info.get("city"),
"status": r.status_code,
"body": r.text,
})
except Exception as e:
out.append({"url": url, "market": country, "error": str(e)})
time.sleep(1.5 * random.uniform(0.6, 1.4))
return country, out
def run_all(urls, run_id):
with ThreadPoolExecutor(max_workers=len(MARKETS)) as pool:
futures = [
pool.submit(collect_market, c, city, urls, run_id)
for c, city in MARKETS
]
return {c: data for c, data in (f.result() for f in futures)}Note verified_city on every record — the location actually confirmed, not the one requested. Months later this is what distinguishes a genuine market difference from a session that quietly drifted.
Region Groups Instead of Country Lists
When you need broad coverage rather than specific countries, region targeting replaces the country list entirely:
proxies_for_region = lambda region, session=None: proxies_for(
country=None, session=session
) # with region-<group> appended by your builderGroups like tier-1 markets, EU, or LATAM become a single parameter rather than an enumerated list you rotate through and maintain. For coverage work where the economic or regulatory region matters more than the specific country, this collapses a lot of configuration.
Common Mistakes
UK instead of GB. Silent failure or fallback.
Assuming city targeting worked. Verify it. City accuracy varies by market and is the dimension most likely to silently fall back.
Reusing a session across markets. Encode country into the session ID.
Mismatched locale bundle. An IP without matching language and timezone is an incoherent visitor.
Not URL-encoding the password. Produces auth failures with correct credentials.
Testing one market and generalizing. Coverage quality in Germany predicts nothing about coverage in Brazil.
Over-specifying city precision. Narrowing to a city shrinks the usable pool, raising reuse and block rates. Request city-level only when the data varies within the country.
FlameProxies supports country and city targeting across 180+ countries, plus OS, ASN, and region-group targeting, on HTTP, HTTPS, and SOCKS5 with the same credential format — and with no concurrency cap, the per-market parallelism in the multi-country pattern above is limited by your own infrastructure rather than a thread allowance.