Keycloak Brute-Force Protection’s Blind Spot: IP Limits and Lockout DoS

Guilliano Molaire Guilliano Molaire 10 min read

Last updated: July 2026

Keycloak’s built-in brute-force protection counts failed logins per user account and temporarily (or permanently) locks that specific account after too many failures. This design has two well-known gaps: it does virtually nothing to stop password spraying — where an attacker fires one guess per account across thousands of accounts from a single IP — and account lockout itself can be weaponized as a denial-of-service to lock out legitimate users before they ever reach the login page. Closing these gaps requires layering IP-based rate limiting at a WAF or reverse proxy in front of Keycloak, tuning lockout thresholds carefully to limit DoS exposure, and combining the result with MFA or passwordless authentication so credential guessing becomes largely irrelevant.


How Keycloak brute-force detection actually works

Keycloak’s brute-force protection lives in Realm Settings > Security Defenses > Brute Force Detection. Enable it and you unlock a set of per-account counters and timers:

Setting What it does
Max Login Failures Number of consecutive failures before the account is locked
Wait Increment How much lockout time grows with each additional failure after the threshold
Max Wait Ceiling on per-failure lockout duration (e.g., 15 minutes)
Quick Login Check Minimum time that must pass between two consecutive logins before the second is counted as a brute-force attempt
Minimum Quick Login Wait How long the account is locked when quick-login protection triggers
Failure Reset Time How long before the failure counter resets if no new failures occur
Permanent Lockout When enabled, a locked account stays locked until an admin manually unlocks it

In Keycloak 26.x, these settings are realm-scoped and apply uniformly to all accounts in that realm. There is no built-in concept of a per-IP counter, a per-IP lockout, or a CIDR-level rate limit. The protection is entirely account-centric.

When a login fails, Keycloak increments the failure counter stored against the user record. When the counter reaches Max Login Failures, the user is either temporarily locked (if Permanent Lockout is off) or permanently locked (if it is on). Events are emitted to the event log — LOGIN_ERROR with error=invalid_user_credentials or error=account_disabled — which are queryable via the Admin Events API and important for your Keycloak audit and event logging strategy.

This is a reasonable defence against a single attacker targeting a single known account with repeated guesses. It is not designed for, and does not stop, the two attack patterns described below.


The two gaps you need to understand

Gap 1 — Password spraying bypasses per-account detection

Password spraying is the practice of attempting one (or a small number of) very common passwords against a large list of usernames. The attacker’s goal is to stay under each account’s Max Login Failures threshold while working through the entire user population.

A concrete example: if your realm is configured with Max Login Failures = 5, an attacker who sends exactly four failed attempts per account never triggers lockout — for any account. Because Keycloak tracks failures per account independently, the attacker can rotate through 10,000 accounts with four guesses each and the system treats every series as within-threshold.

From Keycloak’s perspective, each account has only four failed logins. None are locked. No alarm fires unless you have external monitoring watching for bursts of LOGIN_ERROR events across many different usernames from a single IP.

This pattern is well-documented in the OWASP Credential Stuffing Prevention Cheat Sheet, which classifies password spraying as a variant of credential-based attacks that per-account controls alone cannot stop.

Gap 2 — Lockout as a denial-of-service vector

The flip side of per-account lockout is that any actor who knows (or can enumerate) valid usernames can deliberately trigger lockout for those accounts. By sending Max Login Failures + 1 requests with wrong passwords, the attacker locks out the legitimate user.

With Permanent Lockout enabled, the legitimate user cannot log in again until an admin manually unlocks the account through the Admin Console or Admin API. Under Temporary Lockout, the window depends on your Wait Increment and Max Wait settings — but during that window, the real user is effectively denied service.

This is not theoretical. CVE-2024-4629, patched in Keycloak 24.0.5 and 25.0.1, described a brute-force bypass where user enumeration in the login flow made it straightforward for an unauthenticated attacker to identify valid accounts and then target them. While the specific bypass was patched, the structural design of per-account lockout means the DoS surface is inherent to any threshold-based account lockout scheme — not a bug but a trade-off.


Threat-by-threat breakdown

The table below maps common credential attack types to what Keycloak’s built-in protection handles and what it does not.

Threat Keycloak built-in stops it? Gap Mitigation
Single IP, single account, many guesses Yes — account locked after threshold None if IP is static Built-in is sufficient; pair with MFA
Password spray (1 guess × many accounts) No — never exceeds per-account threshold Core gap IP-based rate limit at WAF/proxy
Credential stuffing (breach list replay) Partial — slows single-account targeting Spraying variant bypasses IP rate limit + MFA + HIBP check
Lockout DoS (deliberate account locking) Makes it possible Inherent to lockout design Temporary lockout only; short Max Wait; CAPTCHA; IP rate limit
Distributed spray (botnet, rotating IPs) No IP rate limit also insufficient alone MFA, passwordless, anomaly detection
Valid-session brute force (token replay) Not in scope of brute-force settings Different layer Token binding, short expiry, audit logging

