Most WordPress sites are built the same way. Buy a theme on a marketplace, install a page builder, add twenty plugins to cover what the theme and builder don’t, and hope they all keep getting along through the next round of updates. It works, and it’s how a huge share of the web gets built. It’s also why so many WordPress sites are slow, fragile and hard to secure.
wpbob.com isn’t built that way. I call the approach Classic Enterprise, and this post is the tour.
What “Classic Enterprise” means
Three things, and nothing else:
- A classic theme, built for one site. PHP templates and one stylesheet, written for this site only. No marketplace theme, no page builder, no block theme.
- A hardened setup. Every part of WordPress that this site doesn’t use is switched off, and the parts it does use are locked down.
- Minimal plugins. A plugin has to earn its place. If fifty lines of theme code can do the job, the theme does it.
In other words, WordPress built the way you’d build any serious web application. It just happens to run on the most popular CMS in the world.
A classic theme with no build step
The theme is plain PHP templates — header, footer, home, single post, page, archive, search, 404 — and a single CSS file. There’s no build step, no Node toolchain, no compiled assets. What’s in the theme folder is exactly what the server runs.
The theme also ships no JavaScript at all. The search box in the header opens with a native HTML <details> element, and the category dropdowns are pure CSS. A visitor’s browser downloads one stylesheet, the page and its images.
The block editor is off for every post type, and so is the block-based widgets screen:
add_filter( 'use_block_editor_for_post_type', '__return_false' );
add_filter( 'use_widgets_block_editor', '__return_false' );
Cache-busting is just as simple: the stylesheet is loaded with the theme’s version number from style.css. Every change gets a version bump, and every visitor gets the new file.
The editor is a plain HTML window
This is the part most people find strange. The visual editor is gone. When I write a post, I write HTML in a code editor (WordPress’s own bundled CodeMirror, with line numbers), and whatever I type is exactly what ships to the browser.
Getting there means removing a set of filters WordPress runs on every post by default:
wpautop, which guesses where paragraphs go and wraps them in<p>tagswptexturize, which rewrites quotes, dashes and ellipsesconvert_smilies,convert_charsandcapital_P_dangitbalanceTagson save, which “fixes” HTML it thinks is broken
They’re removed from the RSS feed too. That one is easy to miss: the feed has its own filter chain, and without it, raw HTML would come through untouched on the site and get mangled in feed readers.
The trade-off is real. This is only safe because wpbob.com has a single administrator who is allowed to post unfiltered HTML anyway. On a site with several authors, I wouldn’t do it.
Stripping what WordPress adds
Out of the box, WordPress loads a lot on every page that a classic theme never uses. All of this is off:
- The block library CSS and global styles
- jQuery on the front end
- The emoji detection script and its styles
- Dashicons for logged-out visitors
- Speculative loading
The <head> gets the same treatment. Gone: the RSD link, the Windows Live Writer manifest, the generator tag, the shortlink, adjacent-post links, oEmbed discovery and the REST API discovery link. What’s left is the title, the canonical URL, the site icon, the stylesheet and one RSS feed link.
Hardening
The security work lives in the theme, and it’s deliberately narrow:
- XML-RPC is off entirely. That closes the pingback attack surface and the old brute-force trick of trying hundreds of passwords in a single request.
- Usernames can’t be enumerated. The
?author=1probe returns a 404, and the REST API’s users endpoint requires a logged-in administrator. - The REST API requires authentication for anyone not logged in.
- Comments, trackbacks and pingbacks are off site-wide, down to the comment form’s endpoint returning a 403.
- The theme and plugin file editors are disabled in the dashboard.
- Security headers are sent on every page:
X-Content-Type-Options,Referrer-Policy,X-Frame-OptionsandPermissions-Policy.
Just as important is what I didn’t do. I didn’t rename the login URL, delete readme.html, try to hide the WordPress version or block /wp-json/ with a regex. Anyone who would bother attacking a site gets around those in seconds, and each one breaks something real, like update checks or debugging. That’s security theatre, not security.
What testing on the live site caught
Code review only gets you so far. Once the theme was uploaded, I checked the live site’s responses directly, and three things turned up that looked right in the code but weren’t:
- The author probe was leaking the username.
?author=1was supposed to 404, but it was returning a 301 redirect, and the redirect’sLocationheader contained the username. WordPress’s own canonical-redirect function runs on the same hook and was getting there first. The fix was to run the check earlier and stop the request immediately after sending the 404. - The REST API link was still being sent. I’d removed it from the
<head>, but WordPress also sends it as an HTTPLinkheader on every response. That’s a separate mechanism, and it needed its own removal. - Two feed links were printing instead of one. WordPress’s feed support adds a site-wide comments feed link whether or not comments exist. With comments off for good, that link pointed at nothing, so the theme now prints only the main feed.
None of these would show up in a browser. You only find them by looking at the actual HTTP responses.
The plugin list
Here’s the full plugin list:
- Yoast SEO, active. It handles titles, meta descriptions and social sharing data. The theme exposes Yoast’s fields to the REST API, so posts and their SEO settings can be managed entirely over the API.
- LiteSpeed Cache, active. It handles page caching on the server.
That’s it. No contact form plugin, no security suite, no page builder, no slider, no social sharing widget.
What it costs
Classic Enterprise isn’t for everyone. There’s no drag-and-drop. Posts are written in HTML. There are no comments. Changing the layout means editing PHP and CSS, not clicking around in a customizer. If you need non-technical people to build pages on their own, a page builder exists for a reason.
What you get in return is a site where every line has a reason to be there: fast because there’s almost nothing to load, secure because there’s almost nothing to attack, and stable because there’s no stack of third-party code to break on the next update.
This is the first post about it. In future posts I’ll go deeper on each piece.