An ounce of prevention is worth a pound of cure.

Let me share steps you can take to make your WordPress site more secure against hackers.

Please note a number of the steps I will share are listed on Hardening WordPress, Official WordPress Codex article.

In May 2011, I shared with our readers about recommended .htaccess file settings for WordPress hosted on our servers.

Since then, I’ve been testing various changes to increase security as well as potentially enhance the performance of a site using the settings.

I’ve tested what I’m about to share extensively on our site at http://www.dynamicnet.net/ for the past several months.

If you have questions following what I’m sharing, please do ask in the comment area at the bottom of this article; but ultimately you may need to work things out with your hosting provider technical support. If your hosting provider cares about you as a person, as well as your business or hobby, they will help you in this area.

NOTE: If you are using a WordPress designer / developer, please have them review the .htaccess file we recommend to ensure it will not conflict with their work.

See Dynamic Net, Inc’s. recommended .htaccess starter file; feel free to copy it to notepad (do not use WordPad or Word unless you know how to save the file as a pure text file without any formatting or codes), and save that file as .htaccess to upload via FTP to the WordPress home directory (typically your domain directory or a blog directory) of your WordPress site.

Let’s go over our recommended .htaccess starter file:

A the top of every .htaccess file should be a measure to protect the .htaccess file itself from browser-based attacks.

order allow,deny
deny from all

Now, we want to tell the Web server there are certain files that no one should browse directly.

Order Deny,Allow
Deny from all

The above lines tell the Web server not to allow the direct browsing of wp-config.php, wp-cache-config.php, advanced-cache.php, php.ini, php5.ini, config.php, and db-config.ini.

Now, if you have a dedicated IP address, you can include an additional layer of protection:

#
#Order Deny,Allow
#Deny from all
#allow from [ip address 1 without brackets]
#allow from [ip address 2 without brackets and so on]
#

Simply fill in your IP address(es) — remove the second allow from if you only have one IP address — and uncomment all of the lines in order for the wp-login.php, install.php and readme.html file to only allowed to be accessed via the browser from the IP addresses so entered.

Next turn off directory indexes so that if someone browses a directory / folder for which you don’t have a default page set up, they will receive an access denied error message.

IndexIgnore *
Options All -Indexes

Next, if you are using a php error log, tell the Web server to deny direct access to the file

 Order allow,deny
 Deny from all
 Satisfy All

Please change the above file name to the file name you use on your web server. If you are not sure what to use, please contact your web hosting provider technical support department.

Next is a method of blocking common bandwidth abusers and hacking tools:

SetEnvIf user-agent "Indy Library" stayout=1
SetEnvIf user-agent "libwww-perl" stayout=1
SetEnvIf user-agent "Download Demon" stayout=1
SetEnvIf user-agent "GetRight" stayout=1
SetEnvIf user-agent "GetWeb!" stayout=1
SetEnvIf user-agent "Go!Zilla" stayout=1
SetEnvIf user-agent "Go-Ahead-Got-It" stayout=1
SetEnvIf user-agent "GrabNet" stayout=1
SetEnvIf user-agent "TurnitinBot" stayout=1
deny from env=stayout

Now we move onto turning on the Apache rewrite engine with the following:

RewriteEngine On
RewriteBase /

What follows is a series of protections against common types of exploits. What you see here can be applied to sites running Drupal, Joomla, and WordPress as well as other PHP-based sites.

