If you own a website or WordPress site, you’ve probably come across or heard of .htaccess. Most people know .htaccess as that file that creates pretty permalinks in WordPress. The official definition of .htaccess, is a hypertext access file that is used for web server configuration, in a decentralized management manner and is a directory level configuration file.WordPress by default does not require an .htaccess file, but an .htaccess file is needed to create pretty permalinks and maybe needed for other WordPress related items like plugins and security. This tutorial is designed to help you create a .htaccess file for WordPress.

If you are using permalinks, you will start WordPress with the following:

# BEGIN WordPress 
<IfModule mod_rewrite.c> RewriteEngine On RewriteBase / RewriteRule ^index.php$ - [L] RewriteRule ^login/?$ /wp-login.php [QSA,L] RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule . /index.php [L] </IfModule> # END WordPress

Everything else should go below the above. Each item should be separated by one (1) blank line. I highly recommend that you keep the comments, as it will help you know and remember what each code snippet does.

Hardening WordPress

You should not rely on .htaccess for absolute security (no such thing anyways) of WordPress, but you can use these code snippets to help harden your WordPress installation.

wp-config.php, readme.html and license.txt

Firstly, we should block access to WordPress’s configuration file, wp-config.php, the readme.html which contains WordPress’s version information and license.txt which is not necessary for anyone to access and will hide that the site is powered by WordPress.


<files wp-config.php> Order allow,deny Deny from all </files>


<files readme.html> Order allow,deny Deny from all </files>


<files license.txt> Order allow,deny Deny from all </files>

All-on-One Blocking

<FilesMatch "^(wp-config.php|readme.html|license.txt)"> Order allow,deny Deny from all Satisfy All </FilesMatch>

Securing the wp-includes directory

The files in the wp-includes directory are not intended to be accessed directly, so we can use the following to secure it.

# 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>

This code snippet will unfortunately not work for WordPress Multisite.

Denying Access to the Login Page

If your WordPress installation only has one author and if that author’s Internet access IP address is static or doesn’t change a lot, you can use .htaccess to block access to everyone except for a specific IP or IPs, like so:

# Block access to wp-login.php 
<Files wp-login.php> Order Deny,Allow Deny from all Allow from xx.xx.xx.xx </Files>

Change xx.xx.xx.xx to the IP you want to access the login page. If there is more than one (1) IP address, add Allow from xx.xx.xx.xx per line, per IP address.

Block specific file types

You may want to block specific file types from being accessed, you can do so like this:

# Block access to the following file types, i.e. filename.type 
<FilesMatch "(^#.*#|.(bak|config|dist|txt|html|htm|eot|otf|ttc|ttf|woff|inc|ini|log|psd|sh|sql)|~)$"> Order allow,deny Deny from all Satisfy All </FilesMatch>

This is great to block access to backups, though backups should NOT be in a public accessible directory to begin with, and file types that are used for design, development, documentation and information, i.e. plugin or theme documentation files.

You are free to modify this list, remember each file type should be in the brackets, and have a | to the right of it, except for the last file type.

Denying Directory Listing

A directory without an index file, i.e. index.html or index.php, will list all sub-directories and files within that directory, if accessed. You do not want this, so let’s not show a file listing.

# Disallow directory listing 
<IfModule mod_autoindex.c> Options -Indexes </IfModule>

Deny POST requests from outside websites

I recommend denying form POST requests from domain names that are not yours. Why? Block spam and unauthorized login requests. By not doing this, you allow malicious end users to create a form within (most likely) or not within a script to post (aka POST) information to your WordPress install, that does not originate from your website.

# Block outside domain names from using the POST method 
<IfModule mod_rewrite.c> RewriteEngine On RewriteCond %{REQUEST_METHOD} POST RewriteCond %{REQUEST_URI} .(wp-comments-post|wp-login).php* RewriteCond %{HTTP_REFERER} !.*himpfen.com.* [OR] RewriteCond %{HTTP_USER_AGENT} ^$ RewriteRule (.*) http://%{REMOTE_ADDR}/$ [R=301,L] </ifModule>

Make sure to change the domain name himpfen.com on line 6, to your domain name..

Prevent MIME-Type Confusion

Basically, assets will reject responses with incorrect MIME types. This prevents attacks based on MIME-type confusion.

<IfModule mod_headers.c> Header set X-Content-Type-Options nosniff </IfModule>

