Skip to main content

Hardening wp

Security case study: Hardening WordPress wp-config.php.

What You Will Learn

  1. Why wp-config.php is the most sensitive WordPress file.
  2. 12 Linux and server-level hardening methods, each with command, expected output, and purpose.
  3. How to combine **permissions, ownership, ACLs, symlinks, and encryption for layered security.
  4. How to block HTTP access, prevent tampering, and monitor suspicious activity.
  5. Best practices for **secure deployment, backups, and monitoring in WordPress VPS hosting.

Prerequisites

  • **Access Level: sudo or root.
  • **Software: Apache/Nginx/OLS, acl, auditd (optional).
  • **Knowledge: File permissions, ownership, WordPress structure.

5W + 1H Framework

QuestionAnswer
What12 practical methods to harden wp-config.php.
WhyProtects database credentials, salts, and security definitions from theft or modification.
WhenImmediately after setup, before production, and after migrations.
WhereDefault: /var/www/html/wp-config.php (or relocated).
WhoSysadmins, DevOps, WordPress VPS owners.
HowBy combining Linux file controls, web server rules, ACLs, immutability, and monitoring.

Hardening Methods (with Expected Output & Purpose)

Restrict File Permissions

chmod 600 /var/www/html/wp-config.php
ls -l /var/www/html/wp-config.php

Expected Output:

-rw------- 1 www-data www-data 4201 Sep 24 wp-config.php

**Purpose: Only the owner can read/write. Others have no access. Prevents other system users from reading DB credentials.

Use Root Ownership with Group Read

chown root:www-data /var/www/html/wp-config.php
chmod 640 /var/www/html/wp-config.php
ls -l /var/www/html/wp-config.php

Expected Output:

-rw-r----- 1 root www-data 4201 Sep 24 wp-config.php

**Purpose: Root owns the file, so PHP/webserver (www-data) can read but cannot modify. Adds protection against web-based exploits.

Move Outside Web Root

mv /var/www/html/wp-config.php /var/www/wp-config.php
ls -l /var/www/wp-config.php

Expected Output:

-rw------- 1 root www-data 4201 Sep 24 wp-config.php

**Purpose: File sits outside public directory, preventing direct HTTP access. WordPress auto-detects it above root.

