TechEarl

Force a New Tor Circuit on Demand with NEWNYM and the Control Port

How to rotate Tor circuits programmatically using the NEWNYM control signal — torrc setup with CookieAuthentication or HashedControlPassword, raw socket script, Python stem, and the rate limit that bites everyone.

Ishan KarunaratneIshan Karunaratne⏱️ 7 min readUpdated
Rotate Tor circuits with NEWNYM. torrc ControlPort setup with cookie or password auth, raw socket and Python stem scripts, the 10-second rate limit, and what NEWNYM does not change.

NEWNYM is the Tor control-port signal that tells the daemon "stop using current circuits for new streams, build fresh ones." It rotates the middle and exit relays of your circuit without disturbing in-flight streams, which is exactly what you want when scraping behind Tor and you've burned through a rate limit. The setup is one torrc block and a few lines of code — the surprise is the silent 10-second rate limit Tor enforces server-side. Below is the working torrc, a raw-socket one-liner, a stem script, and the gotcha list.

Step 1: open the control port in torrc

NEWNYM goes through the Tor control port, default 9051. Open it and pick an auth method.

text
# /etc/tor/torrc
ControlPort 9051

# Option A: cookie auth (preferred locally)
CookieAuthentication 1
CookieAuthFileGroupReadable 1

# Option B: password auth (use if controller runs elsewhere)
# HashedControlPassword 16:872860B76453A77D60CA2BB8C1A7042072093276A3D701AD684053EC4C

Cookie auth is cleaner on the same host because filesystem permissions handle who can read the cookie. Password auth is the right answer when the controller process is a different user (or a different machine over SSH-tunnel).

Generate the password hash with Tor itself:

bash
tor --hash-password 'yourpassword'
# 16:872860B76453A77D60CA2BB8C1A7042072093276A3D701AD684053EC4C

Paste the output (the whole 16:... string) into HashedControlPassword. Reload:

bash
sudo systemctl reload tor
# or
sudo pkill -HUP tor

Step 2: send NEWNYM

Three working approaches, in increasing order of cleanliness.

Raw socket (no dependencies, copy-paste)

If you have HashedControlPassword set, the password is sent in plaintext over the local socket (it's localhost — that's fine):

bash
{
  echo "AUTHENTICATE \"yourpassword\""
  echo "SIGNAL NEWNYM"
  echo "QUIT"
} | nc 127.0.0.1 9051

Expected output:

code
250 OK
250 OK
250 closing connection

Three 250s mean: authenticated, signal accepted, clean disconnect. Anything else is a problem.

For cookie auth, read the cookie file and pass it hex-encoded:

bash
COOKIE=$(xxd -c 256 -p /var/lib/tor/control_auth_cookie)
{
  echo "AUTHENTICATE $COOKIE"
  echo "SIGNAL NEWNYM"
  echo "QUIT"
} | nc 127.0.0.1 9051

The cookie file path depends on your DataDirectory. On most Debian/Ubuntu installs it's /var/lib/tor/control_auth_cookie. On macOS Homebrew it's /opt/homebrew/var/lib/tor/control_auth_cookie (Apple Silicon).

Python with stem

stem is the official Python controller for Tor. Cleaner error messages, type-safe signal names, and it handles cookie auth transparently:

bash
pip install stem
python
from stem import Signal
from stem.control import Controller

with Controller.from_port(port=9051) as controller:
    controller.authenticate()  # auto-detects cookie or env-provided password
    controller.signal(Signal.NEWNYM)
    print("New circuit signalled.")

If you're using password auth instead of cookie:

python
with Controller.from_port(port=9051) as controller:
    controller.authenticate(password="yourpassword")
    controller.signal(Signal.NEWNYM)

Use a Unix socket if you have ControlSocket set in torrc — same API, swap from_port for from_socket_file:

python
with Controller.from_socket_file("/var/run/tor/control") as controller:
    controller.authenticate()
    controller.signal(Signal.NEWNYM)

nyx (interactive)

nyx is the terminal Tor monitor (formerly arm). Run it, press m, choose "new identity" — it sends NEWNYM under the hood. Useful when you want to watch the circuit list update in real time. Install via apt install nyx on Debian/Ubuntu or brew install nyx on macOS.

Step 3: verify the circuit actually rotated

NEWNYM is fire-and-forget. Tor returns 250 OK regardless of whether new circuits build successfully. To prove it worked, check the exit IP before and after:

