DocsGetting started
TCP/IP fingerprints
Every connection sends a Windows 11 TCP/IP fingerprint by default. Add a suffix to your username to send macOS, iOS or Linux instead.
Why your OS shows up in traffic
Operating systems don’t open connections in quite the same way. Each has its own defaults for the handshake: how many router hops a packet may cross, how much data it offers to accept before an acknowledgement, and which TCP options it lists, in what order. Tools such as p0f read those defaults and name the system behind them.
A proxy opens a fresh connection to the site for you, so the site reads the proxy server’s defaults instead of yours. If a browser announces itself as Windows while the handshake looks like a Linux server, anti-bot systems take note.
Pine lets you decide which system your connections look like, so the handshake and the User-Agent agree.
Choose an OS
The choice lives in your username. Leave it as it is for Windows 11, or add a suffix for another system:
| OS | Add to your username | Pick it for User-Agents with |
|---|---|---|
| Windows 11 | nothing, it’s the default | Windows NT |
| macOS | -os-macos |
Macintosh or Mac OS X |
| iOS | -os-ios |
iPhone or iPad |
| Linux | -os-linux |
X11; Linux |
So pine_4k2r connects as Windows, and pine_4k2r-os-linux connects as Linux. Nothing else changes: the same host, port and password, at no extra cost, over HTTP, HTTPS or SOCKS5.
Because the choice travels with the login, it applies to each connection on its own. Two scrapers can share one IP and still present different systems.
HTTP libraries like curl and python-requests don’t claim an OS at all. If you give them a browser User-Agent, give them the matching suffix as well.
Set it in your client
# A Linux User-Agent over SOCKS5, with the Linux profile to match
curl --proxy socks5h://USERNAME-os-linux:PASSWORD@HOST:PORT \
-A "Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0" \
https://api.ipify.org
import requests
# Pick the suffix from the User-Agent, so the two can't drift apart.
SUFFIXES = {"Windows NT": "", "Macintosh": "-os-macos", "iPhone": "-os-ios", "iPad": "-os-ios", "X11; Linux": "-os-linux"}
def proxies_for(user_agent):
suffix = next((s for marker, s in SUFFIXES.items() if marker in user_agent), "")
url = f"http://USERNAME{suffix}:PASSWORD@HOST:PORT"
return {"http": url, "https": url}
safari = ("Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 "
"(KHTML, like Gecko) Version/17.5 Safari/605.1.15")
reply = requests.get("https://api.ipify.org", headers={"User-Agent": safari},
proxies=proxies_for(safari), timeout=20)
// Playwright: the profile goes in the proxy username, the User-Agent in the context.
import { chromium } from "playwright";
const browser = await chromium.launch({
proxy: { server: "http://HOST:PORT", username: "USERNAME-os-macos", password: "PASSWORD" },
});
const context = await browser.newContext({
userAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_5) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15",
});
How the systems differ
These are the defaults each profile reproduces, the same ones fingerprinting tools compare:
- Windows starts packets with a TTL of 128, offers a 65,535-byte window, and leaves TCP timestamps off. Its options run: mss, nop, wscale, nop, nop, sackOK.
- macOS and iOS start at a TTL of 64 with the same window, turn timestamps on, and close the option list with an end-of-list marker. Both systems use one network stack, which is why their profiles look alike.
- Linux also starts at 64 with timestamps on, but offers a 64,240-byte window and lists SACK second: mss, sackOK, ts, nop, wscale.
Confirm it worked
Any service that echoes your handshake back will show the profile at work. tls.peet.ws is a free one, and its /api/all reply includes a tcp_syn object under tcpip. This loop prints the key fields for three profiles side by side:
for os in "" -os-macos -os-linux; do
printf '%-10s' "${os:-windows}"
curl -s -x "http://USERNAME$os:PASSWORD@HOST:PORT" https://tls.peet.ws/api/all \
| jq -c '.tcpip.tcp_syn | {window, timestamps, options: .option_order}'
done
Each line should match the list above. The TTL arrives a little lower than its starting value, because every router on the way takes one off.