Secure wp-config.php and Server Configs: Hardening Guide
Lock down WordPress secrets and server configs before attackers scrape credentials. This guide covers real permission fixes, salt rotation, and web-server rules—plus when to book Fixwebnode server support.
If your WordPress site stores database passwords, salts, and debug flags in plain text, a single mis-set permission or backup copy can hand an attacker the keys. This guide walks homeowners and small-business operators through hardening wp-config.php and the Apache/Nginx files that protect it—so you can reduce exposure without guessing.
Scope assumes a typical Linux VPS or shared host with shell or SFTP access (Ubuntu/Debian-style paths are shown; adjust for your stack). You will set correct ownership and modes, move secrets out of the web root where possible, rotate authentication keys, lock down directory listing and PHP in uploads, and verify nothing sensitive is still world-readable. For hands-on help, Fixwebnode server support can take over the same checklist on production hosts across Australia and remote regions we cover in our service areas.
Why hardening wp-config.php and server configs matters
WordPress loads database credentials, table prefix, and cryptographic salts from wp-config.php on every request. If that file is readable by the wrong user, left as a .bak inside the document root, or served because the web server ignores extension rules, bots harvest it within minutes of a scan. Server configuration files—Apache .htaccess / vhost conf, Nginx site blocks, PHP-FPM pool settings—decide whether directory indexes appear, whether .php runs under wp-content/uploads, and whether backup names like wp-config.php~ are blocked.
Small sites are not exempt. Automated scanners do not care about traffic volume; they care about open permissions and default paths. Hardening here is preventive maintenance, not optional polish.
Common issues that leave credentials exposed
These problems show up repeatedly on DIY WordPress installs. Each has a different root cause.
1. World-readable or group-writable wp-config.php
Symptoms: File mode shows 644, 664, or worse 666/777; ls -l lists the web user or “others” with read. Hosting scanners or security plugins flag “config file permissions insecure.” In shared hosting, neighboring accounts may read your DB password if the filesystem allows it.
2. Authentication unique keys and salts missing, default, or never rotated after a compromise
Symptoms: AUTH_KEY, SECURE_AUTH_KEY, and related defines still say “put your unique phrase here,” or you restored from backup after malware and never rotated. Users stay logged in on stolen cookies; password resets feel ineffective because cookie hashing still matches old salts.
3. Backup copies and editor swap files inside the web root
Symptoms: Files such as wp-config.php.bak, wp-config.php.old, wp-config.php.save, wp-config.php~, or .wp-config.php.swp exist under the document root. A direct HTTPS request returns the full PHP source as text because the server treats unknown extensions as static files.
4. Web server allows indexes or PHP execution under uploads
Symptoms: Visiting /wp-content/uploads/ lists filenames; a dropped shell.php in uploads executes when requested. Access logs show probes for uploads/*.php. Often caused by missing Nginx location rules or Apache php_admin_flag / handler overrides not applied to that path.
How to fix each issue step by step
Fix 1 — Correct ownership and permissions on wp-config.php
Goal: only the account that runs PHP (and root for admin) can read the file; the web process should not need write access in normal operation.
Step 1 — Identify the web/PHP user and document root
ps aux | egrep 'php-fpm|apache2|httpd|nginx' | head
whoami
pwd
ls -la wp-config.php 2>/dev/null || ls -la /var/www/html/wp-config.phpNote the user column for php-fpm or apache (often www-data, nginx, or a per-site user like siteuser).
Step 2 — Set owner and restrictive mode
sudo chown root:www-data /var/www/html/wp-config.php
sudo chmod 640 /var/www/html/wp-config.php
# If PHP runs as the site owner instead of www-data:
# sudo chown siteuser:siteuser /path/to/wp-config.php
# sudo chmod 600 /path/to/wp-config.phpPrefer 600 when the PHP worker and file owner are the same account; use 640 with group www-data when root owns the file and PHP is in that group. Never leave 777 or world-writable bits.
Step 3 — Verify no “other” read bit and that WordPress still loads
stat -c '%a %U:%G %n' /var/www/html/wp-config.php
curl -sI https://your-domain.example/ | head -n 5Expect modes 600 or 640 and HTTP 200 on the homepage. If you get a database connection error, the PHP user cannot read the file—adjust group membership, not mode to 644.
Step 4 — Optional: move wp-config one level above the web root
WordPress automatically looks for wp-config.php in the parent of ABSPATH. If your document root is /var/www/html/public, place the file at /var/www/html/wp-config.php so the web server cannot map a URL to it even if misconfigured.
sudo mv /var/www/html/public/wp-config.php /var/www/html/wp-config.php
sudo chown root:www-data /var/www/html/wp-config.php
sudo chmod 640 /var/www/html/wp-config.phpWhen DIY is not enough: multi-site chroot layouts, OpenLiteSpeed, or hosts that reset permissions on deploy often need a specialist. Book Fixwebnode server support if permission changes break the site after every push.
Fix 2 — Generate and install fresh WordPress salts
Goal: invalidate old cookies and ensure every key define is long and unique.
Step 1 — Back up the current config
sudo cp -a /var/www/html/wp-config.php /root/wp-config.php.$(date +%F).bak
sudo chmod 600 /root/wp-config.php.*.bakKeep the backup outside the web root only.
Step 2 — Fetch new salts from the official generator
curl -sS https://api.wordpress.org/secret-key/1.1/salt/Copy the eight define(...) lines returned.
Step 3 — Replace the existing key block in wp-config.php
Edit carefully (use sudo -u appropriate editor). Remove old AUTH_KEY through NONCE_SALT lines and paste the new block. Do not break surrounding PHP syntax.
sudo nano /var/www/html/wp-config.php
# or: sudo sed -n '1,120p' /var/www/html/wp-config.php | headStep 4 — Confirm the site loads and force re-login
php -l /var/www/html/wp-config.php
curl -sI https://your-domain.example/wp-login.php | head -n 8php -l must report “No syntax errors.” All sessions are invalidated; admins log in again. If you suspect a prior breach, also rotate the MySQL password in both the database and the DB_PASSWORD define.
Call a pro when malware keeps rewriting wp-config.php after salt rotation—that is an integrity problem, not a one-line fix. Fixwebnode’s Secure Linux Admin & Full-Stack Infrastructure Australia work covers host-level cleanup alongside WordPress secrets.
Fix 3 — Find and remove exposed config backups
Goal: eliminate alternate filenames attackers request by dictionary.
Step 1 — Search the document root for config variants
sudo find /var/www/html -maxdepth 3 \( \
-iname 'wp-config*.bak' -o -iname 'wp-config*.old' -o \
-iname 'wp-config*.save' -o -iname 'wp-config*~' -o \
-iname 'wp-config*.swp' -o -iname 'wp-config.php.txt' -o \
-iname '.wp-config.php.swp' \
\) -lsStep 2 — Move hits to a root-only archive, then delete after review
sudo mkdir -p /root/wp-config-quarantine
sudo find /var/www/html -maxdepth 3 \( -iname 'wp-config*.bak' -o -iname 'wp-config*.old' -o -iname 'wp-config.php.txt' \) -exec mv -t /root/wp-config-quarantine {} +
sudo chmod -R 600 /root/wp-config-quarantineStep 3 — Block residual patterns at the web server
Apache (inside the site .htaccess or vhost):
<FilesMatch "(?i)^wp-config.*\.(bak|old|save|swp|txt|dist)$">
Require all denied
</FilesMatch>
<Files "wp-config.php">
Require all denied
</Files>Note: denying wp-config.php via Files stops HTTP fetch; PHP still includes it from disk. On Nginx, prefer:
location ~* /wp-config.*\.(bak|old|save|swp|txt)$ {
deny all;
return 404;
}
location = /wp-config.php {
deny all;
return 404;
}Reload after edit:
sudo apache2ctl configtest && sudo systemctl reload apache2
# or
sudo nginx -t && sudo systemctl reload nginxStep 4 — Verify direct URL access fails
curl -sI https://your-domain.example/wp-config.php
curl -sI https://your-domain.example/wp-config.php.bakExpect 403 or 404, not 200 with body length matching the file size.
If you also fight broken package mirrors after host recovery, the related runbook Fix Corrupted Linux APT Sources.list - Alabama Expert Support covers restoring clean APT sources so security updates install again—often the missing piece after a messy rebuild.
Fix 4 — Harden uploads and directory listing in server config
Goal: no autoindex; no PHP execution under user-upload paths.
Step 1 — Disable indexes for WordPress content trees (Apache)
# In the vhost or a committed .htaccess under wp-content/
Options -IndexesStep 2 — Nginx: turn off autoindex and neutralize PHP in uploads
location /wp-content/uploads/ {
location ~* \.php$ {
deny all;
return 404;
}
autoindex off;
}Step 3 — Add a defensive .htaccess inside uploads (Apache/php-fpm)
printf '%s\n' \
'<FilesMatch "\\.(?i:php|phtml|php[0-9]|phar)$">' \
' Require all denied' \
'</FilesMatch>' \
'Options -Indexes' \
| sudo tee /var/www/html/wp-content/uploads/.htaccess
sudo chmod 644 /var/www/html/wp-content/uploads/.htaccessStep 4 — Verify
# Should not list files:
curl -sI https://your-domain.example/wp-content/uploads/
# Drop a harmless probe only in a test env, then remove it:
echo '<?php echo "x";' | sudo tee /var/www/html/wp-content/uploads/_probe.php
curl -sI https://your-domain.example/wp-content/uploads/_probe.php
sudo rm -f /var/www/html/wp-content/uploads/_probe.phpListing should be denied; the probe must not return 200 with PHP output.
Also disable production debug in wp-config.php if still on:
define('WP_DEBUG', false);
define('WP_DEBUG_LOG', false);
define('WP_DEBUG_DISPLAY', false);
@ini_set('display_errors', 0);Debug logs under wp-content/debug.log can leak paths and queries if left web-accessible—remove or block that file the same way as config backups.
When DIY is enough vs when to book Fixwebnode
DIY is enough when you have shell access, a single site, clean file ownership, and the symptoms match one of the four issues above. Work from a snapshot or host backup first; change one control at a time; re-test login and checkout flows after each step.
Book a specialist when any of these apply: you cannot identify the PHP runtime user; permissions reset on every deploy or host panel “fix”; wp-config.php is rewritten by malware after you harden it; you run nested reverse proxies, LiteSpeed, or container mounts that ignore classic chmod; database credentials may already have leaked and need coordinated rotation with firewall rules; or the site is down and you need recovery plus hardening in one pass.
Fixwebnode operates as a direct specialist for server support—not a bid board. Geography and remote coverage are listed on our all service areas page, including Australian production hosts and selected remote regions. Deep Linux admin and stack lockdown is available via Secure Linux Admin & Full-Stack Infrastructure Australia.
Next step: lock the config layer with a specialist conversation
You now have a concrete path: restrict wp-config.php modes and placement, rotate salts, quarantine backup copies, and stop indexes plus PHP under uploads. Those four controls close the most common credential-leak paths on small WordPress servers.
If you want a second set of eyes on a live host—or the DIY steps conflict with your panel’s permission model—start a conversation with Fixwebnode and walk through the same checklist on your stack. Book through the landing page: server support step-by-step with Fixwebnode. Bring SSH access notes and whether you use Apache or Nginx so the session stays focused on config hardening, not guesswork.