Protect Against ClickJacking

Supported web browsers will prevent an attacker/hacker from putting your website’s content into an iframe on another website.

I’ve noticed that links in StumbleUpon where this is enabled will not show, so I would recommend using this for membership sites or web apps.

<IfModule mod_headers.c> 
Header always append X-Frame-Options SAMEORIGIN 

Help Prevent XSS Attacks

Use the following code snippet to prevent again some cross-site scripting (XSS) attacks.

<IfModule mod_headers.c> 
Header set X-XSS-Protection "1; mode=block" 

There is of course more code snippets you can use to harden the web server itself, but the above is great for WordPress itelf and WordPress related items.

Performance Optimization

Use the following .htaccess code snippets to help optimize the performance your WordPress installation.

Leverage Browser Caching

You can set the expiration of the HTTP headers, like so:

# Leverage Browser Caching by setting HTTP header expires 
```<IfModule mod_expires.c> ExpiresActive On ExpiresByType image/jpg "access plus 1 year" ExpiresByType image/jpeg "access plus 1 year" ExpiresByType image/gif "access plus 1 year" ExpiresByType image/png "access plus 1 year" ExpiresByType text/css "access plus 1 month" ExpiresByType application/pdf "access plus 1 month" ExpiresByType text/x-javascript "access plus 1 month" ExpiresByType application/javascript "access plus 1 month" ExpiresByType text/javascript "access plus 1 month" ExpiresByType application/x-shockwave-flash "access plus 1 month" ExpiresByType image/x-icon "access plus 1 year" ExpiresDefault "access plus 2 days" </IfModule>

UTF-8 encoding

Use UTF-8 encoding for files being served as text/html or text/plain.

# Use UTF-8 encoding 
AddDefaultCharset utf-8

You can also force certain file types to use UTF-8 encoding:

# Force the use of UTF-8 encoding 
<IfModule mod_mime.c> AddCharset utf-8 .css .js </IfModule>

You can add more if you wish, by separating each file type with a space and each file type should start with a ..

Specify a Vary: Accept-Encoding header

Publicly cacheable, compressible resources should have a “Vary: Accept-Encoding” header:

# Specify a Vary: Accept-Encoding header 
<IfModule mod_headers.c> <FilesMatch ".(js|css|xml|gz)$"> Header append Vary: Accept-Encoding </FilesMatch> </IfModule>


If you are using a older version of Apache, use GZIP, but if the server is Apache 2, skip this and use DEFLATE.

# Use the GZIP Apache module 
<ifModule mod_gzip.c> mod_gzip_on Yes mod_gzip_dechunk Yes mod_gzip_item_include file .(html?|txt|css|js|php|pl)$ mod_gzip_item_include handler ^cgi-script$ mod_gzip_item_include mime ^text/.* mod_gzip_item_include mime ^application/x-javascript.* mod_gzip_item_exclude mime ^image/.* mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.* </ifModule>


The DEFLATE module replaced the GZIP module for Apache 2.

# Enable DEFALTE 
<IfModule mod_deflate.c> AddOutputFilter DEFLATE js css AddOutputFilterByType DEFLATE text/plain AddOutputFilterByType DEFLATE text/html AddOutputFilterByType DEFLATE text/xml AddOutputFilterByType DEFLATE text/css AddOutputFilterByType DEFLATE text/javascript AddOutputFilterByType DEFLATE application/xml AddOutputFilterByType DEFLATE application/xhtml+xml AddOutputFilterByType DEFLATE application/rss+xml AddOutputFilterByType DEFLATE application/javascript AddOutputFilterByType DEFLATE application/x-javascript BrowserMatch ^Mozilla/4 gzip-only-text/html BrowserMatch ^Mozilla/4.0[678] no-gzip BrowserMatch bMSIE !no-gzip !gzip-only-text/html Header append Vary User-Agent </IfModule>

Allow persistent connections

If you want to allow persistent connections from the same TCP connection, use keep alive. Be careful in using this as there are disadvantages.

# Keep alive for persistent connections 
<IfModule mod_headers.c> 
Header set Connection Keep-Alive 

Get it All

You’re now done. This is a great start for your .htaccess file and you can build upon it by forcing SSL, using cross domain and so on. I wanted this tutorial to be as general as possible, and in some cases, some code snippets require further explanation on usage so I excluded them.

View Source Download (tar.gz)