Mitigation pattern: layer IP-based rate limiting at your reverse proxy

Keycloak is not designed to be exposed directly to the internet without a reverse proxy or WAF in front of it. This architectural layer is where IP-based rate limiting belongs.

Option A — Nginx rate limiting

If you run Nginx as your reverse proxy, the limit_req_zone directive creates a per-IP request bucket for the token endpoint:

http {
    # Define a zone: 10 MB of state, limit to 10 requests/second per client IP
    limit_req_zone $binary_remote_addr zone=keycloak_login:10m rate=10r/s;

    server {
        listen 443 ssl;
        server_name auth.example.com;

        location /realms/ {
            limit_req zone=keycloak_login burst=20 nodelay;
            limit_req_status 429;
            proxy_pass http://keycloak:8080;
        }
    }
}

Adjust rate and burst to match your expected legitimate traffic. A burst of 20 with a rate of 10 r/s allows brief spikes (users submitting forms quickly) while throttling sustained spraying attempts.

Option B — Cloudflare WAF rate limiting rules

If Cloudflare sits in front of Keycloak, create a custom rate limit rule targeting the login endpoint:

Field: URI Path
Operator: starts with
Value: /realms/

Rate: 20 requests per 10 seconds per IP
Action: Block (returning 429)

Cloudflare’s Bot Fight Mode and Bot Score fields can supplement this by blocking known bot ASNs before they reach your Keycloak instance, without requiring a fixed IP rate limit that might affect legitimate users on shared egress.

Skycloak’s managed platform includes a configurable WAF layer that applies these rules at the infrastructure level without requiring you to manage Nginx configs or Cloudflare rulesets manually.

Option C — Envoy or API Gateway

In Kubernetes deployments, Envoy’s local_rate_limit or global_rate_limit filters on the ingress gateway provide similar per-IP bucketing before requests reach Keycloak pods. The principle is the same: rate limit by $remote_addr on the /realms/ prefix.


Tuning lockout settings to limit DoS exposure

If you need to keep account lockout enabled (and there are valid reasons to), the following tuning approach reduces the DoS window without eliminating protection:

Use temporary lockout, not permanent lockout. Permanent lockout is a DoS amplifier: one successful spray attempt by an attacker produces a support ticket every time. Temporary lockout with a short Max Wait (5-15 minutes) limits the attacker’s impact and self-resolves without admin intervention.

Set a realistic Max Login Failures. Five to ten failures is common, but anything below three makes it trivial for an attacker to trigger lockout with two failed attempts (a typo plus a deliberate bad password). Ten is a reasonable baseline that still catches genuine brute force while requiring more attacker effort per lockout.

Keep Failure Reset Time short. If a failure counter resets after one hour of no activity, an attacker who locks you out in the morning has neutralized your account for the rest of the morning. A reset time of 10-15 minutes limits the window and allows the failure counter to clear between legitimate mis-types.

Enable Quick Login Check. This setting prevents the scenario where an automated tool sends thousands of requests per second against a single account by detecting when two login attempts for the same account arrive too close together. The Minimum Quick Login Wait should be at least 10 seconds.

A complete recommended baseline for a production realm is included in the Keycloak production readiness checklist.


Why MFA and passwordless change the threat model

The most durable mitigation for credential-based attacks is to make the credential alone insufficient or irrelevant.

MFA renders password spraying largely moot. If every account requires a TOTP code or WebAuthn assertion after the password, a successful password guess does not produce a working session. An attacker who sprays 10,000 accounts correctly and catches 50 weak passwords still has nothing without the second factor. Keycloak supports TOTP, email OTP, WebAuthn, and conditional MFA policies — covered in detail in MFA integration patterns for enterprise applications.

Passwordless authentication eliminates the attack surface entirely. WebAuthn/passkeys replace the password with a phishing-resistant cryptographic credential. There is no shared secret to spray, stuff, or brute-force. Keycloak 21+ supports WebAuthn as both a second factor and a first factor (via the webauthn-passwordless authenticator in authentication flows).

Even for internal applications where you cannot mandate passkeys for all users today, enabling MFA for admin accounts and service accounts removes the highest-value targets from the spraying threat model.


Monitoring and detection

