A safe, production-ready Bash script for hardening WordPress file permissions and reducing common attack surfaces.
Designed for sysadmins, developers, and VPS users who want a repeatable, auditable, low-risk way to secure WordPress installs.
- π Secure WordPress file & directory permissions (per WordPress guidelines)
- π§Ή Removes insecure example config files
- π« Disables XML-RPC (common brute-force & DDoS vector)
- π Secures
.htaccess - π Terminal spinner (automatically disabled in cron / non-TTY)
- π§ͺ
--dry-runmode (no changes made) - π Accepts full path OR domain name
- π Auto-discovers WordPress installs
- π’
--all-sitesmode - ποΈ Automatic permission backups (with optional restore)
- π§ Multisite detection (informational only)
- π Strong safety checks to prevent destructive usage
- π Detailed logging to
/var/log/wordpress-permissions.log
- β Backup & Restore - Automatic permission backups before changes
- β Enhanced Security - Additional hardening steps (wp-includes, debug.log, directory listings)
- β Better Error Handling - Commands fail safely with proper feedback
- β Detailed Logging - Timestamped logs with error levels
- β Validation - Pre-flight checks and post-change verification
- β Restore Feature - Rollback changes if something goes wrong
- β Configurable Permissions - Adjust for your specific needs
- Linux (tested on Ubuntu / Debian)
- Bash 4+
- Root or sudo privileges
- WordPress installed under common web roots
git clone https://github.com/haywardgg/wp-hardening-tool.git
cd wp-hardening-tool
chmod +x wp-hardening.shsudo ./wp-hardening.sh example.comsudo ./wp-hardening.sh /var/www/html/example.comsudo ./wp-hardening.sh --dry-run example.comsudo ./wp-hardening.sh --all-sitessudo ./wp-hardening.sh --base-path=/srv/www example.comMultiple --base-path flags may be supplied.
sudo ./wp-hardening.sh --no-backup example.comsudo ./wp-hardening.sh --restore=/tmp/wp-perms-backup/example-latestsudo ./wp-hardening.sh --verbose example.comsudo ./wp-hardening.sh --base-path=/srv/www example.comsudo ./wp-hardening.sh --owner=webadmin --group=webgroup example.comsudo ./wp-hardening.sh --no-backup example.comsudo ./wp-hardening.sh --restore=/tmp/wp-perms-backup/example-latestsudo ./wp-hardening.sh --dry-run --ws-group=nginx example.comWhen a domain name is supplied (e.g. example.com), the script searches for:
/var/www/example.com/var/www/html/example.com- Any additional
--base-pathvalues
When a full path is supplied, it is used directly after validation.
- Sets safe file permissions (directories
755, files644) - Secures
wp-config.php(permissions600, webserver group) - Allows WordPress to write to
wp-content(directories775, files664) - Sets setgid on
wp-contentdirectories for proper group inheritance
- Disables
xmlrpc.php(sets000permissions) - Secures
wp-includesdirectory (sets750permissions) - Protects
wp-content/debug.log(sets000permissions when present) - Disables directory listing in
wp-content/uploadsvia.htaccess - Removes example files:
wp-config-sample.php,readme.html,license.txt - Secures
.htaccess(sets644permissions, webserver group)
- Creates permission backups before changes
- Validates WordPress structure before proceeding
- Prevents execution on system directories
- Post-change verification
- Detailed logging to
/var/log/wordpress-permissions.log
- Permission backups are created by default in
/tmp/wp-perms-backup(disable with--no-backup). - Restore the most recent backup for a site via
--restore=/tmp/wp-perms-backup/site-name-latest. - Actions and warnings are logged to
/var/log/wordpress-permissions.log.
This script is intentionally defensive.
It WILL NOT:
- Run on
/,/var,/var/www, or/var/www/html - Run on directories that are not WordPress
- Modify directories without
wp-config.php - Fail silently β all critical errors stop execution
It DOES:
- Validate WordPress structure before changes
- Refuse unsafe or ambiguous targets
- Support
--dry-runto preview changes - Create permission backups by default (stored in
/tmp/wp-perms-backup) - Provide restoration via
--restore=/tmp/wp-perms-backup/site-name-latest - Operate idempotently (safe to run repeatedly)
- Must be run as root or via sudo
- XML-RPC is disabled by default
(re-enable manually if required by your setup) - Always test with
--dry-runon production servers first
The script automatically disables animations when run from cron or other non-TTY environments.
0 3 * * * root /usr/local/bin/wp-hardening.sh --all-sites --no-backup0 2 * * 0 root /usr/local/bin/wp-hardening.sh --all-sites0 4 * * * root /usr/local/bin/wp-hardening.sh --all-sites >> /var/log/wp-hardening-cron.log 2>&1Cron output will be clean and readable.
"Permission denied" errors:
- Ensure script is run as root/sudo
- Check SELinux/AppArmor permissions
"No WordPress sites found":
- Use
--base-pathto add custom locations - Verify
wp-config.phpexists in target directories
"Refusing unsafe directory":
- Script prevents execution on system directories
- Use full path to specific WordPress install
Backup failures:
- Check
/tmpdirectory permissions - Ensure
getfaclis installed for ACL backups
If WordPress multisite is detected, the script will:
- Notify the user
- Apply the same permission logic
No multisite-specific changes are made.
- A malware scanner
- A firewall
- A replacement for server-level security
- A WordPress plugin
It is a baseline hardening tool, not a full security suite.
MIT License - see LICENSE file for details.
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Submit a pull request
Guidelines:
- Maintain backward compatibility
- Add tests for new features
- Update documentation
- Follow existing code style
Lee Hayward
Making WordPress servers safer by default.
If this tool helps you, please:
- Star the repository
- Share with other WordPress administrators
- Report issues and suggest improvements
Remember: Security is a process, not a product. Regular updates, monitoring, and defense-in-depth are essential for true security.