Bcrypt Cost Factor: How Many Rounds in 2026?
The short answer for 2026: start at a cost factor of 12 and adjust from there based on your own server. If you only remember one number, remember 12. But the real answer is "whatever makes one hash take roughly a quarter of a second on the hardware that actually serves your logins," and the only way to know that is to measure it. This article walks through what the cost factor means, why each step doubles the work, how to benchmark it, how to raise it over time without breaking existing users, and the one gotcha that silently weakens long passwords.
What the cost factor actually controls
Bcrypt's "rounds" are not a count of iterations the way they are in PBKDF2. The cost factor is a base-2 logarithm of the work: bcrypt runs its expensive Blowfish key-setup step 2^cost times. The algorithm comes from Niels Provos and David Mazieres' 1999 USENIX paper "A Future-Adaptable Password Scheme," and that adaptability is the whole point: as CPUs get faster, you increase one small number and the function gets exponentially more expensive for attackers.
This is why the difference between cost 10 and cost 12 is not "20% more work." It is four times more work, because each increment doubles it:
- Cost 11 = 2x the work of cost 10
- Cost 12 = 4x the work of cost 10
- Cost 13 = 8x the work of cost 10
- Cost 14 = 16x the work of cost 10
So when you raise the cost factor by 1, you exactly double both the time it takes you to verify a login and the time it takes an attacker to test each guess. That symmetry is what makes the parameter so powerful, and why "just one more" is a real decision.
A practical timing table
Because the work doubles per step, the timing curve is easy to reason about: pick the cost where one hash lands near your latency budget, and every step up or down halves or doubles it. The exact milliseconds depend entirely on your CPU and your bcrypt implementation, but the relationships are fixed:
| Cost factor | Relative work | Approx. time (illustrative) |
|---|---|---|
| 10 | 1x baseline | ~60-90 ms |
| 11 | 2x | ~120-180 ms |
| 12 | 4x | ~250-350 ms |
| 13 | 8x | ~500-700 ms |
| 14 | 16x | ~1-1.4 s |
Treat the millisecond column as a shape, not a promise. On a fast modern server, cost 12 often lands near the widely used 250 ms operational target; on a small shared VM it might be slower. The point of the table is the doubling, not the absolute numbers, which is exactly why you must benchmark rather than copy a figure off the internet.
Why 12, and what OWASP says
The OWASP Password Storage Cheat Sheet sets a baseline minimum work factor of 10 for bcrypt and, more importantly, tells you to set that factor as high as your verification server's performance will allow so authentication stays acceptably fast while remaining expensive to brute-force. The common operational target teams aim for is on the order of 250 milliseconds per hash: slow enough to make offline cracking painful, fast enough that a login does not feel sluggish and a burst of sign-ins does not exhaust your CPU. Cost 12 is a sensible default that usually lands in that neighborhood on 2026-era server CPUs, which is why "start at 12" is good advice rather than a magic constant. It is also where the ecosystem has converged: PHP 8.4 raised its built-in default bcrypt cost from 10 to 12.
Do not go below 10 for production user passwords, and be cautious going above 13-14: hashing is CPU-bound, so a too-high cost turns a login spike into a denial-of-service against yourself. It is also worth knowing that OWASP now treats bcrypt as a solid choice for existing systems but recommends Argon2id as the first pick for new applications.
Benchmark it on your own hardware
Never ship a cost factor you have not measured on the machine that will run it. Here is a minimal Node.js benchmark using the standard bcrypt library; run it on your actual server, not your laptop:
const bcrypt = require('bcrypt');
async function timeCost(cost) {
const start = process.hrtime.bigint();
await bcrypt.hash('benchmark-password', cost);
const ms = Number(process.hrtime.bigint() - start) / 1e6;
console.log(`cost ${cost}: ${ms.toFixed(0)} ms`);
}
(async () => {
for (let c = 10; c <= 14; c++) await timeCost(c);
})();
Pick the highest cost factor whose timing stays under your latency budget for a single hash, then leave headroom for concurrency. If you want to feel the exponential curve interactively before writing code, our password hashing tool has a built-in benchmark mode that times the work factor across settings on your current device; it runs entirely in your browser, so nothing is sent anywhere. (Note: that tool uses PBKDF2-SHA256 rather than bcrypt itself, but the doubling-cost intuition it demonstrates is identical.)
The 72-byte truncation gotcha
Bcrypt only processes the first 72 bytes of its input. Anything beyond byte 72 is silently ignored, which means two long passphrases that share the same first 72 bytes will produce matching hashes. This is a consequence of the underlying Blowfish key schedule, not a bug in any one library. It bites hardest with multibyte UTF-8 input, where a "60-character" password can already exceed 72 bytes, and with the common but dangerous pattern of "pepper plus password" concatenation that can push real password material past the cutoff.
The safe fix is to pre-hash the input with a keyed hash before bcrypt. Do not simply run the password through plain SHA-256 and base64-encode it: feeding an unsalted fast hash into bcrypt enables a real attack called password shucking, where a cracker substitutes a hash already sitting in a breach database and reduces the problem to cracking the inner SHA-256. OWASP's recommended construction is bcrypt(base64(hmac-sha384(password, pepper))): HMAC keys the pre-hash with a secret pepper that an attacker does not have, base64 keeps the result well under 72 bytes and free of NUL bytes (some implementations truncate at the first NUL), and the pepper is stored outside the database, ideally in app config or an HSM. Apply pre-hashing consistently on both sign-up and login, and fold it into any migration so old hashes can be re-derived. If you cannot pre-hash, at minimum enforce a maximum password length your users can actually hit and document the 72-byte limit for your team.
Migrating the cost factor over time
Because hardware keeps getting faster, today's "expensive" becomes tomorrow's "cheap." Plan to raise the cost factor every couple of years, and never try to bulk-rehash a user table, because you do not have anyone's plaintext password. The correct pattern is to rehash transparently on the next successful login:
- User submits their password; you verify it against the stored hash as normal.
- On a successful verify, read the cost factor embedded in the stored hash (it sits right in the string, for example the
12in$2b$12$...). - If that cost is below your current target, you already have the plaintext in hand for this one request, so hash it again at the new cost and overwrite the stored value.
Many libraries expose a helper so you do not parse the string by hand: Node's bcrypt has getRounds(hash) to read the embedded cost, and PHP offers password_needs_rehash() to compare a stored hash against your current options in one call. Over a few weeks of normal traffic, your active users migrate themselves with zero password resets and no downtime; dormant accounts upgrade whenever they return. This same login-time pattern also lets you migrate algorithms entirely if you ever move to Argon2, which we cover in our comparison of bcrypt, Argon2, scrypt, and PBKDF2.
One last operational note: pair a sensible cost factor with sensible inputs. A high work factor protects a stolen hash database, but it does nothing if users pick guessable passwords. Encourage long, unique secrets and check them with a password strength checker before they ever reach your hashing code.
Frequently Asked Questions
Start at a cost factor of 12 and tune from there. The goal is for one hash to take roughly 250 milliseconds on the server that handles your logins. Benchmark cost 10 through 14 on that actual hardware and pick the highest value that stays within your login latency budget, leaving headroom for concurrent sign-ins. PHP 8.4 made 12 its default cost, reflecting where the ecosystem has settled.
The cost factor is a base-2 exponent, so each increment doubles the work. Cost 12 is exactly twice the work of cost 11 and four times that of cost 10. Raising the factor by one doubles both your verification time and an attacker's per-guess cost, which is why small changes have large effects.
The limit comes from bcrypt's Blowfish-based key schedule: it processes at most 72 bytes of input and silently ignores the rest, so two long passwords sharing the same first 72 bytes hash identically. This matters with multibyte UTF-8 or pepper concatenation. The safe fix is to pre-hash with a keyed HMAC and a secret pepper, then base64-encode, applied consistently on sign-up and login. Avoid plain unsalted SHA-256 pre-hashing, which enables password shucking.
Rehash transparently on login. When a user signs in successfully, you briefly have their plaintext, so read the cost embedded in the stored hash and, if it is below your current target, hash again at the higher cost and overwrite the record. Node's bcrypt exposes getRounds() and PHP offers password_needs_rehash() for this. Active users upgrade themselves over time with no resets and no downtime.
No. Bcrypt is CPU-bound, so an excessively high cost can turn a burst of logins into a self-inflicted denial of service. Going above 13 or 14 on typical servers risks slow logins and CPU exhaustion. Choose the highest cost that meets your latency budget under realistic concurrency, not the highest possible number.