Security Guide
HeadlessCore is designed for self-hosted headless WordPress. Security is built into the theme and Next.js starter kit, but production hardening is a shared responsibility — unlike WP Engine's managed platform, you control the infrastructure.
Production deploy: aaru.live runbook (Cloudflare + WP + Next.js).
This guide covers:
- Built-in security controls (what ships in the repo)
- Production hardening checklist
- Parallel comparison with WP Engine + Faust.js
- Configuration reference
Defense in depth
┌─────────────────────────────────────┐
│ Edge (Cloudflare / CDN / WAF) │ ← You configure
└──────────────────┬──────────────────┘
│
┌──────────────────▼──────────────────┐
│ Next.js frontend (public site) │
│ • CSP + security headers │
│ • Secret-gated API routes │
│ • Rate limits (+ optional Redis) │
│ • Turnstile CAPTCHA on forms │
└──────────────────┬──────────────────┘
│ WPGraphQL (HTTPS)
┌──────────────────▼──────────────────┐
│ WordPress CMS (private subdomain) │
│ • Standard WP auth (wp-admin) │
│ • GraphQL mutation rate limits │
│ • Introspection off in production │
│ • XML-RPC disabled, no generator │
└─────────────────────────────────────┘
Principle: The public site never runs PHP. WordPress admin and the database
stay off the main marketing domain (aaru.live), ideally on a separate
subdomain (cms.aaru.live) with restricted access.
Built-in controls
Next.js frontend
| Control | Where | Details |
|---|---|---|
| Security headers | next.config.ts |
X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, HSTS (production), CSP |
| Revalidation secret | /api/revalidate |
Shared secret in JSON body; constant-time compare (secureCompare) |
| Preview secret | /api/preview |
Query-param gate; constant-time compare |
| Rate limiting | API routes | 30/min revalidate, 20/min preview, 10/min downloads; in-memory or Upstash Redis |
| Form protection | Contact, newsletter, comments | Honeypot field, Turnstile CAPTCHA, per-IP rate limits |
| Search indexing | POST /api/search |
SEARCH_INDEX_WEBHOOK_SECRET + rate limit |
| Stripe webhooks | /api/stripe-webhook |
Signature verification via stripe.webhooks.constructEvent |
| License keys | hc1. / hnp1. |
Offline RSA-2048 verification; no license phone-home |
| License delivery | WordPress mutation | hash_equals on LICENSE_DELIVERY_SECRET |
| Theme downloads | /api/theme-updates/download |
Valid license required; ZIPs outside public/ |
| Multi-tenant | tenant.ts |
Per-tenant revalidation/preview secrets when enabled |
WordPress theme
| Control | Where | Details |
|---|---|---|
| GraphQL introspection | inc/security.php |
Disabled when WP_ENVIRONMENT_TYPE=production |
| Public mutation rate limits | inc/security.php |
Contact, newsletter, comments, search log — per-IP transients |
| XML-RPC | inc/security.php |
Disabled |
| User enumeration | inc/security.php |
Blocks ?author= for anonymous visitors |
| Generator tag | inc/security.php |
WordPress version hidden from HTML |
| Security headers | inc/security.php |
nosniff, SAMEORIGIN, HSTS on HTTPS |
| File editor | inc/security.php |
DISALLOW_FILE_EDIT unless already set in wp-config.php |
| Akismet | inc/akismet.php |
Optional comment spam when plugin active |
| AI mutations | inc/ai-tools.php |
Requires valid pro license and edit_posts |
| License verification | functions.php |
openssl_verify() on every uncached check |
Production hardening checklist
Use this before going live (e.g. aaru.live + cms.aaru.live).
Infrastructure
- HTTPS everywhere — TLS on frontend and CMS; redirect HTTP → HTTPS
- Separate domains — Public site on
www/ apex; CMS oncms.subdomain - Cloudflare (or similar) — WAF, DDoS protection, bot fight mode on CMS
- Restrict CMS access — Cloudflare Access, IP allowlist, or VPN for
/wp-admin - Hide CMS from search —
robots.txtdisallow on cms subdomain; no public links
Secrets (generate with openssl rand -hex 32)
-
REVALIDATION_SECRET— match in WP Headless Core → Settings -
PREVIEW_SECRET— long random string -
SEARCH_INDEX_WEBHOOK_SECRET— if using pluggable search -
LICENSE_DELIVERY_SECRET— if using Stripe license delivery -
TURNSTILE_SECRET_KEY+NEXT_PUBLIC_TURNSTILE_SITE_KEY— production forms - Never commit
.envor PEM private keys
WordPress (wp-config.php)
define( 'WP_ENVIRONMENT_TYPE', 'production' );
define( 'DISALLOW_FILE_EDIT', true );
define( 'FORCE_SSL_ADMIN', true );
// Unique keys from https://api.wordpress.org/secret-key/1.1/salt/
- Strong admin password + 2FA plugin (e.g. Wordfence, Solid Security)
- Remove default
adminusername - Keep WordPress, PHP, and plugins updated
- Disable unused plugins; only WPGraphQL + JWT auth required
Next.js
- Set
NODE_ENV=production - Configure Upstash Redis for distributed rate limits (see below)
- Review CSP in
next.config.tsif adding third-party scripts - Deploy latest build (theme-updates,
/download, webhooks must exist)
Optional but recommended
- Fail2ban or Cloudflare rate rules on CMS login
- Database backups (daily) + off-site storage
- Uptime monitoring on frontend and GraphQL endpoint
- Security headers scan (securityheaders.com)
Distributed rate limiting (Upstash)
In-memory rate limits only apply per Node.js process. On Vercel, Railway, or multiple containers, configure Upstash (free tier available):
UPSTASH_REDIS_REST_URL=https://xxxx.upstash.io
UPSTASH_REDIS_REST_TOKEN=your-token
When both are set, /api/revalidate, /api/preview, /api/search (POST),
and download routes use Redis automatically. If Upstash is unreachable, the
app falls back to in-memory limits and logs a warning.
HeadlessCore vs WP Engine + Faust.js
Parallel comparison for agencies evaluating both stacks.
Platform model
| Aspect | HeadlessCore | WP Engine Headless Platform + Faust.js |
|---|---|---|
| Hosting | Self-hosted (any VPS, Vercel, Coolify) | WP Engine managed WP + managed Node.js |
| Cost | Server cost + optional $99 license | ~$20–50+/mo per environment (platform fees) |
| Vendor lock-in | None (MIT core, standard WPGraphQL) | Medium (platform deploy, Faust tooling) |
| WordPress host | Any (XAMPP, DigitalOcean, etc.) | WP Engine required for integrated experience |
| Frontend | Next.js 16 App Router (you own) | Faust.js toolkit on Next.js (evolving to modular HWP Toolkit) |
Security
| Aspect | HeadlessCore | WP Engine + Faust.js |
|---|---|---|
| Managed WAF / DDoS | You add (Cloudflare, etc.) | Included on platform |
| WP security patching | Your responsibility | Automatic on WP Engine |
| SOC2 / enterprise compliance | Your infrastructure | WP Engine certified |
| Decoupled attack surface | Yes — no PHP on public site | Yes — same headless benefit |
| Preview authentication | Shared secret (PREVIEW_SECRET) |
OAuth/JWT redirect flow (more standard) |
| Revalidation | Shared secret webhook | Platform / Faust-integrated |
| GraphQL introspection | Off in production (theme) | Configurable (your WPGraphQL settings) |
| Form spam protection | Turnstile + honeypot + rate limits | Bring your own |
| Rate limiting | Built-in (+ optional Redis) | Platform edge limits + your code |
| License / entitlement | Offline RSA (hc1.) — no telemetry |
N/A (subscription billing) |
| Security headers (frontend) | CSP, HSTS, X-Frame-Options in next.config.ts |
Your Faust/Next.js config |
| CMS hardening | Theme hooks (inc/security.php) |
WP Engine hardening + your config |
Features (not security, but affects product choice)
| Feature | HeadlessCore | WP Engine + Faust.js |
|---|---|---|
| Pro Gutenberg blocks | 47 blocks built-in | DIY / ACF blocks |
| WooCommerce headless | Store API checkout | Possible, DIY |
| Multi-tenant | Optional built-in | No |
| i18n (Polylang/WPML) | Built-in | DIY |
| Search (Meilisearch/Algolia) | Pro built-in | DIY |
| AI content tools | Pro built-in | No |
| CRM (HubSpot/Mailchimp) | Pro built-in | DIY |
| Stripe license delivery | Built-in | No |
| Theme auto-updates | License-gated self-host | Git deploy |
Verdict
| Scenario | Better fit |
|---|---|
| Enterprise team, compliance budget, no DevOps | WP Engine |
| Agency resale, white-label, many client sites | HeadlessCore |
| Maximum control, lowest long-term cost | HeadlessCore |
| Preview/auth DX out of the box | Faust.js (OAuth flow) |
| Feature-rich product bundle | HeadlessCore |
| Security with zero configuration | WP Engine |
| Security with Cloudflare + this checklist | Comparable |
Honest summary: WP Engine wins on managed infrastructure security by
default. HeadlessCore can reach parity or better control when you deploy
with Cloudflare WAF, Upstash rate limits, hardened wp-config.php, and CMS
access restrictions — at the cost of more setup.
API route security reference
| Route | Auth | Rate limit |
|---|---|---|
POST /api/revalidate |
REVALIDATION_SECRET |
30/min/IP |
GET /api/preview |
PREVIEW_SECRET |
20/min/IP |
POST /api/search |
SEARCH_INDEX_WEBHOOK_SECRET |
60/min/IP |
GET /api/search |
Public (CAPTCHA N/A) | 60/min/IP |
POST /api/stripe-webhook |
Stripe signature | None (Stripe retries) |
GET /api/theme-updates/* |
License key | 10/min/IP (download) |
GET /api/download/product |
Stripe session or hc1. key |
10/min/IP |
| Contact/newsletter server actions | Turnstile + honeypot | 5/15min/IP |
See API Routes for request/response formats.
Stripe licensing tradeoff
Automated license delivery stores LICENSE_SIGNING_PRIVATE_KEY_PEM on the
Next.js server. This is opt-in — manual CLI key generation keeps the
private key fully offline. See Stripe Licensing and
Licensing.
Reporting vulnerabilities
If you discover a security issue in HeadlessCore, report it privately to
hello@aarudhnextgen.com rather than opening a public GitHub issue. Include
steps to reproduce and affected versions.
Related docs
- Architecture — security boundaries
- API Routes
- Deployment
- Licensing
- Multi-Tenant — per-tenant secrets and rate-limit keying