Hardening
Making WordPress Secure
Overview
If you already have an instance of WordPress running and have only installed the Underscores theme we need to ensure that the rest of your WordPress installation has some basic security measures in place. The Grove follows the recommended security protocols defined in this article: https://wordpress.org/documentation/article/hardening-wordpress/
You can follow the guide below to ensure these practices are in place.
wp-config.php
Inside of the wp-config.php file we need to ensure we have the following security measures in place:
Authentication unique keys and salts.
It is important that we update the default authentication keys using the recommended WordPress salts. This is very easy to do.
To generate the new keys go to this URL (found inside of the wp-config.php file): https://api.wordpress.org/secret-key/1.1/salt/
Find the block of defined keys:
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' );
Replace this block with the newly copied keys. you're end result will look something like this:
define('AUTH_KEY', '&NL?#~DOT_{~5MxJ 6_lr0|(DCBB/c~>|q|-Ac@|u#Q).bVS_}onWHd&rr1r09K&');
define('SECURE_AUTH_KEY', '|Pu7x_f+K<y*4<zh}ya{<+Od|-H)%1/cfS$Lu8]uKeI=_^i)uXw^CGCj>Rm[$%KX');
define('LOGGED_IN_KEY', 'U#yaM:TnuqIJ-^S-N|>F<*|dv(}-gN(do)~/,}5n{STWI^?@dY}iH^(h_iZo!)!i');
define('NONCE_KEY', '#;VVc84Jv3kod~X?Fo94kI:2#>hYub#k5$_A5TL-{U#MC/ELl={^NV-S}t+;D_63');
define('AUTH_SALT', '_,z-tDcaSsbxu]/5.-{&+>w[+~|?tQTYjgDF`bweRC`m7M$}Lk)ms#=m6OT4Ll7h');
define('SECURE_AUTH_SALT', 'BwAuv_[Y3c*0zd2[{jzr7bB;t6TL>vRiomgf~_Qij:WZ.}ZeB#&cn,I>|{bY!GJ%');
define('LOGGED_IN_SALT', '[D6xJgzCVY~}7:SG1FWpnM|juAMZUN*:d+dwgz9E5!hY&[-^5jk=epDFu=o8r&<-');
define('NONCE_SALT', 'Uz8[!~-u0g@l+b3A>ybwpC_Go k}dR*Mv(p#&|edM|`&tQB[@hYQB8 i4P~`cxx');
Change the table prefix
It's important you update the default WordPress table prefix (wp_) to something less common. This prefix can be related to the client on someway or entirely random.
Find this line in the wp-config.php file:
$table_prefix = 'wp_';
and change it to something like:
$table_prefix = 'grv_';
Disable the Theme File Editor
This prevents users, specifically admin users, from making edits to the theme files from directly within the WordPress admin.
Underneath the line definining the debug mode:
define( 'WP_DEBUG', false );
Add the following block of code:
## Disable Editing in Dashboard
define('DISALLOW_FILE_EDIT', true);
The end result will look like this:
/**
* For developers: WordPress debugging mode.
*
* Change this to true to enable the display of notices during development.
* It is strongly recommended that plugin and theme developers use WP_DEBUG
* in their development environments.
*
* For information on other constants that can be used for debugging,
* visit the documentation.
*
* @link https://wordpress.org/support/article/debugging-in-wordpress/
*/
define( 'WP_DEBUG', false );
## Disable Editing in Dashboard
define('DISALLOW_FILE_EDIT', true);
/* Add any custom values between this line and the "stop editing" line. */
.htaccess
If we're using Apache as our Web Server we'll need to add some additional rules to the .htaccess file. You can simply copy and paste each item, in order, starting at the top of the file.
Security Headers
# Security Headers
<IfModule mod_headers.c>
Header set X-XSS-Protection "1; mode=block"
Header set X-Frame-Options "SAMEORIGIN"
Header set X-Content-Type-Options "nosniff"
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
Header always set Content-Security-Policy "upgrade-insecure-requests;"
Header set Referrer-Policy "same-origin"
Header set Feature-Policy "geolocation 'self'; vibrate 'none'"
Header always set Expect-CT "max-age=7776000, enforce"
</IfModule>
XML-RPC DDoS & Trackback & Pingback Protection
# XML-RPC DDoS & TRACKBACK/PINGBACK PROTECTION
<FilesMatch "^(xmlrpc\.php|wp-trackback\.php)">
#Require all denied
#Require ip x.x.x.
</FilesMatch>
Brute-force login prevention
Stops direct POSTing to wp-login.php
# Brute-force login prevention (stops direct POSTing to wp-login.php)
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} POST
RewriteCond %{REQUEST_URI} wp-login\.php*
RewriteCond %{HTTP_HOST}@@%{HTTP_REFERER} !^([^@]*)@@https?://\1/.*
RewriteRule .* - [F,L]
</IfModule>
Set expire header for static assets
# Set expire header for static assets
<IfModule mod_expires.c>
ExpiresActive On
<FilesMatch "\.(css|eot|gif|ico|jpeg|jpg|js|png|svg|swf|ttf|woff)">
ExpiresDefault "access plus 1 year"
</FilesMatch>
</IfModule>
Block the include-only files
# Block the include-only files.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>
Disallow direct access to the wp-config.php file
# Disallow direct access to the wp-config.php file
<files wp-config.php>
Require all denied
</files>
Full .htaccess file
# Security Headers
<IfModule mod_headers.c>
Header set X-XSS-Protection "1; mode=block"
Header set X-Frame-Options "SAMEORIGIN"
Header set X-Content-Type-Options "nosniff"
Header always set Strict-Transport-Security "max-age=63072000; includeSubDomains"
Header always set Content-Security-Policy "upgrade-insecure-requests;"
Header set Referrer-Policy "same-origin"
Header set Feature-Policy "geolocation 'self'; vibrate 'none'"
Header always set Expect-CT "max-age=7776000, enforce"
</IfModule>
# XML-RPC DDoS & TRACKBACK/PINGBACK PROTECTION
<FilesMatch "^(xmlrpc\.php|wp-trackback\.php)">
#Require all denied
#Require ip x.x.x.
</FilesMatch>
# Brute-force login prevention (stops direct POSTing to wp-login.php)
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_METHOD} POST
RewriteCond %{REQUEST_URI} wp-login\.php*
RewriteCond %{HTTP_HOST}@@%{HTTP_REFERER} !^([^@]*)@@https?://\1/.*
RewriteRule .* - [F,L]
</IfModule>
# Set expire header for static assets
<IfModule mod_expires.c>
ExpiresActive On
<FilesMatch "\.(css|eot|gif|ico|jpeg|jpg|js|png|svg|swf|ttf|woff)">
ExpiresDefault "access plus 1 year"
</FilesMatch>
</IfModule>
# Block the include-only files.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^wp-admin/includes/ - [F,L]
RewriteRule !^wp-includes/ - [S=3]
RewriteRule ^wp-includes/[^/]+\.php$ - [F,L]
RewriteRule ^wp-includes/js/tinymce/langs/.+\.php - [F,L]
RewriteRule ^wp-includes/theme-compat/ - [F,L]
</IfModule>
# Disallow direct access to the wp-config.php file
<files wp-config.php>
Require all denied
</files>
# Kill PHP Execution
<Files ~ "\.ph(?:p[345]?|t|tml)$">
#deny from all
</Files>
# BEGIN WordPress
# The directives (lines) between "BEGIN WordPress" and "END WordPress" are
# dynamically generated, and should only be modified via WordPress filters.
# Any changes to the directives between these markers will be overwritten.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
Last updated