IP-based rate limiting at the proxy layer stops most spraying attempts before they reach Keycloak. But layered defences require visibility. Configure Keycloak to emit all login events to a centralized log aggregator and alert on:

  • Rapid LOGIN_ERROR events across many distinct usernames from a single IP in a short window
  • LOGIN_ERROR events where error=account_disabled appears for accounts you know are active
  • Any spike in LOGIN_ERROR volume that is not correlated with a known incident (maintenance, password reset campaign)

The Keycloak Admin Events API (GET /admin/realms/{realm}/events) supports filtering by type, dateFrom, and dateTo. For production deployments, piping these events to a SIEM or alerting tool is a baseline expectation, not an optional extra. The complete Keycloak auditing and event logging guide covers the full event schema, retention configuration, and query patterns.

For organizations with insider-threat concerns, patterns of repeated failed logins against privileged accounts — even under threshold — are worth surfacing to your security team. Reducing insider risk with IAM controls covers the broader detection strategy.


Frequently asked questions

Does Keycloak have IP-based brute-force protection?

No. As of Keycloak 26.x, the built-in Brute Force Detection feature tracks failed login attempts per user account, not per source IP address. There is no native IP-based rate limiting, IP blocklist, or CIDR-level throttle in Keycloak’s core feature set. IP-based controls must be implemented at the layer in front of Keycloak — a WAF, Nginx, Cloudflare, or an API gateway.

Can Keycloak account lockout be used for a denial-of-service attack?

Yes. If an attacker can enumerate valid usernames — through the login error response, a user API, or directory enumeration — they can deliberately trigger lockout for those accounts by sending Max Login Failures + 1 incorrect password attempts. Under Permanent Lockout, the targeted accounts remain inaccessible until an administrator manually unlocks them, which scales as a DoS vector against organizations with large user populations. Temporary lockout with a short Max Wait is the recommended configuration to limit this exposure.

How do I protect Keycloak from password spraying?

The primary control is IP-based rate limiting at a reverse proxy or WAF positioned in front of Keycloak. This limits how many login attempts any single IP address can make per unit of time, regardless of how many different accounts are targeted. Secondary controls include enabling MFA for all accounts (so a correct password alone is insufficient) and monitoring for bursts of LOGIN_ERROR events distributed across many usernames from the same source IP.

Is CVE-2024-4629 still a risk in Keycloak 26?

CVE-2024-4629 was a brute-force bypass present in Keycloak versions before 24.0.5 and 25.0.1. The specific vulnerability — which allowed unauthenticated bypass of brute-force protection under certain conditions — is patched in those releases and all subsequent versions. Keycloak 26.x is not affected by the original CVE. However, the structural trade-offs of per-account lockout (spray vulnerability and lockout-as-DoS) are design characteristics, not bugs, and remain in all versions. Running Keycloak 26.x on a supported patch release and layering IP controls at the proxy remains the correct posture.

What is the safest lockout configuration for a public-facing Keycloak realm?

A reasonable baseline: Max Login Failures = 10, Wait Increment = 30 seconds, Max Wait = 15 minutes, Failure Reset Time = 15 minutes, Quick Login Check enabled with Minimum Quick Login Wait = 10 seconds, and Permanent Lockout disabled. Pair this with IP rate limiting at your WAF or reverse proxy (20-30 requests per 10 seconds per IP on the /realms/ path), MFA for all accounts, and event monitoring for LOGIN_ERROR spikes. This configuration accepts some residual spray risk in exchange for a bounded, self-resolving DoS window.


Conclusion

Keycloak’s brute-force detection is a useful baseline control, but it is explicitly account-scoped. Password spraying and lockout-as-DoS are structural consequences of that design, not bugs waiting to be fixed. The mitigation is architectural: put an IP-aware rate limiter in front of Keycloak, tune lockout settings to limit DoS impact, and layer MFA or passwordless authentication so that winning the password guessing game does not translate into a working session.

None of these layers are difficult to implement individually. The challenge is maintaining them correctly across Keycloak upgrades, realm changes, and infrastructure evolution — which is where managed infrastructure earns its cost. If you would rather focus on your application than on hardening Keycloak’s perimeter, Skycloak’s managed Keycloak hosting includes WAF configuration, security hardening defaults, and ongoing version management out of the box.

Tired of running Keycloak yourself?

Skycloak runs real upstream Keycloak for you with a 99.99% SLA. No fork, no lock-in, just managed Keycloak that stays patched and on call so you don't have to.

Guilliano Molaire
Written by
Founder

Guilliano is the founder of Skycloak and a cloud infrastructure specialist with deep expertise in product development and scaling SaaS products. He discovered Keycloak while consulting on enterprise IAM and built Skycloak to make managed Keycloak accessible to teams of every size.

Start Free Trial Talk to Sales
© 2026 Skycloak. All Rights Reserved. Design by Yasser Soliman