Deny via Web Server Config

  • Apache:

    <files wp-config.php>
    deny from all
    </files>

  • Nginx:

    location ~* wp-config.php { deny all;

  • OLS:

    <location wp-config.php> deny all </location>

Verification:

curl -I http://localhost/wp-config.php

Expected Output:

HTTP/1.1 403 Forbidden

**Purpose: Prevents file from being served if misconfigured or still under web root.

Apply ACLs

setfacl -m u:www-data:r /var/www/html/wp-config.php
getfacl /var/www/html/wp-config.php

Expected Output:

user::rw-
user:www-data:r--
group::---
mask::r--
other::---

**Purpose: Grants only the www-data user read access, even if ownership is root. Granular access control.

Make File Immutable

sudo chattr +i /var/www/html/wp-config.php
lsattr /var/www/html/wp-config.php

Expected Output:

----i--------e----- wp-config.php

**Purpose: Prevents modification/deletion of file, even by root, until immutable flag is removed (chattr -i). Stops malware from overwriting the config.

mv /var/www/html/wp-config.php /etc/wp-config-secure.php
ln -s /etc/wp-config-secure.php /var/www/html/wp-config.php
ls -l /var/www/html/wp-config.php

Expected Output:

lrwxrwxrwx 1 root root 28 Sep 24 wp-config.php -> /etc/wp-config-secure.php

**Purpose: Keeps real config in a secure system directory, only exposing a symlink in the web root.

Apply SELinux/AppArmor Context

chcon -t httpd_sys_content_t /var/www/html/wp-config.php
ls -Z /var/www/html/wp-config.php

Expected Output (SELinux enabled):

-rw-------. root www-data system_u:object_r:httpd_sys_content_t:s0 wp-config.php

**Purpose: Uses SELinux/AppArmor to strictly control which processes can access the file.

Encrypt with GPG

gpg -c /var/www/html/wp-config.php
ls -l /var/www/html/wp-config.php.gpg

Expected Output:

-rw-r--r-- 1 root root 2000 Sep 24 wp-config.php.gpg

**Purpose: Protects config in backups and repos. Only decrypted on deploy. Ensures secrets arent stored in plaintext.

Disable Directory Listings

  • Apache:

    Options -Indexes

  • Nginx:

    autoindex off;

Verification:

curl -I http://localhost/wp-content/

Expected Output:

HTTP/1.1 403 Forbidden

**Purpose: Prevents attackers from browsing directories in case of misconfigurations.

Use Environment Variables

Edit wp-config.php:

define('DB_USER', getenv('WP_DB_USER'));
define('DB_PASSWORD', getenv('WP_DB_PASS'));

Set variables in system:

export WP_DB_USER=wpuser
export WP_DB_PASS=securepass
printenv | grep WP_DB

Expected Output:

WP_DB_USER=wpuser
WP_DB_PASS=securepass

**Purpose: Credentials stored outside code. Safer for CI/CD and avoids committing secrets.

Monitor Access Attempts

auditctl -w /var/www/html/wp-config.php -p rwxa -k wpconfig_audit
ausearch -k wpconfig_audit

Expected Output:

time->Tue Sep 24 10:45:12 2025
type=PATH msg=audit(...): item=0 name="/var/www/html/wp-config.php"

**Purpose: Detects every read/write/execute attempt on the file. Helps identify intrusion or suspicious behavior.

Practical Scenarios

ScenarioMethod AppliedOutcome
Fresh installchmod 600DB creds protected.
Shared VPSRoot ownership + 640PHP can read, root controls writes.
CI/CD pipelineEnv vars + symlinksSecrets secured in deployment process.
Host misconfigWeb server deny rulesHTTP requests blocked.
Malware injectionchattr +iConfig can’t be overwritten.
ForensicsAudit loggingSuspicious file access detected.

Best Practices

  1. Use least privilege: start with 600 or 640.
  2. Combine multiple layers: permissions + ownership + server rules + monitoring.
  3. Move wp-config.php **outside web root if possible.
  4. **Never use 777 for any fix. Correct ownership instead.
  5. Use **root ownership with 640 in production.
  6. Enable **immutable flag (chattr +i) after config is finalized.
  7. Add **deny rules in Apache/Nginx/OLS as a fail-safe.
  8. Prefer **environment variables for DB creds in CI/CD pipelines.
  9. Encrypt backups containing wp-config.php.
  10. Use **audit logging to monitor all access attempts.
  11. Recheck perms after migration or restore.
  12. Regularly test with curl to ensure file isnt accessible.

Quick Lab

cd /var/www/html

# 1. Lock down permissions
chmod 600 wp-config.php
ls -l wp-config.php

# 2. Root owns with group-read
chown root:www-data wp-config.php
chmod 640 wp-config.php
ls -l wp-config.php

# 3. Make immutable
sudo chattr +i wp-config.php
lsattr wp-config.php

# 4. Test HTTP access
curl -I http://localhost/wp-config.php

Expected Results:

  • File shows rw-r----- root www-data.
  • lsattr shows i flag.
  • curl returns 403 Forbidden.

Cheat Sheet

MethodCommandExpected OutputPurpose
Restrict permschmod 600 wp-config.php-rw-------Owner-only access
Root ownershipchown root:www-data wp-config.php && chmod 640-rw-r----- root www-dataRoot controls, PHP can read
Move above rootmv wp-config.php /var/www/File in /var/www/Hidden from web
Block accesscurl -I http://localhost/wp-config.php403 ForbiddenVerify deny rules
Immutable flagchattr +i wp-config.php----i-------- in lsattrStops modifications
ACLssetfacl -m u:www-data:r wp-config.phpgetfacl shows ACLGranular control
Env vars`printenvgrep WP_DB`Shows DB creds
Audit monitorausearch -k wpconfig_auditLogs read/write eventsDetects intrusion

Mini Quiz

  1. What does chmod 600 achieve for wp-config.php?
  2. Why is chown root:www-data with 640 safer than www-data:www-data with 644?
  3. What is the effect of chattr +i on a file?
  4. How does moving wp-config.php outside web root improve security?
  5. Which command shows who last accessed wp-config.php?