Reduced attack surface: the structural advantage of headless
In a traditional architecture, WordPress simultaneously exposes its back-office, its PHP themes, and its plugins to the public. In headless mode, the WordPress theme is no longer accessible to visitors. Only the API (REST or GraphQL) is exposed, which significantly reduces the number of attack vectors.
This separation does not relieve you of securing each layer individually. The following sections detail the measures to apply to the WordPress back-end, the API, and the Next.js frontend.
Securing the WordPress back-office
Restrict access to wp-admin
The WordPress admin panel must only be accessible to authorized IP addresses. This restriction is applied at the web server level (Apache or Nginx).
# Nginx — restrict wp-admin access by IP
location /wp-admin {
allow 203.0.113.10; # Office IP
allow 198.51.100.0/24; # VPN range
deny all;
}
location /wp-login.php {
allow 203.0.113.10;
allow 198.51.100.0/24;
deny all;
}
Two-factor authentication (2FA)
Install a 2FA plugin (WP 2FA, Two Factor Authentication) to require a temporary code in addition to the password. This measure neutralizes brute-force attacks and password compromises.
Limit login attempts
The Limit Login Attempts Reloaded plugin blocks IP addresses after a configurable number of failed login attempts. Recommended settings: 3 attempts before a 15-minute lockout, then progressive blocking.
Restrict allowed IPs on wp-admin
Configure your web server (Nginx or Apache) to allow only IP addresses from your team or VPN. Any access attempt from an unauthorized IP receives a 403 error.
Enable 2FA
Install a 2FA plugin and configure it for every account with the editor role or higher. Prefer TOTP applications (Google Authenticator, Authy) over SMS codes.
Limit login attempts
Install Limit Login Attempts Reloaded and configure a threshold of 3 attempts. Progressive lockouts deter automated attacks.
Disable XML-RPC
The XML-RPC protocol is a vector for brute-force and DDoS attacks. In headless mode, it has no use. Disable it in the .htaccess file or via a plugin.
Disable XML-RPC
The xmlrpc.php file allows remote connections to WordPress. In a headless architecture, it is unused and constitutes an attack vector (brute force, DDoS via pingback). Disable it:
// functions.php — disable XML-RPC
add_filter('xmlrpc_enabled', '__return_false');
# Nginx — block xmlrpc.php at the server level
location = /xmlrpc.php {
deny all;
}
Hide the WordPress version
The WordPress version is exposed by default in the HTML source code and in HTTP headers. This information makes it easier to target known exploits.
// functions.php — remove WordPress version
remove_action('wp_head', 'wp_generator');
add_filter('the_generator', '__return_empty_string');
Securing the API
Authentication of sensitive endpoints
The WordPress REST API is partially public by default. Read endpoints (posts, pages) are accessible without authentication, which is necessary for the frontend. Write endpoints and sensitive data, however, must require authentication.
// Restrict API access to authenticated users for certain endpoints
add_filter('rest_authentication_errors', function ($result) {
if (!empty($result)) {
return $result;
}
if (!is_user_logged_in()) {
// Allow only public endpoints
$public_routes = ['/wp/v2/posts', '/wp/v2/pages', '/wp/v2/categories'];
$current_route = $_SERVER['REQUEST_URI'];
// Block non-public endpoints
}
return $result;
});
Rate limiting
Rate limiting caps the number of API requests per unit of time and per IP address. Without this protection, an attacker can overload the server or massively extract data.
# Nginx — rate limiting on the REST API
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/m;
location /wp-json/ {
limit_req zone=api burst=10 nodelay;
proxy_pass http://wordpress_backend;
}
CORS configuration
CORS (Cross-Origin Resource Sharing) headers control which domains can query the API. Limit allowed origins to your frontend domain.
// functions.php — restrict CORS to your frontend
add_action('rest_api_init', function () {
remove_filter('rest_pre_serve_request', 'rest_send_cors_headers');
add_filter('rest_pre_serve_request', function ($value) {
header('Access-Control-Allow-Origin: https://www.your-site.com');
header('Access-Control-Allow-Methods: GET, OPTIONS');
header('Access-Control-Allow-Headers: Authorization, Content-Type');
return $value;
});
});
API key for the frontend
For public endpoints, use an API key passed via a custom header (for example X-API-Key). This key is not a strong security mechanism (it's visible in client code) but it allows you to filter out illegitimate requests and to make per-client rate limiting easier.
Hardening wp-config.php
The wp-config.php file contains the database credentials and security keys. Apply these measures:
// wp-config.php — hardening measures
// Disable the file editor in admin
define('DISALLOW_FILE_EDIT', true);
// Force HTTPS for admin
define('FORCE_SSL_ADMIN', true);
// Limit post revisions (reduce data surface)
define('WP_POST_REVISIONS', 5);
// Disable debug mode in production
define('WP_DEBUG', false);
define('WP_DEBUG_DISPLAY', false);
define('WP_DEBUG_LOG', false);
Environment variables and secret management
Secrets (API keys, tokens, database credentials) must never appear in versioned source code.
Hard rule: no secrets in code
Use environment variables to store secrets. On the Next.js side, variables prefixed with NEXT_PUBLIC_ are exposed to the browser. WordPress API keys, tokens, and database credentials must use variables without that prefix, accessible only on the server side.
# .env.local (Next.js) — never committed to Git
WORDPRESS_API_URL=https://admin.your-site.com/wp-json
WORDPRESS_AUTH_TOKEN=your_secret_token
REVALIDATION_SECRET=your_isr_secret
# These variables are accessible only on the server
# DO NOT use NEXT_PUBLIC_ for secrets
Security headers on the Next.js frontend
The next.config.js file lets you set HTTP security headers applied to every response from the frontend.
// next.config.js — security headers
const securityHeaders = [
{ key: 'X-Content-Type-Options', value: 'nosniff' },
{ key: 'X-Frame-Options', value: 'DENY' },
{ key: 'X-XSS-Protection', value: '1; mode=block' },
{ key: 'Referrer-Policy', value: 'strict-origin-when-cross-origin' },
{ key: 'Permissions-Policy', value: 'camera=(), microphone=(), geolocation=()' },
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' https://admin.your-site.com data:;"
},
{ key: 'Strict-Transport-Security', value: 'max-age=63072000; includeSubDomains; preload' },
];
module.exports = {
async headers() {
return [{ source: '/(.*)', headers: securityHeaders }];
},
};
HTTPS and DDoS protection
HTTPS everywhere
TLS encryption is mandatory on both layers:
- Frontend: certificate managed automatically by Vercel, Netlify, or your host
- WordPress backend: Let's Encrypt certificate or one provided by the host, with HTTP to HTTPS redirection
DDoS protection via CDN
A Next.js frontend deployed on Vercel or Netlify benefits natively from CDN DDoS protection. For the WordPress backend, place it behind Cloudflare or an equivalent service.
Dependency auditing
The Next.js project's npm dependencies may contain known vulnerabilities. Integrate auditing into your CI/CD pipeline.
# Check dependency vulnerabilities
npm audit
# Automatically fix compatible vulnerabilities
npm audit fix
# Continuous monitoring (GitHub Dependabot or Snyk)
Automated audit
Enable GitHub Dependabot or Snyk on your repository. These tools detect vulnerabilities in dependencies and propose automatic updates via pull requests.
Authentication and session management
Article suivantPerformance and Core Web Vitals in headless