JPKCom Harden & Trim — Guide & Tips

Harden WordPress, reduce data leaks and drop dead weight with JPKCom Harden & Trim — installation, harmless defaults, presets, wp-config.php constants and practical tips.

JPKCom Harden & Trim bundles just over 40 small settings that make a WordPress site more secure, make it reveal less about itself and its users, and make it load less unnecessary code. They all live on one settings page, and each one can also be set with a constant in wp-config.php. The principle behind it: if you activate the plugin and touch nothing, nothing about your site should visibly change — with one deliberate exception, XML-RPC.

Guide

Requirements

  • WordPress 7.1 or later (tested up to WordPress 7.1)
  • PHP 8.3 or later

That's a higher WordPress minimum than the other JPKCom plugins (6.9 or 7.0); they all require PHP 8.3.

Installation

  1. In the admin area, go to Plugins → Add New and click Upload Plugin.
  2. Choose the plugin's ZIP file and click Install Now.
  3. Click Activate, then review Settings → Harden & Trim.

Important right after activation: the Disable XML-RPC switch is on out of the box. xmlrpc.php then answers with 403, and Jetpack, the WordPress mobile apps and incoming pingbacks and trackbacks stop working. If you need any of them, switch it off or put define( 'JPKCOM_HARDEN_TRIM_XMLRPC', false ); in wp-config.php.

The defaults

Out of the box, only things nobody sees, or that browsers do anyway, are switched on:

  • Dead weight: the emoji scripts and self pingbacks are gone. Emoji stay as Unicode characters.
  • <head>: the shortlink, the RSD link, the REST API links and the generator tag with the WordPress version disappear. The REST API itself keeps working.
  • Privacy: author enumeration via ?author=N ends in a 404 for logged-out visitors (pretty permalinks only), the REST API user list answers anonymous requests with 401, oEmbed data names the site instead of the author, users disappear from the sitemap, and the dashboard call that reports your browser and site URL to api.wordpress.org is dropped.
  • Security: login errors are worded neutrally, X-Powered-By and X-Redirect-By are removed, X-Content-Type-Options: nosniff and Referrer-Policy: strict-origin-when-cross-origin are sent, REST API JSONP is off — and XML-RPC is refused.

Apart from XML-RPC, everything that can break something is off out of the box and carries a warning on the settings page.

The settings page

Under Settings → Harden & Trim the switches are sorted into five groups: Performance, Clean up <head>, Privacy, Security and Roles. Notes next to the fields show what a switch affects: "Changes appearance", "Affects editors", "Not available with block themes" or "Locked by wp-config.php". The Roles group lists the global switches under "Global — role exemption does not apply".

A few switches worth knowing before you turn them on:

  • Asset version strings: "Replace with a hash" hides the versions on the front end without breaking cache busting. "Remove" leaves stale files in browser caches after updates.
  • Do not load block library CSS and Do not load global styles: block content loses its styling, or its colours, spacing and column layouts. Both switches are locked with block themes.
  • Heartbeat: "Optimized" sets the interval to 120 seconds. "Disabled" also stops post locking, classic-editor autosave and the login modal when a session expires.
  • Anonymise commenter IP addresses: new comments store the IP shortened (IPv4 to /24, IPv6 to /48) and no user agent. For new comments this cannot be undone.
  • Strict-Transport-Security: browsers remember the header for a year. Only switch it on when HTTPS works everywhere.
  • Application passwords: "Disabled for everyone" or "Only for the roles selected below". Without them, MCP and Abilities clients, the mobile app and other integrations stop working. If you choose the roles option without selecting a role, nobody can use them, administrators included.
  • Disable comments: behaves like JPKCom Disable Comments in version 1.0.11 — comments closed, existing ones hidden (not deleted), comment feeds and REST routes answering 404. Notes in the block editor keep working. The switch is never part of a preset.

Presets

Three presets set a whole group of switches at once:

Preset Content
Harmless the defaults, XML-RPC refused
Recommended plus optimized heartbeat, X-Frame-Options, no Events and News widget, privacy-friendly video embeds, file editors off, 20 revisions
Strict plus hashed asset versions, no post embeds, no comment reply script, no Dashicons for visitors, local avatars, shortened comment IPs, editor without remote content, core AI off, Permissions-Policy, 5 revisions, 2-minute autosave

