WeProxy

Guide

DNS Leak Guide: How to Close WebRTC, Header, and DNS Leaks

14 min read

A DNS leak, a WebRTC candidate, or a header leak can expose the real address before the exit IP matters. Close the channel with socks5h, an ICE dump, and an echo test.

A DNS leak happens when the target hostname is resolved before the proxy tunnel exists. The HTTP request may leave through gw.weproxy.com.tr while the query still hits the office resolver, the home router, or the browser’s DoH endpoint. That log stores the real client IP and the name that was looked up. WebRTC ICE candidates and Forwarded headers open a second and a third channel in the same session.

This guide separates the TCP handshake from the resolution order, shows how socks5h differs from local DNS, and explains why browser UDP often stays outside an HTTP proxy profile. Examples use cURL, Python, and Node.js. The goal is to put the exit IP and the identity plane (DNS, ICE, headers) on the same path. A clean exit IP does not help while a leak is still open.

A DNS leak happens before the exit IP

Opening https://example.com is two jobs. Resolution asks which address the name maps to. Transport asks how TCP (and TLS) reaches that address. A proxy usually takes only the second job. If the first job stays on the client, you have a DNS leak.

The resolver sees the source IP of the query, the exact name (QNAME), and the type (A, AAAA, HTTPS). The target site does not see that log. The resolver operator does. Collection pipelines, transparency reports, or a misconfigured router turn that trace into an operational risk.

DNS over HTTPS does not close this by itself. DoH is still an HTTPS request. If that request bypasses the proxy profile, the resolver still sees the real client IP, plus the QNAME. If DoH itself goes through the proxy, the resolver sees the proxy exit. The question is not “is DNS encrypted?” It is “which IP sent the query?”

Practical test: pick a name you control, such as probe-20261008.example.com. Put the client behind the proxy, request that name once, and read the authoritative DNS log. If the source is not the expected exit IP, the DNS leak is real. Public “DNS leak test” pages illustrate the same idea. Your own log is the stronger evidence, because you can see which resolver answered.

TCP handshake and resolution order

The TCP three-way handshake (SYN, SYN-ACK, ACK) starts only after some address is known. With a proxy, that address is not always the origin. On an HTTP proxy the client opens TCP to the proxy first, then asks for a tunnel with CONNECT. SOCKS5 does the same with a control channel. The handshake with the origin runs inside the tunnel after the tunnel is up.

The order is roughly:

  1. The client resolves the proxy hostname (gw.weproxy.com.tr). That query may stay local. It is the gateway name, not the target site.
  2. The client opens TCP to the proxy and authenticates.
  3. The client either sends the hostname to the proxy or resolves it locally first.
  4. The proxy opens TCP to the origin (and resolves the name if step 3 sent a hostname).
  5. The client sends a TLS ClientHello inside the tunnel. SNI carries the origin name. That is metadata, separate from DNS.

If step 3 resolves locally, the DNS leak is finished before the origin handshake. Seeing TLS “through the proxy” does not erase that log line.

HTTP CONNECT leaves the hostname with the proxy

An HTTP proxy carries HTTPS with the CONNECT method. The client sends CONNECT example.com:443 HTTP/1.1. The hostname goes to the proxy. A healthy client does not turn that name into an A record first. The proxy opens TCP to the origin, returns 200 Connection Established, and the client finishes TLS inside the tunnel.

In that model the target’s DNS query should not hit the client resolver. If it does, the library called getaddrinfo before CONNECT. Some older HTTP clients claim a proxy is configured and still resolve locally. Confirm with a log. Do not trust the protocol label alone.

On plain HTTP the client sends an absolute URL: GET http://example.com/path. Resolution is still on the proxy. Most daily work is the TLS CONNECT path.

The WeProxy gateway expects a username and password on gw.weproxy.com.tr:8989. If the password contains @, :, or /, percent-encode it inside the URL. Otherwise the client treats the username as the host and CONNECT never starts. That is a connection failure, not a leak, and teams mix the two up.

SOCKS5, local DNS, and socks5h

SOCKS5 does not inspect application bytes. RFC 1928 carries the destination as IPv4 (ATYP 0x01), a domain name (ATYP 0x03), or IPv6 (ATYP 0x04). A domain name means the proxy resolves it. An IPv4 address means the client already did.

socks5h is not a separate protocol. It is the scheme curl and many libraries use for “send the hostname to the proxy.” In curl, --socks5 resolves locally. --socks5-hostname and socks5h:// use ATYP 0x03. Same password, same port, different DNS path.

Do not confuse this with UDP. Classic DNS is UDP/53, but remote DNS over SOCKS5 does not require UDP ASSOCIATE. The client does not emit the query. It sends the hostname on the control channel, and the proxy uses its own resolver. UDP ASSOCIATE is for games, voice, and some STUN paths. The UDP proxy guide and the HTTP vs SOCKS5 comparison keep those layers apart.

Static residential packages in Turkey offer HTTP and plain SOCKS5. UDP ASSOCIATE is not on those packages. That does not block remote DNS via socks5h. SOCKS5 without UDP can still carry a hostname as ATYP 0x03. Jobs that need UDP should follow the location matrix. Tying a DNS leak test to a UDP badge produces a false negative.