RewriteCond %{QUERY_STRING} proc/self/environ [OR]
RewriteCond %{QUERY_STRING} mosConfig_[a-zA-Z_]{1,21}(=|\%3D) [OR]
RewriteCond %{QUERY_STRING} base64_(en|de)code[^(]*\([^)]*\) [OR]
RewriteCond %{QUERY_STRING} (|%3E) [NC,OR]
RewriteCond %{QUERY_STRING} GLOBALS(=|\[|\%[0-9A-Z]{0,2}) [OR]
RewriteCond %{QUERY_STRING} _REQUEST(=|\[|\%[0-9A-Z]{0,2})
RewriteRule .* index.php [F]

RewriteCond %{REQUEST_METHOD} GET
RewriteCond %{QUERY_STRING} [a-zA-Z0-9_]=http:// [OR]
RewriteCond %{QUERY_STRING} [a-zA-Z0-9_]=(\.\.//?)+ [OR]
RewriteCond %{QUERY_STRING} [a-zA-Z0-9_]=/([a-z0-9_.]//?)+ [NC]
RewriteRule .* - [F]

RewriteCond %{QUERY_STRING} \=PHP[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12} [NC]
RewriteRule .* - [F]

RewriteCond %{QUERY_STRING} (;||'|"|\)|%0A|%0D|%22|%27|%3C|%3E|%00).*(/\*|union|select|insert|cast|set|declare|drop|update|md5|benchmark) [NC]
RewriteRule .* - [F]

Next come the WordPress specific settings for permalinks along with additional protection per the Hardening WordPress codex article.

RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

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]

The rest deal with optimizing the performance of the site itself.

    Header append Vary Accept-Encoding

AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css text/javascript
AddOutputFilterByType DEFLATE application/xml application/xhtml+xml application/rss+xml
AddOutputFilterByType DEFLATE application/javascript application/x-javascript

BrowserMatch ^Mozilla/4 gzip-only-text/html
BrowserMatch ^Mozilla/4\.0[678] no-gzip
BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html

Header append Vary User-Agent env=!dont-vary

FileETag MTime Size

        # Enable expiration control
        ExpiresActive On

        # Default expiration: 1 hour after request
        ExpiresDefault "now plus 1 hour"

        # CSS and JS expiration: 2 week after request
        ExpiresByType text/css "now plus 2 weeks"
        ExpiresByType application/javascript "now plus 2 weeks"
        ExpiresByType application/x-javascript "now plus 2 weeks"

        # Image files expiration: 1 month after request
        ExpiresByType image/bmp "now plus 1 month"
        ExpiresByType image/gif "now plus 1 month"
        ExpiresByType image/jpeg "now plus 1 month"
        ExpiresByType image/jp2 "now plus 1 month"
        ExpiresByType image/pipeg "now plus 1 month"
        ExpiresByType image/png "now plus 1 month"
        ExpiresByType image/svg+xml "now plus 1 month"
        ExpiresByType image/tiff "now plus 1 month"
        ExpiresByType image/vnd.microsoft.icon "now plus 1 month"
        ExpiresByType image/x-icon "now plus 1 month"
        ExpiresByType image/ico "now plus 1 month"
        ExpiresByType image/icon "now plus 1 month"
        ExpiresByType text/ico "now plus 1 month"
        ExpiresByType application/ico "now plus 1 month"
        ExpiresByType image/vnd.wap.wbmp "now plus 1 month"
        ExpiresByType application/vnd.wap.wbxml "now plus 1 month"
        ExpiresByType application/smil "now plus 1 month"

        # Audio files expiration: 1 month after request
        ExpiresByType audio/basic "now plus 1 month"
        ExpiresByType audio/mid "now plus 1 month"
        ExpiresByType audio/midi "now plus 1 month"
        ExpiresByType audio/mpeg "now plus 1 month"
        ExpiresByType audio/x-aiff "now plus 1 month"
        ExpiresByType audio/x-mpegurl "now plus 1 month"
        ExpiresByType audio/x-pn-realaudio "now plus 1 month"
        ExpiresByType audio/x-wav "now plus 1 month"

        # Movie files expiration: 1 month after request
        ExpiresByType application/x-shockwave-flash "now plus 1 month"
        ExpiresByType x-world/x-vrml "now plus 1 month"
        ExpiresByType video/x-msvideo "now plus 1 month"
        ExpiresByType video/mpeg "now plus 1 month"
        ExpiresByType video/mp4 "now plus 1 month"
        ExpiresByType video/quicktime "now plus 1 month"
        ExpiresByType video/x-la-asf "now plus 1 month"
        ExpiresByType video/x-ms-asf "now plus 1 month"

If you are using a stock WordPress .htaccess file that just contains the mod_rewrite statements for permalinks (see settings, Permalinks in your WordPress administration area), then you should be safe to replace the .htaccess file with the .htaccess file what we recommend for security and performance

.

However, if you have a lot of settings already present above and beyond the normal permalinks, you would need to see what can be replaced or otherwise merged in.

Please contact us if you are one of our hosting customers, and we will do this for you freely. If you are not hosting with us, your own hosting provider technical support department should be happy to help you determine what can be replaced / merged or ignored for your particular environment.