Module 7 · 7.4
English
Hardening your website: capabilities, headers and security plugins
Module 7: Security · Reading time: 10 minutes
Learning objective: After this lesson, you can harden a WordPress website with permissions, settings and security headers, and judge security plugins for what they're really worth.
Key concepts: hardening, file permissions (644, 755), DISALLOW_FILE_EDIT, no PHP in uploads, directory listing, hiding the version, X-Powered-By, security headers, backup files and .git in the web directory, WAF on the server, WAF in a plugin, malware scanner
What hardening is
Hardening means: setting up your website so that a mistake or vulnerability can do less damage. It doesn't replace updates and strong passwords (lesson 7.2 and 7.3), but it limits the consequences. Think of a house: updates are the locks, hardening is making sure that a burglar who does get in still can't reach the safe.
File permissions
Every file and directory on the server has permissions: who may read, write and execute it. They're written as three digits, for the owner, the group and everyone else. The guideline from the WordPress documentation:
| What | Permissions | Meaning |
|---|---|---|
| Directories | 755 | Owner may do anything, others may read and open |
| Files | 644 | Owner may read and write, others may only read |
| wp-config.php | stricter, for example 600 or 640 | Only the owner (and possibly the web server group) may read |
Which value works for wp-config.php depends on how your host runs PHP: on most shared hosting, PHP runs as your own user, in which case 600 is enough. Try it and check whether the website still works.
Never 777. That means: everyone on the server may write to it. A guide that says "set the directory to 777 if uploading doesn't work" is solving the problem the wrong way. With good hosting, this is never necessary either.
Settings in wp-config.php
A few lines that make a big difference (above "stop editing"):
// Don't allow editing files from the dashboard (Appearance > Theme Editor and Plugins > Plugin Editor).
define( 'DISALLOW_FILE_EDIT', true );
// Never show error messages on visitors' screens (lesson 5.4).
define( 'WP_DEBUG', false );With DISALLOW_FILE_EDIT, the editor that lets you change PHP files of themes and plugins from the dashboard disappears. An intruder who takes over an administrator account can then no longer put malicious code into your theme with two clicks. You can still install plugins; if you also want to forbid that (for example on a website you manage for a client), there's DISALLOW_FILE_MODS, but then updates via the dashboard stop working too.
No PHP in uploads, no directory listing
In wp-content/uploads you should only find images and documents, never PHP files. Intruders who manage to upload a file through a vulnerability specifically try to place a PHP file there and then request it. Block this with a .htaccess file in the uploads directory (on LiteSpeed and Apache):
<Files *.php>
Require all denied
</Files>Also turn off directory listing. Without an index.php or index.html in a directory, some web servers show a list of all files ("Index of /wp-content/uploads/2026/11"). That's a shop window for anyone looking for backups or sensitive documents. In the .htaccess file of your web directory:
Options -IndexesMany hosts have already turned this off by default. The exercise for this lesson checks it.
No leftovers in the web directory
Everything in the web directory can be requested via the web, except what the server executes as PHP or blocks. Dangerous are:
- copies of wp-config.php (
wp-config.php.bak,wp-config.old,wp-config.php~from a text editor): these contain your database password and get sent as plain text (lesson 1.3); - backups such as
backup.zip,site.tar.gzordatabase.sqlin the web directory; - a
.gitdirectory left by a developer, which lets someone download your entire codebase (and sometimes passwords); - debug.log in wp-content (lesson 5.4);
- old installation or test files such as
info.phpwithphpinfo().
Keep backups outside the web directory or off the server, and clean up any leftovers.
Hiding the version: a small win
WordPress adds this to the HTML by default:
<meta name="generator" content="WordPress 7.1.4" />and the same in the RSS feed. PHP sometimes announces itself in a header too: X-Powered-By: PHP/8.3.14. Hiding this is reasonable, but don't expect too much from it: intruders' programs just try a vulnerability regardless of the version you show, and versions can often also be deduced from ?ver= after scripts. Hiding it is tidying up, not security. A website that hides its version but doesn't update is just as vulnerable.
Here's how, in your own small plugin or in the functions.php of your child theme:
remove_action( 'wp_head', 'wp_generator' );
add_filter( 'the_generator', '__return_empty_string' );And the PHP header with expose_php = Off in your host's PHP settings (in DirectAdmin under Select PHP version, Options), or with Header unset X-Powered-By in .htaccess.
Security headers
With a few headers, you tell the browser how to treat your website. The most important ones:
| Header | What it does |
|---|---|
Strict-Transport-Security | Only ever via https (lesson 3.1) |
X-Content-Type-Options: nosniff | The browser won't guess what type of file something is; prevents an uploaded "image" from being executed as a script |
X-Frame-Options: SAMEORIGIN or Content-Security-Policy: frame-ancestors 'self' | Other websites may not show your website in a frame; prevents clickjacking (an invisible button from your website hidden under someone else's) |
Referrer-Policy: strict-origin-when-cross-origin | When someone clicks through to another website, only your domain name is sent along, not the full address |
Permissions-Policy | Turns off browser features your website doesn't use (camera, microphone, location) |
Content-Security-Policy | Determines exactly which sources scripts and styles may come from. Powerful, but tricky to get right with WordPress and plugins; start with Report-Only mode |
In .htaccess (on LiteSpeed and Apache):
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>Afterwards, test whether everything still works: embedded content, payment buttons, a chat widget. External tools such as securityheaders.com and internet.nl show you what your website sends.
Security plugins, a fair look
There are many security plugins (for example Wordfence, Solid Security, All-In-One Security, Sucuri). They often bundle: a firewall, a malware scanner, login protection, two-factor authentication, notifications and a hardening checklist. Useful, but with limits:
- A firewall in a plugin (WAF) only starts once PHP is running. A malicious request first has to pass through the web server and PHP before the plugin sees it. A vulnerability in a PHP file that's called directly, or an attack that heavily loads PHP itself, isn't always stopped by it. A firewall on the server (for example ModSecurity, Imunify360 or a firewall from your host) sits earlier in the chain and stops more, without your plugin doing the computing work. Ask your host what's running on the server.
- A scanner finds things after the fact. A malware scanner tells you something's wrong. That's valuable, but it doesn't prevent anything. And a scanner on the same server as a hacked website can itself have been altered by the intruder.
- They don't replace updates. A plugin with a known vulnerability stays vulnerable, even with a security plugin alongside it. Some security services offer "virtual patches" that block known attacks until you've updated; handy as a stopgap, not as a replacement.
- They're a plugin themselves. More code, more to keep updated, sometimes a vulnerability of their own. And two security plugins at once often clash.
- They cost processing power, especially scanners that run on your own server.
A sensible approach: first check what your host already does (firewall, malware scanning, blocking login attempts). Add what's missing, usually two-factor authentication and limiting login attempts. An extensive security plugin is mainly useful with hosting that does little itself.
Summary
- Hardening limits the damage from a mistake or vulnerability; it doesn't replace updates and passwords.
- Directories 755, files 644, wp-config.php stricter; never 777.
- Turn on DISALLOW_FILE_EDIT, block PHP in uploads, turn off directory listing and clear backups, .git and debug.log out of the web directory.
- Hiding the version is tidying up, not security; security headers help the browser protect your visitors.
- A firewall on the server stops more than a plugin; security plugins are an addition, not a replacement.
Quiz
Exercise
Exercise A7.4: What does your website show to everyone? Enter your website's address. The tool reads the homepage and the RSS feed (/feed/) and checks: is there no WordPress version in the generator tag of the HTML and the feed, does the X-Powered-By header not reveal a PHP version, and does your website send X-Content-Type-Options: nosniff, protection against frames (X-Frame-Options or frame-ancestors) and a Referrer-Policy? It's fine (orange) if HSTS (lesson 3.1) and Permissions-Policy are missing.
Exercise A7.4b: No leftovers in the web directory. Prove that it's your own website (TXT record or file with the exercise's code). The tool then requests a short fixed list: the directory /wp-content/uploads/ (no directory listing), /.git/HEAD, /wp-content/debug.log and a few common names for copies of wp-config.php. It's fine if none of these return content (404 or 403). The tool doesn't try anything else and doesn't change anything.
Further reading
These articles describe the steps in WebOké's panels; they work similarly in other panels.
- DirectAdmin guide 11.5: File and directory permissions (chmod)
- DirectAdmin guide 11.4: Showing and editing hidden files such as .htaccess
- DirectAdmin guide 15.10: Web Application Firewall
- DirectAdmin guide 15.11: Imunify360: scanning your website for malware
- Blocking files via .htaccess, such as plugin-googlemap2-proxy.php
- PHP security
- Hardening WordPress (developer.wordpress.org, English): https://developer.wordpress.org/advanced-administration/security/hardening/
- Security headers (MDN, English): https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers#security
