SIP 401 vs 407
Short version: one 401 or 407 followed by a credentialled retry is not a failure — that is how SIP Digest authentication is supposed to look. The failure mode is the loop: challenge, credentialled retry, challenge again. And a loop has more causes than a wrong password.
401 vs 407: who is challenging you
The two codes carry the same mechanism but name a different challenger. A
401 Unauthorized comes from the server that will consume the request
itself — a registrar answering your REGISTER, or a UAS answering your INVITE. It
carries WWW-Authenticate and expects the retry to carry
Authorization. A 407 Proxy Authentication Required comes
from a proxy the request was passing through; it carries
Proxy-Authenticate and expects Proxy-Authorization. RFC 3261
reserves 401 for registrars and UASs and 407 for proxies — hence the shorthand,
right far more often than not: 401 means registration, 407 means a call.
Several proxies on the path may each challenge, so
Proxy-Authorization headers accumulate: each proxy takes the credentials
for its own realm and forwards the rest. Two requests are never challenged at all —
RFC 3261 section 22.1 rules out challenging a CANCEL (it cannot be resubmitted with
credentials), and an ACK cannot be answered, so it carries a copy of the INVITE's
credentials instead.
Digest auth on the wire
The challenge names a realm, supplies a server-chosen
nonce, and usually a qop and an algorithm.
The client never sends the password: it hashes
username:realm:password into one value, method:uri into
another, combines them with the nonce — plus, when the challenge names a
qop, a client nonce (cnonce) and a per-nonce request
counter (nc) — and returns the result in
response=. The server does the same arithmetic and compares. The scheme
is HTTP Digest — RFC 2617, revised by RFC 7616; RFC 8760 brought the SHA-256
variants to SIP, though in the field MD5 still dominates.
Two parameters do most of the diagnostic work. stale=true on a repeat
challenge means "your credentials were fine, but the nonce was not — take this fresh
one and retry without re-prompting for a password". And nc must
increment each time a nonce is reused — that is the replay check; a retransmission
keeps its nc, a new request must not.
One challenge is normal
Clients send the first request bare on purpose — they cannot compute a response before they have seen a nonce. The healthy shapes:
REGISTER → 401 (WWW-Authenticate: nonce N)
REGISTER + Authorization (nonce N) → 200 OK
INVITE → 407 (Proxy-Authenticate: nonce N)
ACK (ends that transaction)
INVITE + Proxy-Authorization, CSeq+1 → 100 / 180 / 200
On the INVITE side the 407 is a final response, so it is ACKed, and the credentialled INVITE is a new request — same Call-ID, CSeq incremented by one. Dashboards that count raw 4xx report a "failure" on every outbound call through a challenging proxy; filter these pairs out before believing an error rate.
What actually causes auth loops
The credentials really are wrong — but check which username
Still the most common cause, with a twist: most gear separates the auth
username from the user part of the From and To URIs, and it is the auth username
the server checks. A PBX trunk with the account number in the From but the wrong
value in the auth-user field loops on 407 against a perfectly correct password.
Usernames are often case-sensitive, and passwords containing & or
% get mangled by provisioning layers more often than anyone admits.
Stale nonces
Servers expire nonces — some after minutes, some after a single use. A client that
computes correctly but answers with a nonce the server no longer honours gets
challenged again, ideally with stale=true; a well-behaved client
silently retries with the new nonce and the loop converges. The pathological
version is a client replaying an old nonce on every new request, or a server
enforcing nc strictly against a client that always sends
nc=00000001. Either way, the tell is in the parameters, not the
password.
A cluster that does not share nonce state
Two registrars behind a round-robin load balancer, nonces held in local memory:
server A mints the challenge, server B receives the answer and has never seen that
nonce. Every response is rejected as stale no matter how correct it is. The
signature is unmistakable — an endless loop in which every challenge says
stale=true with a fresh nonce, while the client's responses are
properly formed.
Realm mismatch
The password hash is bound to the realm — username:realm:password — so
anything that changes the realm invalidates every stored hash. Servers that store
pre-computed hashes cannot simply rename their realm, and a failover server
presenting a different realm breaks clients that cached credentials against the
old one. If the realm in the challenge is not the
realm the client was provisioned with, no password will ever work.
Something rewrote the request in flight
The digest covers the request method and URI, and strict servers verify that the
uri= parameter in the credentials matches the Request-URI they actually
received. A SIP ALG on a router, or an SBC rewriting the Request-URI in transit,
silently breaks that comparison: the client signed one URI, the server received
another. Both ends are behaving correctly — the middlebox is the culprit.
Algorithm disagreement
Since RFC 8760 a server may offer several challenges, strongest first — SHA-256,
then MD5. An older client may ignore the unfamiliar challenge outright, or answer
it with MD5 arithmetic. Rare, but worth a glance at
algorithm= when everything else checks out.
Registration auth vs per-call auth
Registration authenticates one thing only: the binding between an address-of-record and a contact. It does not pre-authorise anything that follows, so most deployments challenge INVITEs separately — which is why a phone can register happily and still hit a 407 on every call. Carrier trunks often skip Digest entirely and authenticate by source IP address; there, a credential problem never appears as a 401/407 loop but as a straight 403 Forbidden. Many servers also convert repeated Digest failure into a 403 after two or three attempts, so the end of an auth loop and an outright ban look identical in the CDR.
What to check in the trace
- Confirm the retry is real: same Call-ID, CSeq incremented, and an
AuthorizationorProxy-Authorizationheader actually present. A client repeating the identical bare request never understood the challenge. - Compare the nonce in the credentials against the most recent challenge. A client replaying an older nonce is a client problem; a server refusing its own fresh nonce is a server problem.
- Read
stale=on the second challenge.stale=truemeans the credentials were accepted and the nonce was not — stop investigating the password. Every challenge stale with a fresh nonce, forever, points at a cluster not sharing nonce state. - Match
realm=in the credentials againstrealm=in the challenge, character for character. - Match
uri=against the Request-URI on the wire at the point of capture. A mismatch means something in between rewrote the request — go find the ALG or SBC. - Check
username=against what the server keys accounts on — where the auth-user-versus-From-user confusion becomes visible. - Watch how the loop ends. A 403 after a few rounds is the server giving up, often with the real reason in the reason phrase. Sudden silence, retransmissions and a local 408 mean a fail2ban-style blocker is now dropping your packets — the auth loop was the trigger.
hiccup pairs every challenge with its credentialled retry, collapses the healthy 401/407 handshakes out of the failure count, and flags nonce reuse, realm mismatches and stale loops as findings — next to the ladder. Self-hosted, free for individual users.
upload a trace