Why a WebRTC leak bypasses the proxy

WebRTC is how the browser gathers candidate IPs for media. RTCPeerConnection builds a host candidate from local interfaces, a server-reflexive candidate through STUN, and a relay candidate through TURN. Host and server-reflexive candidates are UDP sockets that do not pass through the page’s HTTP proxy. A profile that says “all browser traffic” often tunnels only TCP 80/443.

The page exit can look residential while the ICE line still shows the home or office IP. The peer, or the page’s own script, can read that candidate. RTCPeerConnection on MDN documents the candidate types. This is the API’s design, not a random glitch.

Run this once in the profile you ship. If typ host or typ srflx is not your proxy exit, the WebRTC leak is open.

const pc = new RTCPeerConnection({
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
})
pc.createDataChannel('leak-check')
pc.onicecandidate = (event) => {
  if (event.candidate) console.log(event.candidate.candidate)
}
const offer = await pc.createOffer()
await pc.setLocalDescription(offer)

The fix is a policy, not a slogan. Set Chromium WebRtcIPHandlingPolicy to disable_non_proxied_udp so UDP that is not proxied stops producing candidates. An enterprise policy or the shortcut flag --force-webrtc-ip-handling-policy=disable_non_proxied_udp does the same job. Repeat the dump afterward. The list should be empty or relay-only. A flag you turned on is not evidence until the ICE candidates change.

Anti-detect browsers can bake this policy into a profile. Still dump ICE once per profile. Flags fall off when a profile is cloned.

Header leaks and the IPv6 side path

DNS and WebRTC expose the client at the network layer. A header leak is the proxy, or the client, writing the client IP onto the request that reaches the origin. A proper forward proxy does not append the client address. Do not confuse a reverse proxy (nginx, a CDN) with a forward proxy. A reverse proxy adding X-Forwarded-For is its own architecture. Your exit proxy forwarding that value to the origin is a separate bug.

Forwarded and X-Forwarded-For

Headers to inspect: X-Forwarded-For, Forwarded (RFC 7239), X-Real-IP, Via, Client-IP. Send one request through the proxy to an echo endpoint. If the reflection shows your office IP, an internal address from the browser profile, or a Via chain the proxy added, write it down.

curl -sS -x "http://USER:[email protected]:8989" \
  "https://httpbin.org/headers"

USER and PASSWORD are the credentials from the panel. If those headers are absent, this request is not showing a header leak. If they are present, split “our code added it” (an axios interceptor, a browser extension) from “a middlebox added it.” Libraries sometimes set X-Forwarded-For: 10.x to be helpful. Origins treat that value as real.

Happy Eyeballs and IPv6

If the client has a working IPv6 path and the proxy profile is IPv4 only, Happy Eyeballs (RFC 8305) can race AAAA against A. IPv6 that goes straight to the ISP never enters the proxy. Symptom: an IPv4 echo shows the proxy, an IPv6 echo shows your home prefix.

Compare both states with the IPv6 checker, profile on and profile off. If the job does not need IPv6, disable IPv6 for that profile. That is the narrow fix. If the job needs IPv6, the exit has to be IPv6 as well. Otherwise traffic you believe is proxied is only half proxied. IPv6 proxy exits cover that second case. They do not, by themselves, close an IPv4 leak.

Compare the leak channels

ChannelWhat leaksTypical causeClose it
DNSReal IP + QNAMELocal resolution on SOCKS5, DoH outside the proxysocks5h / --socks5-hostname, put DoH in the tunnel
WebRTCHost and srflx IPBrowser UDP outside the proxydisable_non_proxied_udp, ICE dump
HeaderClient IP at the originX-Forwarded-For, Forwarded, ViaEcho test, remove client interceptors
IPv6ISP prefixHappy Eyeballs, IPv4-only proxyDisable IPv6 on the profile, or use an IPv6 exit
SNIOrigin name (not your IP)TLS ClientHelloSeparate metadata; do not call it a DNS leak

Do not mistake SNI for a DNS leak. SNI carries the origin name inside TLS, and the origin already expects that name. The problem is a resolver the origin never sees, logging the same name next to your real IP.

Step-by-step tests with cURL, Python, and Node.js

Credentials below are placeholders. First note your address with the proxy off, using the My IP tool. Repeat with the proxy, or call api.ipify.org. If both addresses match, the tunnel is not up. Do not start the DNS test yet.

cURL

# HTTP proxy: CONNECT carries the hostname
curl -sS -x "http://USER:[email protected]:8989" https://api.ipify.org
echo

# SOCKS5, local DNS — leak risk
curl -sS --socks5 "USER:[email protected]:8989" https://api.ipify.org
echo

# SOCKS5, remote DNS
curl -sS --socks5-hostname "USER:[email protected]:8989" https://api.ipify.org
echo

The second and third commands can print the same exit IP. The DNS difference does not show up on an IP echo. It shows up in the authoritative log of a name you control. If the third command logs the exit IP and the second logs the client IP, the diagnosis is done.