A preset sets every switch it manages to its own value or the default, never touches a locked switch and never turns on one of the risky ones. So a disabled heartbeat or removed asset versions are reset. Never touched: disabling comments, block library CSS, global styles, HSTS, feed links, application passwords and the role lists. Before applying, the page asks for confirmation, and afterwards it lists the switches that changed.

Constants in wp-config.php

Every switch has a constant. It always wins over the stored value and locks the field on the settings page. Put it before the "That's all, stop editing" line:

define( 'JPKCOM_HARDEN_TRIM_EMOJI', true );
define( 'JPKCOM_HARDEN_TRIM_HEARTBEAT', 'optimized' );
define( 'JPKCOM_HARDEN_TRIM_POST_REVISIONS', 20 );
define( 'JPKCOM_HARDEN_TRIM_EXEMPT_ROLES', 'editor,author' );

Switches take true or false, select fields an exact value such as optimized, number fields a value from the list (e.g. 20, not 7), role lists an array or a comma-separated list. The plugin ignores an invalid value and names the constant on the settings page. WordPress's own constants WP_POST_REVISIONS, AUTOSAVE_INTERVAL, DISALLOW_FILE_EDIT and WP_AI_SUPPORT in turn take precedence over the plugin and its constants.

Role exemption

Under Roles you pick roles that see the untouched WordPress. This applies to only six switches that directly affect logged-in users: emoji, asset versions, comment reply script, block library CSS, global styles and heartbeat. Everything else is global. On multisite a super admin counts as an administrator on every site. Out of the box no role is exempt.

Import, export and kill switch

Export downloads the stored settings as a JSON file — without the site URL and without user data. Import accepts a file of up to 64 KB or pasted JSON, accepts known keys only, checks every value like the form does and reports how many settings were applied and which were rejected. It's the easiest way to bring the same configuration to several sites.

If something breaks after a change, define( 'JPKCOM_HARDEN_TRIM_DISABLE_ALL', true ); in wp-config.php switches all features off. The settings page stays reachable and shows a notice. Deactivating the plugin has the same effect.

Tips & Tricks

  • Each switch in one place only: SEO, caching and security plugins often offer the same functions. This matters most for headers: the plugin skips a header that WordPress or another plugin has already set in PHP, but it can't see headers from the server configuration (nginx add_header, Apache mod_headers), a proxy or a CDN. Two Strict-Transport-Security headers are the worst case. If a page cache serves the pages, PHP headers may be missing altogether — then the server configuration is the more reliable place.
  • Keeping XML-RPC open for good: every preset switches XML-RPC on again, even if you turned it off for Jetpack before. With define( 'JPKCOM_HARDEN_TRIM_XMLRPC', false ); it stays open, whichever preset you apply.
  • Next to the standalone plugins: the XML-RPC and comments switches behave hook for hook like JPKCom Disable XML-RPC and JPKCom Disable Comments. Both standalone plugins are still maintained, and running them alongside does no harm; the settings page points it out. The one exception: JPKCom Disable Comments up to 1.0.10 removes the REST comment routes and breaks notes in the block editor, even next to this plugin. Update it to at least 1.0.11. Hiding the login URL remains the job of JPKCom Hide Login.
  • Author enumeration and ActivityPub: if you use the ActivityPub plugin, the switch only lets through requests that ActivityPub itself answers with the actor document. Federation still finds your authors, and every other ?author=N request gets the 404.
  • Not a GDPR tool: the privacy switches reduce what your site sends to third parties and reveals about its users. They don't replace a privacy policy, a consent tool, a data processing agreement or legal advice — and the privacy-friendly video embeds are no consent solution either.
  • Put the settings filter in a plugin: the filter jpkcom_harden_trim_settings runs exactly once, on the plugins_loaded hook. In a theme's functions.php it comes too late — put it in a (must-use) plugin.
  • Deactivating or deleting: deactivating keeps the settings. Only deleting the plugin removes the stored option and the cached data, on multisite from every site.
  • The plugin itself stays quiet: no remote requests, no telemetry. The one exception is the updater, which fetches a manifest from GitHub Pages and caches it for a day. The Update URI header has been set from the start, so the wordpress.org update API returns no updates for this plugin — a plugin published there under the same slug cannot pose as an update.

Further reading