惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

D
Docker
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - Franky
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
A
About on SuperTechFans
博客园 - 【当耐特】
Microsoft Security Blog
Microsoft Security Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The GitHub Blog
The GitHub Blog
雷峰网
雷峰网
博客园_首页
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
IT之家
IT之家
博客园 - 叶小钗
Google DeepMind News
Google DeepMind News
aimingoo的专栏
aimingoo的专栏
博客园 - 聂微东
B
Blog RSS Feed
H
Help Net Security
Recent Announcements
Recent Announcements
阮一峰的网络日志
阮一峰的网络日志
D
DataBreaches.Net
L
LangChain Blog
Vercel News
Vercel News

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Using a locked-down WordPress as the form backend for my ...
Vitalii Kiiko · 2026-06-19 · via DEV Community

Vitalii Kiiko

Static sites are great: fast, cheap to host, almost nothing to attack. Then you add a contact form and hit the same wall everyone hits — a static site can't process a submission. You need a backend.

The usual answers are a third-party service (Formspree, Netlify Forms, Basin) or a small server you now have to babysit. Both add a dependency you don't control, a recurring bill, and — the part that bugs me most — your submission data lives on someone else's infrastructure.

There's a third option I've been running for a while: one WordPress install, zero public pages, used purely as a form endpoint. Every form from every static site I own hits it. I own all the data. And because it serves no public HTML, its attack surface is close to nothing.

The architecture

Three pieces, each doing one job:

  • WordPress — the backend. Locked down so hard it doesn't behave like a normal WP site anymore.
  • A form plugin — handles building, validation, storage, email, file uploads. (I use CraftForms because it exposes a clean craftforms/v1 REST namespace and can also serve the form HTML to an external page — more on that below.)
  • Your static frontend — Cloudflare Pages / Netlify / wherever. It either fetches the REST endpoint on submit, or drops in an embed snippet.

WordPress never serves a public request. It only processes submissions.

The part that matters: locking it down

The biggest WordPress attack vector isn't your host — it's outdated plugins. So the first move is brutal minimalism: one plugin, no theme, no page builder, no public frontend. A WP install with one plugin and a blocked frontend has almost no CVE surface, because none of the usual stuff is installed.

The rest is one must-use plugin. Drop this in wp-content/mu-plugins/ (no activation needed) and you've blocked the four standard entry points:

<?php
if ( ! defined( 'ABSPATH' ) ) exit;

// 1. Restrict the REST API to your form namespace only.
//    Kills user enumeration (/wp/v2/users), route discovery, the usual REST exploits.
add_filter( 'rest_pre_dispatch', function ( $result, $server, $request ) {
    if ( strpos( $request->get_route(), '/craftforms/' ) === 0 ) {
        return $result;
    }
    return new WP_Error( 'rest_restricted', 'REST API disabled.', [ 'status' => 403 ] );
}, 10, 3 );

// 2. Kill XML-RPC (brute-force + pingback DDoS amplification).
add_filter( 'xmlrpc_enabled', '__return_false' );

// 3. Block every public frontend request for logged-out visitors.
//    Runs in PHP, so it works on Apache, nginx, anything — no .htaccess needed.
add_action( 'template_redirect', function () {
    if ( is_user_logged_in() ) return;
    status_header( 403 );
    nocache_headers();
    exit;
} );

template_redirect doesn't fire for REST or wp-admin, so your submission endpoint and the admin panel still work — only public pages get the 403. (The full version in the article also moves /wp-login.php to a secret slug so brute-force scanners can't find it.)

Verify it:

curl https://your-backend.com/wp-json/wp/v2/          # → 403
curl https://your-backend.com/wp-json/craftforms/v1/embed/KEY   # → form data

Submitting from the static side

Plain JSON to one endpoint:

const res = await fetch(
  'https://your-backend.com/wp-json/craftforms/v1/submit/contact-form',
  {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(Object.fromEntries(new FormData(form))),
  }
);

Or skip building the form entirely and let the backend render it — a single embed <div> + script tag, and you get every field type, client-side validation, and even Stripe payments / bookings on a site that has no server of its own.

Why I keep doing it this way

A third-party form service gives you one thing: an email on submit. This setup gives me a real backend I own — SMTP delivery I control, branded HTML emails, a submissions database, file uploads into the Media Library, and (with the embed) full ecommerce/booking on a static site. No per-submission billing, no data on someone else's box.


I wrote the full walkthrough — the complete mu-plugin (including the hidden-login bit), the embed setup, required-header spam protection, and the static-frontend workflow — over on my blog:

👉 Use WordPress as a Locked-Down Form Backend for Static Sites

Disclosure: I build CraftForms, the form plugin used here — but the lock-down approach works with any plugin that exposes a single REST namespace.