How to test digest auth with curl
Run curl -v --digest --user user:passwd https://host/private. The verbose transcript holds two requests. The first carries no credentials and earns a real 401 with a nonce; the second answers it with an MD5 response= value and earns the 200. Without --digest curl sends Basic and the server rejects it.
Why check this
Digest auth turns up on appliances, printers, camera firmware, older Java and IIS applications, and internal tools nobody has migrated. Run this check when a client cannot reach such an endpoint, when a proxy or gateway is put in front of one, and on any regression pass that touches the authentication layer.
The failure it prevents is specific. A load balancer that spreads requests across nodes breaks digest, because the node that issued the nonce is not the node that has to verify it. The symptom is intermittent 401 responses that nobody can reproduce, and the transcript below is how you show that the client answered the challenge correctly and a different node refused it. The reason is visible in step 1: the exchange is stateful across two requests, and basic auth is not.
The same transcript answers the question testers actually ask about digest. The password itself never travels. What travels is a hash over the password, the realm, the nonce, the method and the URI, so a wrong password and a correct one produce requests of the same shape.
Prerequisites
- curl 7.0 or later. No HTTP/2 build is needed. See the curl manual page for --digest.
- An endpoint that issues a Digest challenge. The examples use
httpbin.org/digest-auth/auth/user/passwd, which acceptsuserandpasswdwithqop=auth. - RFC 7616 for the meaning of
nonce,cnonce,nc,qopandstale.
Steps
- Step 1.
Run the exchange with
--digestand keep the whole transcript.curl -s -v --digest --user user:passwd https://httpbin.org/digest-auth/auth/user/passwd> GET /digest-auth/auth/user/passwd HTTP/2 > Host: httpbin.org > User-Agent: curl/8.21.0 > Accept: */* > < HTTP/2 401 < content-type: text/html; charset=utf-8 < content-length: 0 < www-authenticate: Digest realm="me@kennethreitz.com", nonce="cff74cbce4a8ffa33fcf624e4b0c4fe7", qop="auth", opaque="fb205ea3b186b62faca3e54852ab6eb1", algorithm=MD5, stale=FALSE < set-cookie: stale_after=never; Path=/ < > GET /digest-auth/auth/user/passwd HTTP/2 > Host: httpbin.org > Authorization: Digest username="user",realm="me@kennethreitz.com",nonce="cff74cbce4a8ffa33fcf624e4b0c4fe7",uri="/digest-auth/auth/user/passwd",cnonce="44a0091eb9bf79e69344085ed1e45319",nc=00000001,algorithm=MD5,response="ffdada36758f81ffb299416501fb6fb3",qop="auth",opaque="fb205ea3b186b62faca3e54852ab6eb1" > User-Agent: curl/8.21.0 > Accept: */* > < HTTP/2 200 < content-type: application/json < content-length: 47 < { "authenticated": true, "user": "user" }Read the two
noncevalues. The client echoed the one the server issued, unchanged. - Step 2.
Drop
--digestand send the same correct password.curl -s -v --user user:passwd https://httpbin.org/digest-auth/auth/user/passwd> Authorization: Basic dXNlcjpwYXNzd2Q= … < HTTP/2 401 < www-authenticate: Digest realm="me@kennethreitz.com", nonce="6a242b9e5971cec3b487b64cb76d78b3", qop="auth", opaque="1c115c2b54e865de28d588cc67910842", algorithm=MD5, stale=FALSEOne request, a correct password, and a
401. curl offered Basic, the server wants Digest, and neither side says so in plain words. - Step 3.
Run the exchange again with a wrong password and compare it with step 1.
curl -s -v --digest --user user:wrong https://httpbin.org/digest-auth/auth/user/passwd> Authorization: Digest username="user",realm="me@kennethreitz.com",nonce="5cfefba19684c0233ac7f7078fbc848c",uri="/digest-auth/auth/user/passwd",cnonce="0a356633cbcd3bafc00cbd221a92ad76",nc=00000001,algorithm=MD5,response="da7dc474906c143b802d46932a907058",qop="auth",opaque="f7cb4015b2408332f1b26003764a870a" … < HTTP/2 401 < www-authenticate: Digest realm="me@kennethreitz.com", nonce="38a5f3a7c594886e29e4bac72a465216", qop="auth", opaque="41671cfb500f39fd72b277803e497cb4", algorithm=MD5, stale=FALSE < set-cookie: last_nonce=5cfefba19684c0233ac7f7078fbc848c; Path=/The header has the same fields as the accepted one in step 1. Only
response=differs, and the stringwrongappears nowhere. - Step 4.
Let curl choose the scheme when you do not know it.
curl -s -v --anyauth --user user:passwd https://httpbin.org/digest-auth/auth/user/passwd> GET /digest-auth/auth/user/passwd HTTP/2 > User-Agent: curl/8.21.0 > < HTTP/2 401 < www-authenticate: Digest realm="me@kennethreitz.com", nonce="3a869a0cf647dd18529182f15dc20590", qop="auth", opaque="ce76ebc2b5a547458931293f3e6f6fb6", algorithm=MD5, stale=FALSE … > Authorization: Digest username="user",realm="me@kennethreitz.com",nonce="3a869a0cf647dd18529182f15dc20590",…,response="35fe26cbc670d7c8331b7bfca92b6b7e",qop="auth",opaque="ce76ebc2b5a547458931293f3e6f6fb6" … < HTTP/2 200
How to read the result
| What you see | What it means | What to do |
| --- | --- | --- |
| A 401, then a 200 on the repeat | The exchange worked as designed | Nothing. Two requests per call is normal for digest. |
| stale=FALSE on the second 401 | The nonce was accepted, the credentials were not | Fix the password. Retrying will not help. |
| stale=TRUE on the second 401 | The nonce expired between the two requests | Retry. A client that does not retry here loses requests under load. |
| Authorization: Basic … against a Digest challenge | --digest is missing | Add --digest, or --anyauth when the scheme is unknown. |
| A 401 on every second request, passing on the rest | The nonce is issued by one node and checked by another | Pin the session to a node or move the nonce store into shared state. |
| algorithm=MD5 with no SHA-256 alternative offered | The server implements RFC 2617 only | Record it. MD5 here is a key derivation, not a transport, so TLS still carries the check. |
Common mistakes
What to check next
- How to test basic auth with curl: the same endpoint shape with the credential sent before any challenge.
- How to check authorization header: reading what a request actually carries when several options are in play.
- How to test API authentication: the token schemes that replaced digest on modern services.
- How to check TLS version of a website: digest hashes the password and protects nothing else in the request, so the transport still has to be checked.
- How to check HTTP status code: reading the
401that opens the exchange without the verbose noise.
FAQ
Basic vs digest authentication, which should a service use?
Neither, for a new service. Basic hands the password to anyone who reads the traffic, so it needs TLS. Digest hashes the password with MD5 and needs TLS anyway, and its nonce state breaks across nodes. Token schemes avoid both problems.
How does digest auth work in one paragraph?
The server answers an unauthenticated request with 401 and a random nonce. The client hashes the password with the user name, the realm, that nonce, its own cnonce, a counter, the method and the URI, and repeats the request carrying only the hash. The server recomputes it.
Why does curl send two requests for one call?
curl has to see the challenge before it can compute the response, because the nonce comes from the server. That is why --digest produces a 401 in the transcript even on a call that succeeds, as step 1 shows.
Can I skip the first request?
Not reliably. The nonce comes from the server, so a client that has never been challenged has nothing to hash against. A client that caches a nonce from an earlier call can reuse it while the server still accepts it, and a test built on that is testing the server's leniency.
What does opaque do?
The server sends it in the challenge, and the client copies it back unchanged. Step 1 and step 4 show the same value in both directions. It carries server state, so a mismatch usually means the answer reached a different node.
Verified
Verified by Maks Vernycurl 8.21.0
Each output block is what the command above it printed on that date, on the host named in the step. Figures read from a live site move between runs. Compare the shape of the answer rather than the digits, and see the methodology for how a page is re-verified.
Related on this site
intermediate6 minpublished updated Maks Verny