# How Ghost Implements Brute Force Protection for API Endpoints

> Discover how Ghost protects API endpoints with layered rate-limiting via express-brute and brute-knex blocking abuse while ensuring smooth access for legitimate users.

- Repository: [Ghost/Ghost](https://github.com/TryGhost/Ghost)
- Tags: internals
- Published: 2026-05-18

---

**Ghost CMS protects its API endpoints using a layered rate-limiting system built on `express-brute` and `brute-knex`, featuring configurable thresholds that automatically reset on successful requests to block abuse without penalizing legitimate traffic.**

Ghost employs a sophisticated mechanism to safeguard its public and administrative API routes from automated attacks and credential stuffing. The implementation, found in the `TryGhost/Ghost` repository, centers on middleware defined in [`ghost/core/core/server/web/shared/middleware/brute.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/web/shared/middleware/brute.js) and [`spam-prevention.js`](https://github.com/TryGhost/Ghost/blob/main/spam-prevention.js), utilizing both in-memory and persistent storage strategies to balance performance with security requirements.

## Middleware Architecture for API Protection

For the Content API, Ghost inserts brute force protection at the very beginning of the authentication chain. In the public authentication middleware stack, the `contentApiKey` rate limiter executes before any credentials are validated:

```javascript
module.exports.authenticatePublic = [
    shared.middleware.brute.contentApiKey,   // Rate limit for API keys
    auth.authenticate.authenticateContentApi,
    auth.authorize.authorizeContentApi,
    cors(),
    shared.middleware.urlRedirects.adminSSLAndHostRedirect,
    shared.middleware.prettyUrls
];

```

The `contentApiKey` middleware defined in [`brute.js`](https://github.com/TryGhost/Ghost/blob/main/brute.js) wraps the core rate limiter with logic to reset counters upon successful requests:

```javascript
contentApiKey(req, res, next) {
    return spamPrevention.contentApiKey().getMiddleware({
        ignoreIP: false
    })(req, res, function (err, ...rest) {
        // On success we reset the brute counter
        if (!err) {
            res.on('finish', () => {
                if (res.statusCode < 400) {
                    req.brute.reset();
                }
            });
        }
        return next(err, ...rest);
    });
}

```

This design ensures that only failed or unauthorized attempts increment the brute force counter, while legitimate API calls with valid keys reset the limit to prevent accidental lockouts.

## Rate Limiting Implementation Details

The actual rate limiter instances are created in [`ghost/core/core/server/web/shared/middleware/api/spam-prevention.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/web/shared/middleware/api/spam-prevention.js). For the Content API, Ghost uses an in-memory store to minimize database overhead:

```javascript
const contentApiKey = () => {
    const ExpressBrute = require('express-brute');
    // In-memory store – no DB hit for each request
    memoryStore = memoryStore || new ExpressBrute.MemoryStore();

    contentApiKeyInstance = contentApiKeyInstance || new ExpressBrute(memoryStore, {
        attachResetToRequest: true,
        failCallback(req, res, next) {
            const err = new errors.TooManyRequestsError({
                message: tpl(messages.tooManyAttempts)
            });
            logging.error(err);
            return next(err);
        },
        handleStoreError
    }, pick(spamContentApiKey, spamConfigKeys));

    return contentApiKeyInstance;
};

```

Key characteristics of this brute force protection system include:

- **Storage Strategy**: The Content API uses `ExpressBrute.MemoryStore` for high-speed checks without database queries. High-risk endpoints like login and password reset use the persistent `brute-knex` store that writes to the `brute` table in the Ghost database.
- **Request Attachment**: Setting `attachResetToRequest: true` exposes the limiter on `req.brute`, enabling the middleware to programmatically reset counters after successful responses.
- **Configurable Thresholds**: Limits are controlled via Ghost's configuration under `config.get('spam').content_api_key`, supporting `freeRetries`, `minWait`, `maxWait`, and `lifetime` parameters.
- **Error Response**: When limits are exceeded, Ghost returns a `TooManyRequestsError` (HTTP 429), logged through the internal `logging` utility.

## Endpoint-Specific Protection Strategies

Ghost reuses this brute force protection pattern across multiple sensitive routes through the `shared.middleware.brute` object:

- **`userLogin`**: Targets `/admin/login` with per-IP and user-IP pair tracking using exponential back-off.
- **`userReset`**: Protects password-reset endpoints with limits scoped per email address.
- **`membersAuthEnumeration`**: Prevents user enumeration attacks on member sign-in endpoints via global per-IP limits.
- **`otcVerification`** and **`otcVerificationEnumeration`**: Limit one-time-code verification attempts per code and per IP.
- **`webmentionsBlock`**: Applies fixed per-IP limits to webmention endpoints.
- **`emailPreviewBlock`**: Restricts email preview UI access to 10 attempts per hour.

Each middleware is wired into its respective route stack, ensuring consistent brute force protection across both public-facing and administrative API endpoints.

## Configuring Brute Force Limits

Administrators can customize rate limiting behavior through Ghost's JSON configuration files. The following example increases the Content API threshold for development environments:

```javascript
// config.development.json
{
  "spam": {
    "content_api_key": {
      "freeRetries": 20,
      "minWait": 1000,
      "maxWait": 60000,
      "lifetime": 3600
    }
  }
}

```

To protect a custom public endpoint with the same brute force rules:

```javascript
router.get('/my-public-data', shared.middleware.brute.contentApiKey, (req, res) => {
    // Handler logic only executes if rate limit not exceeded
    res.json({data: 'public'});
});

```

## Summary

- Ghost's brute force protection relies on `express-brute` middleware centralized in [`ghost/core/core/server/web/shared/middleware/brute.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/web/shared/middleware/brute.js).
- The Content API uses an in-memory store for performance, while authentication endpoints use persistent `brute-knex` storage.
- Counters automatically reset when responses return HTTP status codes below 400, ensuring legitimate traffic flows uninterrupted.
- All thresholds are configurable via the `spam` section of Ghost's configuration files.
- Multiple specialized middlewares protect specific attack vectors including login attempts, password resets, and user enumeration.

## Frequently Asked Questions

### How does Ghost differentiate protection between the Content API and Admin API?

The Content API uses `ExpressBrute.MemoryStore` for minimal overhead on high-volume public reads, while Admin API endpoints handling sensitive operations like authentication utilize `brute-knex` for persistent storage in the database. This distinction ensures that high-risk actions maintain accurate counts across server restarts, while public content delivery remains performant.

### What HTTP response does Ghost return when brute force limits are exceeded?

When a client exceeds the configured threshold, Ghost returns a `TooManyRequestsError` with HTTP status code 429. The error propagates through Ghost's centralized error handling pipeline, providing a consistent response format that includes the standard error message template defined in the spam prevention configuration.

### Can brute force protection be disabled for specific API endpoints?

While Ghost does not recommend disabling security controls, the middleware architecture allows developers to omit `shared.middleware.brute` wrappers from custom route definitions. For built-in endpoints, modifications to [`ghost/core/core/server/web/shared/middleware/api/spam-prevention.js`](https://github.com/TryGhost/Ghost/blob/main/ghost/core/core/server/web/shared/middleware/api/spam-prevention.js) would be required, though such changes expose the instance to credential stuffing and abuse attacks.

### Why does Ghost reset brute force counters on successful requests?

The reset mechanism ensures that the rate limiter distinguishes between abusive traffic and legitimate usage patterns. By attaching to the response finish event and checking for status codes below 400, Ghost ensures that only unauthorized or failed attempts accumulate toward the limit threshold, preventing accidental lockouts from valid API key usage.