Python

requests writes the hostname into CONNECT when HTTPS uses an HTTP proxy. SOCKS5 needs requests[socks] (PySocks). socks5:// resolves locally on many installs. Remote DNS needs the socks5h:// scheme.

import requests

proxies = {
    "http": "socks5h://USER:[email protected]:8989",
    "https": "socks5h://USER:[email protected]:8989",
}
response = requests.get("https://api.ipify.org", proxies=proxies, timeout=30)
print(response.text)

If an HTTP proxy is enough, use the http:// scheme and skip PySocks. The Python proxy integration walks through the same gateway format. With trust_env=True, an HTTP_PROXY environment variable overrides the dict in your code. Set Session.trust_env = False during the test, or you will debug “the file says socks5h, the process uses HTTP.”

Node.js

Node’s built-in fetch does not pick up the system proxy. For an HTTP proxy, undici’s ProxyAgent puts the hostname on CONNECT. For SOCKS5 with remote DNS, use socks5h:// with socks-proxy-agent so the agent sends ATYP 0x03. A wrapper that calls dns.lookup and then connects to the IP brings the leak back.

import { ProxyAgent, fetch } from 'undici'

const proxy = new ProxyAgent('http://USER:[email protected]:8989')
const response = await fetch('https://api.ipify.org', { dispatcher: proxy })
console.log(await response.text())

The Node.js proxy integration uses the same gateway shape. An agent injected with global-agent or NODE_OPTIONS can shadow the ProxyAgent in the file. Run the test in a clean process.

Rate limits, sticky sessions, and pools

After the leak is closed, the same exit IP still hits target rate limits. 429 and Retry-After (HTTP semantics live in RFC 9110) show up when too much concurrency sits on one address. A sticky session keeps cookies and local storage stable. It also piles the counter onto that IP. A per-request rotating pool spreads the counter and breaks session continuity.

Do not mix the two problems. Rotation does not close a DNS leak. Every new exit still leaves your real IP on the resolver log. Fix DNS and WebRTC first, then the pool policy.

import time
import requests

def get_with_backoff(url, proxies, attempts=5):
    delay = 1.0
    response = None
    for _ in range(attempts):
        response = requests.get(url, proxies=proxies, timeout=30)
        if response.status_code != 429:
            return response
        retry_after = response.headers.get("Retry-After", "")
        time.sleep(float(retry_after) if retry_after.isdigit() else delay)
        delay = min(delay * 2, 30)
    return response

Backoff is not a way to “beat” a block. If the target returns an account lock or an empty body instead of 429, a larger pool amplifies the mistake. Lower concurrency, honor Retry-After, and pick geography only when the job actually needs that exit.

For steps that need higher trust, a rotating residential IP pool fits. When the same identity must stay for hours, static residential proxy infrastructure keeps one exit fixed. Jobs with weak defenses and tight latency fit datacenter proxy options. Pick the product after the leak test. Channel first, product second.

For Turkey-targeted work, a Turkey exit keeps the site’s geo layer and your DNS log telling the same country story. Location mapping lives on the Turkey proxy locations page. Pointing a Turkey exit at a site in another country is not a protocol error. It is an inconsistency you do not need unless the job requires it.

GDPR, KVKK, and robots.txt

An IP address is personal data when it can identify a person. In the EU, that definition sits in Regulation 2016/679 on EUR-Lex. In Turkey, Law 6698 (KVKK) is published on mevzuat.gov.tr. This guide is not legal advice. A DNS log and a WebRTC candidate can carry your real IP to a third-party resolver or to a page script. If the data you collect includes other people’s IPs, purpose and retention need their own review.

robots.txt is not an access-control list. RFC 9309 defines it as a voluntary crawl preference. Being able to open a URL through a proxy does not lift a disallow. The target’s terms, personal-data rules under GDPR or KVKK, and the WeProxy acceptable use policy apply together. Automation stays on surfaces that are public and allowed.

Checklist on a WeProxy exit

  1. Record the proxyless IP with My IP.
  2. Repeat over HTTP CONNECT or socks5h. Do not continue if the address did not change.
  3. Read the authoritative DNS log for a name you control. A source that is not the exit means local resolution.
  4. Dump ICE in the browser. If srflx is your home IP, apply the WebRTC policy and dump again.
  5. Look for X-Forwarded-For and Forwarded on an echo endpoint.
  6. If IPv6 is on, repeat the IPv6 check inside the proxy profile.
  7. On 429, fix concurrency and Retry-After first. Do not try to close a DNS leak by buying a larger pool.
  8. After the test, scrub credentials from shell history and CI logs. A password inside a URL turns the leak test into its own incident.

Once the DNS leak is closed, the WebRTC candidate matches the proxy, and the origin request does not carry the client IP, the exit IP is the only identity plane. Sticky versus rotating, and residential versus datacenter, are useful questions after that. Changing products in the reverse order does not change what the resolver logged.

Get Started

Test Your Proxy Setup Today

Check connectivity with free tools or talk to sales to pick the right package.