WordPress Security Hardening — .htaccess, wp-config.php and File Permissions Guide
In this tutorial, you'll learn how to harden WordPress security — securing wp-config.php with salts and keys, writing .htaccess rules to block exploits, setting correct file permissions, enabling SSL, and deploying two-factor authentication.
What You'll Learn
- Why WordPress is targeted — 40% of the web, popular attack vector
- Securing wp-config.php — salts, keys, and security constants
- .htaccess security rules — block directory browsing, wp-includes, XML-RPC, bad bots
- File permissions — directories 755, files 644, wp-config.php 600
- SSL/HTTPS setup with Let's Encrypt and mixed content fixes
- Two-factor authentication with Wordfence and WP 2FA plugin
- Limit login attempts, disable file editing, disable PHP execution in uploads
- Security headers — HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy
- Regular security audits and monitoring
Why It Matters
WordPress powers over 40% of all websites, making it the single biggest target for automated attacks, botnets, and exploit scanners. Hackers do not target WordPress because it is insecure — they target it because there are millions of installs, and even a 0.01% success rate yields thousands of compromised sites. A single vulnerability in an outdated plugin, a weak password, or a misconfigured server can give an attacker full control of your site. Security hardening is not optional; it is a fundamental responsibility of anyone running a WordPress site. The techniques you will learn here block 99% of automated attacks before they reach your application.
Real-World Use
A small e-commerce store running WordPress with WooCommerce gets hit by a botnet that scans for xmlrpc.php to brute-force credentials. Because the sysadmin disabled XML-RPC via .htaccess, the bot gets a 403 Forbidden response. The same store uses Wordfence 2FA for all admin accounts and has DISALLOW_FILE_EDIT enabled, so even if a contributor account is compromised, the attacker cannot inject malicious code into theme files. These three rules alone prevent the most common WordPress compromises.
Learning Path
flowchart LR
A["User Roles & Capabilities"] --> B["Security Hardening
You are here"]:::current
B --> C["Performance Optimization"]
C --> D["Maintenance & Backups"]
D --> E["Multisite Network"]
classDef current fill:#38bdf8,color:#0f172a,stroke-width:2px
Why WordPress Is Targeted
WordPress is the most popular CMS on the planet. That alone makes it a target. Automated scanners crawl the web looking for:
- Default admin accounts (username
admin) - Outdated WordPress versions with known CVEs
- Weak passwords vulnerable to brute force
- xmlrpc.php for credential stuffing attacks
- Vulnerable plugins and themes
Think of your WordPress site like a house in a busy neighbourhood. Most burglars are not targeting you specifically — they are walking down the street trying every door handle. Security hardening makes your door locked while others leave theirs open. The attacker moves on.
Securing wp-config.php
The wp-config.php file is the most sensitive file in your WordPress installation. It contains database credentials, authentication salts, and security constants. If an attacker reads this file, they own your site.
Move wp-config.php Above Web Root
WordPress actually looks for wp-config.php in the directory above your public_html folder by default. Take advantage of this:
# Move wp-config.php one level above public_html
mv /var/www/html/wp-config.php /var/www/wp-config.php
The file is now inaccessible via the web, even if your web server is misconfigured. PHP can still read it because it executes on the server, not through the web.
Authentication Salts and Keys
Salts are random strings that WordPress uses to encrypt user cookies and authentication tokens. If an attacker steals your database but not your salts, they cannot forge login cookies.
// In wp-config.php — never use these, generate fresh ones
define('AUTH_KEY', 'put your unique phrase here');
define('SECURE_AUTH_KEY', 'put your unique phrase here');
define('LOGGED_IN_KEY', 'put your unique phrase here');
define('NONCE_KEY', 'put your unique phrase here');
define('AUTH_SALT', 'put your unique phrase here');
define('SECURE_AUTH_SALT', 'put your unique phrase here');
define('LOGGED_IN_SALT', 'put your unique phrase here');
define('NONCE_SALT', 'put your unique phrase here');
You regenerate salts at any time by visiting https://api.wordpress.org/secret-key/1.1/salt/. Copy the output and paste it into wp-config.php, replacing the existing values.
# Fetch fresh salts from the WordPress API
curl https://api.wordpress.org/secret-key/1.1/salt/
When you change salts, all existing login sessions are invalidated. Every user must log in again. This is useful if you suspect a session has been compromised.
Essential Security Constants
Add these to your wp-config.php file above the line that says "That's all, stop editing":
// Disable file editing from the admin dashboard
define('DISALLOW_FILE_EDIT', true);
// Force all admin and login pages to use SSL
define('FORCE_SSL_ADMIN', true);
// Enable automatic updates for all core releases
define('WP_AUTO_UPDATE_CORE', true);
// Limit post revisions to prevent database bloat
define('WP_POST_REVISIONS', 5);
// Set automatic trash emptying to 7 days
define('EMPTY_TRASH_DAYS', 7);
// Disable theme and plugin file editing via the admin
define('DISALLOW_FILE_MODS', true);
Why each one matters:
DISALLOW_FILE_EDIT— Prevents admins from editing theme and plugin files through the WordPress admin editor. If an attacker gains admin access, they cannot inject PHP code into your theme files.FORCE_SSL_ADMIN— Ensures all login cookies and authentication data travel over encrypted HTTPS. Without this, a man-in-the-middle attack can steal your session.WP_AUTO_UPDATE_CORE— Enables automatic updates for minor releases and security patches. Most attacks target known vulnerabilities in outdated versions.DISALLOW_FILE_MODS— Blocks theme and plugin installation, update, and deletion from the admin panel. Useful for client sites where you handle changes via staging.
.htaccess Security Rules
The .htaccess file is an Apache/Nginx configuration file that controls access at the directory level. It lives in your WordPress root folder.
Block Directory Browsing
By default, Apache will list the contents of a directory if no index file exists. Attackers use this to explore your file structure:
# Disable directory browsing
Options -Indexes
Block Access to wp-includes
The wp-includes directory contains core PHP files that should never be accessed directly:
# Block direct access to wp-includes
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^wp-includes/(.*)$ / [F,L]
</IfModule>
The [F] flag returns a 403 Forbidden response. The [L] flag tells Apache to stop processing further rules.
Block Access to wp-config.php
Even though you moved wp-config.php above web root, add this as defense in depth:
# Block access to wp-config.php
<Files wp-config.php>
order allow,deny
deny from all
</Files>
Disable XML-RPC
XML-RPC (xmlrpc.php) is a remote API that allows external applications to interact with WordPress. It is commonly used for brute-force attacks because a single request can test hundreds of credentials:
# Block XML-RPC entirely
<Files xmlrpc.php>
order deny,allow
deny from all
</Files>
If you need XML-RPC for the WordPress mobile app or Jetpack, you can restrict it by IP instead:
# Allow XML-RPC only from specific IPs
<Files xmlrpc.php>
order deny,allow
deny from all
allow from 192.168.1.100
</Files>
Block Bad Bots by User Agent
Automated scanners identify themselves with specific user agents. Block the most common ones:
# Block bad bots and scanners
RewriteCond %{HTTP_USER_AGENT} (ahrefsbot|mj12bot|semrushbot|dotbot|mega-index) [NC]
RewriteRule .* - [F,L]
The [NC] flag makes the match case-insensitive. The [F] returns 403. Adjust the list based on who is hitting your site — check your access logs first.
File Permissions
File permissions control who can read, write, and execute files on your server. Incorrect permissions are one of the most common security holes.
The Correct Setup
# Directories — 755 (owner can write, everyone can read/execute)
find /var/www/html -type d -exec chmod 755 {} \;
# Files — 644 (owner can write, everyone can read)
find /var/www/html -type f -exec chmod 644 {} \;
# wp-config.php — 600 or 440 (only owner or group can read)
chmod 600 /var/www/wp-config.php
chmod 600 /var/www/html/.htaccess
Why These Numbers?
The three digits represent: owner, group, and everyone.
- 7 (rwx) — Read (4) + Write (2) + Execute (1) = 7. Directories need execute so the web server can list files.
- 6 (rw-) — Read (4) + Write (2) = 6. Files need write for the owner only.
- 4 (r--) — Read only. wp-config.php needs no write access once configured.
- 0 (---) — No access. Used for sensitive files that should not be web-accessible.
# Verify permissions
ls -la /var/www/html/wp-config.php
# Should show: -rw------- 1 www-data www-data ...
wp-content Permissions
The wp-content directory needs special attention because plugins and themes write files here:
# wp-content directories — 755
find /var/www/html/wp-content -type d -exec chmod 755 {} \;
# wp-content files — 644
find /var/www/html/wp-content -type f -exec chmod 644 {} \;
The uploads folder may need group write (775) if your web server runs as a different user than your FTP user:
# Uploads directory — 775 if needed for web server write access
chmod -R 775 /var/www/html/wp-content/uploads
SSL/HTTPS Setup
SSL (Secure Sockets Layer) encrypts traffic between the browser and your server. Google uses HTTPS as a ranking signal, and modern browsers mark HTTP sites as "Not Secure."
Let's Encrypt Free SSL
# Install Certbot on Ubuntu
sudo apt update
sudo apt install certbot python3-certbot-apache
# Obtain and install SSL certificate
sudo certbot --apache -d yourdomain.com -d www.yourdomain.com
# Verify auto-renewal (Certbot adds a systemd timer)
sudo certbot renew --dry-run
Update Site URL to HTTPS
After installing SSL, update your WordPress URLs:
# Via WP-CLI
wp search-replace 'http://yourdomain.com' 'https://yourdomain.com' --skip-columns=guid
# Or via phpMyAdmin SQL
UPDATE wp_options SET option_value = 'https://yourdomain.com' WHERE option_name = 'siteurl';
UPDATE wp_options SET option_value = 'https://yourdomain.com' WHERE option_name = 'home';
Fix Mixed Content
Mixed content occurs when your page loads over HTTPS but some assets (images, scripts, stylesheets) load over HTTP. Browsers block these, breaking your site:
-- Fix mixed content in post content and widgets
UPDATE wp_posts SET post_content = REPLACE(post_content, 'http://yourdomain.com', 'https://yourdomain.com');
UPDATE wp_postmeta SET meta_value = REPLACE(meta_value, 'http://yourdomain.com', 'https://yourdomain.com');
You can also use a plugin like Really Simple SSL which handles the entire migration and fixes mixed content automatically.
Two-Factor Authentication
Passwords alone are not enough. Two-factor authentication (2FA) adds a second verification step — usually a one-time code from an authenticator app.
Using Wordfence 2FA
# Install Wordfence via WP-CLI
wp plugin install wordfence --activate
Navigate to Wordfence > Login Security and enable 2FA for administrator accounts. Each admin scans a QR code with Google Authenticator or Authy and enters a 6-digit code on login.
Using WP 2FA Plugin
# Install WP 2FA plugin
wp plugin install wp-2fa --activate
This plugin lets you force 2FA for specific roles. Configure it under Users > WP 2FA. You can:
- Require 2FA for all administrators
- Allow users to choose between email codes and authenticator app
- Generate backup codes (users print and store these)
Backup Codes
When setting up 2FA, the plugin generates 10 one-time backup codes. Store these somewhere safe, like a password manager. If you lose your phone and have no backup codes, you will be locked out of your site.
// A backup code looks like this — store it securely
// XXXX-XXXX-XXXX-XXXX
Limit Login Attempts
Without login limiting, an attacker can try thousands of passwords per minute:
Using Wordfence
Wordfence includes brute-force protection. It blocks an IP after a configurable number of failed attempts:
# Configure via WP-CLI
wp wordfence config set loginSec_lockInvalidUsers 1
wp wordfence config set loginSec_maxFailures 20
wp wordfence config set loginSec_maxForgotPasswd 5
Using Limit Login Attempts Reloaded
# Install the plugin
wp plugin install limit-login-attempts-reloaded --activate
Configure: block after 4 failed attempts, lockout for 20 minutes, increase lockout time with each attempt.
Disable File Editing and PHP Execution
We already covered DISALLOW_FILE_EDIT in wp-config.php. The next step is preventing PHP execution in directories where users upload files.
Disable PHP Execution in Uploads
Create a .htaccess file in /wp-content/uploads/:
# wp-content/uploads/.htaccess
<Files *.php>
deny from all
</Files>
This ensures that even if an attacker uploads a malicious PHP file (e.g., via a vulnerable plugin), it cannot execute.
Disable PHP Execution in Specific Directories
# In root .htaccess — block PHP execution in wp-content/uploads
<Directory /var/www/html/wp-content/uploads>
php_admin_flag engine off
</Directory>
Security Headers
HTTP security headers tell the browser how to behave when loading your site. They prevent common attacks like clickjacking, MIME sniffing, and data injection.
# Add to .htaccess
<IfModule mod_headers.c>
# HSTS — force HTTPS for one year
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
# Prevent MIME type sniffing
Header always set X-Content-Type-Options "nosniff"
# Prevent clickjacking — do not load in iframes
Header always set X-Frame-Options "SAMEORIGIN"
# Control referrer information
Header always set Referrer-Policy "strict-origin-when-cross-origin"
# Restrict browser features
Header always set Permissions-Policy "geolocation=(), microphone=(), camera=()"
</IfModule>
- Strict-Transport-Security (HSTS) — Tells the browser to always use HTTPS. Prevents SSL-strip attacks.
- X-Content-Type-Options — Prevents the browser from guessing MIME types. Stops certain drive-by download attacks.
- X-Frame-Options — Prevents your site from being loaded in an iframe on another domain. Blocks clickjacking.
- Referrer-Policy — Controls what referrer information is sent with requests. Prevents leaking sensitive URL parameters.
- Permissions-Policy — Disables browser features like geolocation and camera that your site does not need.
Regular Security Audits
Security is not a one-time setup. You need ongoing monitoring:
# What to check monthly
1. Review failed login attempts (Wordfence > Live Traffic)
2. Check for updated plugins and themes
3. Run a site scan (Wordfence > Scan)
4. Review user accounts — remove inactive users
5. Check file integrity — look for unexpected changes
6. Verify SSL certificate is valid and not expiring
7. Review .htaccess rules for unauthorized changes
8. Check error logs for suspicious patterns
9. Test your backups (restore to staging)
10. Run a security plugin scan
Automate what you can:
# Cron job to check file integrity weekly
0 3 * * 0 /usr/bin/wp core verify-checksums --path=/var/www/html >> /var/log/wordpress-checksums.log
Common Mistakes
Setting file permissions to 777 to fix a "cannot write" error. This gives everyone full access to your files. If a script on your server is compromised, the attacker can modify any 777 file. The correct fix is to change the file owner to match the web server user (usually www-data).
Leaving wp-config.php in the web root. Even with a block rule in .htaccess, the file can still be read if Apache is misconfigured or if another vulnerability exposes file contents. Move it above web root for true protection.
Using the same WordPress salts for years. Salts should be regenerated periodically, especially after a security incident or when a user with admin access leaves the team. Each regeneration invalidates all existing sessions.
Enabling XML-RPC without knowing what it does. Many sites have xmlrpc.php accessible because it is enabled by default. Attackers use it for credential stuffing — trying thousands of username/password combinations in a single request. If you do not use the WordPress mobile app or Jetpack, disable it.
Relying only on plugins for security. Security plugins are important, but they cannot fix misconfigured file permissions, an outdated PHP version, or missing security headers. Layer security at every level — server, filesystem, application, and network.
Practice Questions
You just regenerated your WordPress salts. What happens to currently logged-in users? Answer: All existing login sessions and cookies are invalidated. Every user, including yourself, must log in again. This is why you should regenerate salts during maintenance windows, not during peak traffic.
Why should you set
DISALLOW_FILE_EDITto true in wp-config.php? Answer: It removes the Theme and Plugin file editors from the WordPress admin dashboard. If an attacker gains access to an admin account, they cannot inject malicious PHP code into your theme files through the editor. They would need FTP or file manager access instead.What does the
[F]flag do in an Apache RewriteRule? Answer: It returns a 403 Forbidden response to the client. When combined with a RewriteCond that matches malicious patterns (like bad bot user agents or direct access to wp-includes), the server tells the client "no" without revealing any content.
Challenge: Run a WordPress security audit on a test site. Use WPScan (command-line WordPress vulnerability scanner) to scan for known vulnerabilities in plugins, themes, and core. Then run a Wordfence scan and compare results. Document three issues you found and how you fixed them. This mirrors the real workflow you will follow when taking over a new client site.
FAQ
Mini Project
Harden a fresh WordPress installation from scratch:
- Install WordPress on a local environment (LocalWP or XAMPP).
- Move wp-config.php above the web root.
- Generate and apply fresh salts from the WordPress API.
- Add security constants:
DISALLOW_FILE_EDIT,FORCE_SSL_ADMIN,WP_AUTO_UPDATE_CORE,DISALLOW_FILE_MODS. - Create a custom .htaccess with: directory browsing disabled, wp-includes blocked, XML-RPC blocked, security headers (HSTS, X-Content-Type-Options, X-Frame-Options).
- Set correct file permissions (755 directories, 644 files, 600 wp-config.php).
- Install Let's Encrypt SSL (or use self-signed for local testing).
- Install Wordfence, enable 2FA for the admin account, set up login limiting.
- Run a Wordfence scan and verify no critical issues remain.
- Document every step in a hardening checklist that you can reuse for client sites.
This exercise gives you a repeatable security baseline that you will apply to every WordPress site you manage.
What's Next
Now that your WordPress site is hardened against common attacks, learn how to keep it fast and lean:
Continue to Lesson 49: Performance Optimization — Caching, CDN, database optimization, and Core Web Vitals.
Related lessons:
- Maintenance & Backups — Automate backups and updates
- PHP Security Best Practices — Secure Coding in the WordPress context
- Apache/Nginx Configuration — Server-level security hardening
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro