How big is an ML-DSA JWT?
With a typical 279-byte header and claims, an ML-DSA-65 JWT is 4,692 bytes, an ML-DSA-44 JWT 3,507 bytes, and an ML-DSA-87 JWT 6,450 bytes. The same token signed with ES256 is 366 bytes.
ML-DSA-65 and ML-DSA-87 tokens no longer fit in a browser cookie (4,096 bytes), and that limit can't be raised.
JWT size by signing algorithm
A compact JWS is base64url(header).base64url(claims).base64url(signature). The signature segment dominates once you switch to a post-quantum algorithm. Base64url (no padding) turns n bytes into ⌈4n/3⌉ characters.
| Algorithm | Signature | Base64url signature | Whole JWT (279 B header + claims) | vs. ES256 token |
|---|---|---|---|---|
| EdDSA (Ed25519) | 64 | 86 | 366 | 1.0× |
| ES256 (P-256) | 64 | 86 | 366 | 1.0× |
| RS256 (RSA-2048) | 256 | 342 | 622 | 1.7× |
| ML-DSA-44 | 2,420 | 3,227 | 3,507 | 9.6× |
| ML-DSA-65 | 3,309 | 4,412 | 4,692 | 13× |
| ML-DSA-87 | 4,627 | 6,170 | 6,450 | 18× |
| SLH-DSA-128s | 7,856 | 10,475 | 10,755 | 29× |
Bytes. Header and claims vary by issuer: add or subtract the difference from your own tokens. The signature segment is exact.
Will it fit?
Bearer tokens travel in Authorization headers (22 bytes of prefix) and session tokens in cookies. Defaults for common servers:
| Limit | Bytes | Type | ML-DSA-44 | ML-DSA-65 | ML-DSA-87 |
|---|---|---|---|---|---|
Browser cookie session=<JWT> (RFC 6265) | 4,096 | hard | fits | +604 | +2,362 |
nginx large_client_header_buffers 4 8k (per header line) | 8,192 | soft | fits | fits | fits |
Apache LimitRequestFieldSize (per header) | 8,190 | soft | fits | fits | fits |
Node.js --max-http-header-size (all headers) | 16,384 | soft | fits | fits | fits |
Overflow in bytes: the token plus its session= cookie name or Authorization: Bearer prefix. nginx first tries client_header_buffer_size (1k by default) and falls back to the large buffers, so that smaller setting doesn't reject long tokens. Node.js counts every header on the request against one budget, so real headroom there is smaller than shown.
What to do about it
- Cookies: move the token server-side. Keep a 32-byte session ID in the cookie and the signed token in your session store. This is the fix Tokenkeep generates for cookie breaches.
- Headers: widen the buffers deliberately. Size them for the token plus every co-tenant header, with headroom (Tokenkeep's policy is +2 KiB and +50%), on every proxy hop: CDN, load balancer, ingress and app server.
- Consider reference tokens. An opaque token that's introspected server-side keeps the PQC signature off the wire entirely.
- Test the whole path. A token that passes nginx can still be rejected by the next hop. Scan the configs together, not one at a time.
Find every limit these sizes break in your stack
Paste a migration, an nginx.conf or an mbedTLS header into the checker, or ask us for a free scan of the whole repository.