bash
# Before
torsocks curl https://check.torproject.org/api/ip
# {"IsTor":true,"IP":"185.X.X.X"}

# Send NEWNYM
echo -e "AUTHENTICATE \"yourpass\"\nSIGNAL NEWNYM\nQUIT" | nc 127.0.0.1 9051

# Wait at least 10 seconds (see rate limit below), then check
sleep 12
torsocks curl https://check.torproject.org/api/ip
# {"IsTor":true,"IP":"94.X.X.X"}

A different IP confirms the circuit rotated. Sometimes the new circuit picks the same exit by chance — try a couple of NEWNYMs to be sure.

The 10-second rate limit

This is what every NEWNYM tutorial omits. Tor enforces a minimum interval of MaxClientCircuitsPending * server-side guard, which in practice means at most one NEWNYM every ~10 seconds is honored by the relay set. Fire ten in a tight loop, and most are dropped silently.

If you need rotation faster than every ten seconds, run multiple Tor instances on different control and SOCKS ports, then round-robin NEWNYM across them:

text
# /etc/tor/torrc-a
SocksPort 9050
ControlPort 9051
CookieAuthentication 1
DataDirectory /var/lib/tor-a

# /etc/tor/torrc-b
SocksPort 9060
ControlPort 9061
CookieAuthentication 1
DataDirectory /var/lib/tor-b

Start both:

bash
tor -f /etc/tor/torrc-a &
tor -f /etc/tor/torrc-b &

Now you have two independent SOCKS proxies on 9050 and 9060, each with its own control port. Alternate which one your scraper hits per request, and rotate each on its own ten-second cadence. The effective rotation rate doubles, triples, scales.

What NEWNYM does and doesn't do

NEWNYM:

  • Marks all current circuits as dirty so new streams open on fresh circuits.
  • Builds new circuits with new middle and exit relays.
  • Returns immediately; circuit construction happens in the background.

NEWNYM does not:

  • Change your entry guard. Tor uses long-lived guards on purpose — rotating them on every NEWNYM would defeat their security properties. To force a new guard, you have to delete your state file in the DataDirectory and restart, which is slow and rarely what you actually want.
  • Close existing streams. Any TCP connection currently open keeps its circuit until the application closes it.
  • Affect hidden-service circuits. Those rotate on their own schedule.

If you genuinely need a new guard (compromise suspected, threat-model change), the correct fix is rotating the guard fingerprint in state, not abusing NEWNYM.

Per-stream circuit isolation: a different lever

Sometimes you don't want to wait ten seconds — you want a different circuit right now for one specific request. NEWNYM isn't the tool; stream isolation is. Set SocksPort to isolate by something:

text
SocksPort 9050 IsolateDestAddr IsolateDestPort IsolateSOCKSAuth

IsolateSOCKSAuth is the magic one: any unique SOCKS username/password pair gets its own circuit. So in Python:

python
import requests

def fetch(url, isolation_tag):
    proxies = {
        "http": f"socks5h://{isolation_tag}:x@127.0.0.1:9050",
        "https": f"socks5h://{isolation_tag}:x@127.0.0.1:9050",
    }
    return requests.get(url, proxies=proxies, timeout=30)

# Each unique isolation_tag gets a separate circuit
r1 = fetch("https://check.torproject.org/api/ip", "request-1")
r2 = fetch("https://check.torproject.org/api/ip", "request-2")
print(r1.json()["IP"], r2.json()["IP"])
# Two different exit IPs

This is the right answer for high-throughput scraping. NEWNYM is the right answer when you want to start over with a fresh circuit pool for everything.

FAQ

TagsTORNEWNYMControl PortstemPythonNetworkingPrivacyAnonymity
Share
Ishan Karunaratne

Ishan Karunaratne

Tech Architect · Software Engineer · AI/DevOps

Tech architect and software engineer with 20+ years across software, Linux systems, DevOps, and infrastructure — and a more recent focus on AI. Currently Chief Technology Officer at a tech startup in the healthcare space.

Keep reading

Related posts

Build an LLM agent with tool use. The agentic loop, tool-call formats on Anthropic / OpenAI / Gemini, JavaScript and Python code, common failure modes.

How to Build an LLM Agent with Tool Use

Build an LLM agent with tool use: the agentic loop, the tool-call format on Anthropic, OpenAI, and Gemini, runnable code in JavaScript and Python, plus the common failure modes.