WordPress .htaccess file
The WordPress core uses .htaccess for two things: Permalinks and Multisite. This means that .htaccess is only required if you have enabled either of these features. Otherwise, .htaccess is entirely optional for default WordPress installations.
Beyond the WP core, many plugins also use the .htaccess file for custom directives involving rewrites, redirects, custom headers, file compression, and much more. In many cases, such plugins add their .htaccess rules to your .htaccess file automatically, behind the scenes.
So even if you haven’t enabled Permalinks or Multisite, your site may be using .htaccess rules added by WordPress plugins for various types of functionality. That’s one of the cool things about .htaccess: it can be configured and customized to improve your site’s performance, security, and usability. To help you get started, this tutorial provides a collection of .htaccess techniques that are useful for any WordPress-powered site. Combined into a blank .htaccess file, these techniques serve as a great starting point for creating your own custom .htaccess file for WordPress.
Menu
- Before diving in..
- Disable directory views
- Set default encoding
- Add Vary header
- Add security headers
- Protect sensitive files
- Leverage browser caching
- Enable file compression
- Block external POST requests
- Enable WordPress permalinks
- Download ’em all
- Going further
Before diving in..
Before making any changes to your site’s .htaccess file(s), remember to make a backup copy. That way you can quickly restore original functionality should anything unexpected happen. These rules are all well-tested and commonly used, but in this line of work anything can happen, so it’s best to be prepared with good working backups just in case.
Disable directory views
With this technique, we increase security by disabling directory views, which also are known as “directory indexes” or “directory listings”. Many web hosts disable directory views by default, but it’s important to know for sure. If your files are visible, here is an effective way to lock things down.
# Disable directory views
Options -Indexes
<IfModule dir_module>
DirectoryIndex disabled
DirectoryIndex index.php
</IfModule>
Here we disable Indexes
via the Options
directive. Then we specify the DirectoryIndex
just in case. So technically it’s fine to just do this:
# Disable directory views
Options -Indexes
I include the additional dir_module
rules because they are related, and some people may find them useful. To learn more about customizing directory views, check out my tutorial, Better Default Directory Views with HTAccess.
Set default encoding
Setting your site’s default encoding as UTF-8
helps browsers and devices correctly interpret your page content.
# Set default encoding
AddDefaultCharset UTF-8
This directive helps to improve the accuracy and consistency of your web pages. It’s also possible to set the character encoding for specific file types. For example, here we are setting UTF-8
encoding for all CSS and JavaScript files:
# Set encoding for CSS & JS
<IfModule mod_mime.c>
AddCharset utf-8 .html .css .js
</IfModule>
For more possibilities, check out the “Add mod_mime suport” section of my tutorial, Useful .htaccess rules for all websites.
Add Vary header
According to Google and others, publicly cacheable, compressible resources should include the following header:
Vary: Accept-Encoding
This is to prevent certain public proxy services from delivering compressed versions of your files to clients that do not support compression. Sending a Vary header for compressed resources helps resolve this issue by instructing the proxy to cache both compressed and uncompressed versions. To implement, add this code to your site’s root .htaccess file:
# Add Vary header
<IfModule mod_headers.c>
<FilesMatch "\.(css|js|gz|xml)$">
Header append Vary: Accept-Encoding
</FilesMatch>
</IfModule>
This code sends the necessary Vary header for CSS, JavaScript, gzip, and XML files. If your site serves other compressed resources, you can add their respective file types to the list, like so:
\.(css|js|gz|xml|zip)$
Here we have added the ZIP file format, zip
.
Add security headers
There are lots of useful headers that can be added to any website. For example, here are three headers that provide some great security benefits:
- Protect against XSS attacks
- Protect against page-framing and click-jacking
- Protect against content-sniffing
Amazingly, you can add these security benefits with a simple slice of .htaccess:
# Add Security Headers
<IfModule mod_headers.c>
Header set X-XSS-Protection "1; mode=block"
Header always append X-Frame-Options SAMEORIGIN
Header set X-Content-Type-Options nosniff
</IfModule>
No edits need made for this code. Simply add to root .htaccess and enjoy the extra protection. You can learn more about this technique by visiting my tutorial, Increase Security with X-Security Headers.
Protect sensitive files
By default, all WordPress installs include the following files:
wp-config.php
– WP configuration filelicense.txt
– WP license informationreadme.html
– WP install & version information
Of these, wp-config.php
should by default NOT be publicly accessible. The other two files, license.txt
and readme.html
, are by default publicly accessible. All three of these files contain information about your WordPress-powered site. By blocking public access to these files, you prevent bad actors from obtaining and using the information when crafting their attacks. Here is the code to make it happen:
# Protect sensitive files
<FilesMatch "^(wp-config.php|license.txt|readme.html)">
Order Allow,Deny
Deny from all
</FilesMatch>
This simple technique uses FilesMatch
to target each of the files that should be denied access. You can protect any other loose or sensitive files by adding them to the FilesMatch
directive.
Leverage browser caching
Browser caching plays an important role with all websites, especially WordPress. This technique ensures that your site is taking advantage of browser caching by setting optimal ExpiresByType
HTTP headers.
# Leverage browser caching
<IfModule mod_expires.c>
ExpiresActive on
ExpiresDefault "access plus 1 month"
#
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 text/javascript "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
ExpiresByType text/x-javascript "access plus 1 month"
#
ExpiresByType image/x-icon "access plus 1 year"
</IfModule>
This code helps to optimize performance by setting expires headers for the most common file types. You can adjust the cache times to suit your needs, and add more file types as required. For more information, check out Google’s article, Leverage Browser Caching.
Enable file compression
File compression reduces the amount of data that is sent to the client, so it is a great way to improve performance. Fortunately, Apache enables compression via its Deflate module, which can be implemented with the following slab of .htaccess:
# Enable file compression
<IfModule mod_deflate.c>
AddOutputFilter DEFLATE css js
AddOutputFilterByType DEFLATE image/svg+xml image/x-icon
AddOutputFilterByType DEFLATE application/vnd.ms-fontobject application/x-font-ttf font/opentype
AddOutputFilterByType DEFLATE application/javascript application/x-javascript text/javascript text/x-js
AddOutputFilterByType DEFLATE text/html text/plain text/richtext text/css application/json text/xsd text/xsl
AddOutputFilterByType DEFLATE text/xml text/x-component application/xml application/xhtml+xml application/rss+xml application/atom+xml
</IfModule>
This code first enables compression for all CSS and JavaScript files. It then enables compression for many other types of files. Feel free to customize the file formats that should be compressed. For more information, check out .htaccess enable compression.
Block external POST requests
POST requests are required when submitting certain forms. For example, the comment form, login form, and most contact forms all submit their data via the POST request method. Normally, POST requests are initiated by users who are visiting your site. But it’s also possible for bad actors to submit malicious POST requests from another domain or source, in an attempt to hack your stuff. To prevent this, we can deny access to all POST requests that are not initiated from your domain. This can be accomplished with the following slice of .htaccess:
# Block external POST
<IfModule mod_rewrite.c>
RewriteCond %{REQUEST_METHOD} POST
RewriteCond %{REQUEST_URI} (wp-comments-post|wp-login)\.php [NC]
RewriteCond %{HTTP_REFERER} !(.*)example.com [NC,OR]
RewriteCond %{HTTP_USER_AGENT} ^$
RewriteRule .* - [L]
</IfModule>
Remember to change example.com
to match your own domain. Once implemented correctly, this code does the following:
- Checks if the request is POST
- Checks if the request is for the comments form or the login form
- Checks if the request is not from your domain, or if the user agent is empty
- If these conditions are true, the request is denied via 403 “Forbidden” response
In doing this, the above technique helps to keep your site secure by blocking any POST requests that do not originate from your domain. This helps to reduce spam, brute-force login attacks, and lots of other malicious mischief.
Note that you can protect other POST forms as well. For example, if you have a contact form located at http://example.com/contact/
, you can add it by modifying the REQUEST_URI
directive like so:
RewriteCond %{REQUEST_URI} (wp-comments-post\.php|wp-login\.php|/contact/) [NC]
Remember to test well before going live with any changes. More info: Block Spam by Denying Access to No-Referrer Requests.
Enable WordPress permalinks
Last but not least, if you have enabled Permalinks for your site, you must include the required .htaccess directives. There are two possibilities here: WordPress installed in the site’s root directory, or WordPress installed in a sub-directory. Depending on where WordPress is installed, you will want to include one of the following sets of directives.
WordPress in site root directory
Here is the code to use if WordPress is installed in the site’s root directory:
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
No modifications are required for this code.
WordPress in a subdirectory
Alternately, here is the code to use if WordPress is installed in it’s own (sub) directory (e.g., a subdirectory named /wordpress/
):
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /wordpress/
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /wordpress/index.php [L]
</IfModule>
# END WordPress
For this code, you will need to edit both instances of wordpress
with the path/directory in which WordPress is installed. For example, here at Perishable Press, WordPress is installed in a directory named /wp/
, so I replaced wordpress
with wp
.
For more information about either of these techniques, check out my tutorial, The htaccess Rules for all WordPress Permalinks.
Bonus tip!
As a bonus, you can add the following directive (just after the first RewriteRule
) to automatically redirect all login
requests to the actual WP Login Page:
RewriteRule ^login/?$ /wp-login.php [QSA,L]
For more information on this trick, check out Chris Coyier’s DigWP post, Simpler Login URL. This is a useful technique for client sites :)
Download ’em all
The techniques presented in this article are not dependent on each other. So you can use them separately, as desired; or you can combine them all into a single .htaccess file to create a solid .htaccess file for WordPress. To make things easier, here is a text file that contains all of the techniques from this tutorial:
Going further
Intentionally, the techniques provided in this article are kept as general and as widely applicable as possible. There are thousands more useful techniques that could be added, but keeping it simple with only the essentials is optimal when first getting started. I encourage you to go further with your .htaccess file. Here are some recommended resources that are worth exploring.
- 6G Firewall
- Archive of .htaccess techniques
- Stop WordPress from modifying .htaccess
- Create .htaccess files on OS X and Windows
- Useful .htaccess rules for all websites
- .htaccess made easy
Questions and comments always welcome :)
7 responses to “WordPress .htaccess file”
Hi Jeff
If i want to add your following code to more pages that contains form to more than just one page (for example contact + advertise + jobs what would the line should look like ?
RewriteCond %{REQUEST_URI} (wp-comments-post\.php|wp-login\.php|/contact/) [NC]
Thanks
If the patterns match the actual URLs, then yes that looks correct. Always make sure to test thoroughly before going live with any changes.
That’s correct because that your line i took from this post as an example, what i’d like to know is, if i want to add more pages (i have more than one page with forms), do i need to write it this way (one line per page) :
RewriteCond %{REQUEST_URI} (wp-comments-post\.php|wp-login\.php|/contact/) [NC]
RewriteCond %{REQUEST_URI} (wp-comments-post\.php|wp-login\.php|/advertise/) [NC]
RewriteCond %{REQUEST_URI} (wp-comments-post\.php|wp-login\.php|/jobs/) [NC]
or do i need to write it on one line, if yes, what would be the structure ? Something like that ?
RewriteCond %{REQUEST_URI} (wp-comments-post\.php|wp-login\.php|/contact/|/advertise/|/jobs/) [NC]
Thank you
Either each on its own line, or combined into one line is fine, totally up to you. Please note that your first example is redundant, with each resource effectively listed three times. Your second example is correct. Also, if you want to post more code, please follow the rules and wrap each line with code tags. Thanks!
Thank you Jeff
My latest message was delete, that’s weird.
I was asking you if – as we force comrpession in .htaccess with deflate – we must delete de gzip compression lines we might have added in wp-config previously and also in any plugin that would offer it or even force it by default ?
Thanks
Hi Mike, sorry about that — I’ve been working with the database and had to restore from backup. Some comments were lost. Not sure about your question, but in general I would use either deflate OR gzip module, not both at the same time. I hope this helps.