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 Karunaratne⏱️ 8 min readUpdated
Share thisCopied
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. The rate limit is purely client-side: Tor compiles in a MAX_SIGNEWNYM_RATE constant of 10 seconds, which means at most one NEWNYM every 10 seconds actually rotates your circuits. Extra signals fired inside that window aren't dropped by relays, they're coalesced and deferred locally by your own Tor process. Fire ten in a tight loop, and only the first takes effect now; the rest collapse into one delayed rotation.

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

See also

Sources

Authoritative references this article was fact-checked against.

TagsTORNEWNYMControl PortstemPythonNetworkingPrivacyAnonymity

Found this useful? Pass it on.

Copied

Ishan Karunaratne

Software Systems Architect · Senior Software Engineer · Engineering Leadership

Software systems architect and senior software engineer with more than two decades designing, building, and running production software, Linux systems, and DevOps infrastructure, and lately working AI into the stack. Now a CTO, though what I write here is drawn from the full arc of that work, across architecture, engineering, and operations, not any single job.

Keep reading

Related posts

grep -c counts matching lines, not occurrences. Use grep -o piped into wc -l for the true count, grep -rc for per-file counts, grep -vc to count non-matching lines, plus the macOS BSD vs GNU differences.

How to Count Matches with grep -c (and the Line-vs-Occurrence Trap)

grep -c counts matching LINES, not occurrences. A line with three hits still counts as 1. The fix is grep -o piped into wc -l, which puts every match on its own line first. Per-file counts, filtering out the :0 noise, counting non-matching lines, and the BSD vs GNU differences.