The Perfect WordPress .htaccess File

Personally, I believe the majority of WordPress users instinctively resort to solving performance, security, and configuring URL-redirect issues by adding bloated, over-engineered, and frequently costly plugins to their WordPress installation. However a great majority of these issues can be resolved simply by utilizing a properly configured .htaccess file. Once configured properly, your site can be protected from unauthorized login attempts, folder/file snooping, XSS, as well as content compression, and various caching directives.

For those who aren’t aware, the .htaccess file is a simple file usually located at the root of an Apache powered website’s public file structure. This file allows users to control numerous properties and other settings on a website such as performance, security, and URL redirects among many others.

I have posted the combined contents of the htacess properties used below in a file available for download at the bottom of this page. With these measures in place, you can take your site from a 35/100 score on Mozilla Observatory to a 105/100:

Original Mozilla Observatory score
New Mozilla Observatory score

The .htaccess file I have provided below will resolve all of these common problems websites are confronted with:


  1. File compression
  2. Expiry headers for various mime types
  3. Customize and control HTTP request/response headers
  4. Disable E-tags (browser validation tokens)


  1. Prevent your site from being used in an iframe
  2. Strictly control which domains can deliver content (“content-security-policy”)
  3. Disable MIME type sniffing
  4. Prevent pages from loading when they detect reflected XSS attacks
  5. Block browsing of WP directories (Options -Indexes)
  6. Block access to files which may contain sensitive information
  7. Prevent brute force attack and other possible attacks on xmlrpc.php
  8. Restrict which visitors are able to access log in the wp-login.php page and the WP admin back end
  9. Enforce HSTS protocol (strict HTTPS- pages will not be rendered unless they are always HTTPS)
  10. Redirect from HTTP to HTTPS


  1. Redirect from old domain to new domain, keeping page URL’s the same


  1. Prevent unwanted PHP files from being downloaded by client

File Compression

The mod_gzip is an Apache module which governs how the gzip data compression scheme is implemented for a site. The settings below instruct the server to use gzip compression on HTML, TXT, CSS, JS, PHP, and PL (Perl) files. Perl files are used in some server configurations. The gzip algorithm allows files to be compressed to about 30% of their original size.

<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/.* ^application/x-javascript.*
  mod_gzip_item_exclude mime ^image/.*
  mod_gzip_item_exclude rspheader ^Content-Encoding:.*gzip.*

The mod_deflate module provides the DEFLATE output filter that allows output from your server to be compressed before being sent to the client’s browser. Note how the single line below can specify eight different MIME types- many users make the mistake of splitting these into one line per MIME type, which is unnecessary.

<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/plain text/html text/xml text/css application/xhtml+xml application/rss+xml text/javascript application/javascript

Expiry Headers

The mod_expires setting tells the client’s browser how long a particular resource can be saved, or cached, before a fresh copy should be downloaded. If a resource or document is cached, it can be fetched from browser memory rather than relying on a website’s server to send a new copy. This can save the client from spending time waiting for a resource to be fetched from your server, while cutting down on unnecessary server traffic.

<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresDefault "access 1 month"

Set Cache Control HTTP Headers

These are directives which govern how long a resource should be cached. The below example specifies that any common file typically delivered to a client’s browser (HTML, CSS, JS) can be cached for a year (2592000 seconds). Note the “public” keyword in use signifies that the resource may be cached by the browser and any other intermediate caches, whereas the “private” keyword means only the browser may store the resource.

<IfModule mod_headers.c>
  <FilesMatch "\\.(css|ico|pdf|flv|jpg|jpeg|png|gif|swf)$">
    Header set Cache-Control "max-age=2592000, public"
  <FilesMatch "\\.(js)$">
    Header set Cache-Control "max-age=2592000, private"
  <FilesMatch "\\.(xml|txt)$">
    Header set Cache-Control "max-age=2592000, public, must-revalidate"
  <FilesMatch "\\.(html|htm|php)$">
    Header set Cache-Control "max-age=2592000, private, must-revalidate"

Disable E-tags

Entity tags (“E-tags”) are a directive to communicate the validity of a validation token. This token would prevent the browser from wasting time requesting a resource that has not changed. Disabling this value is not the most critical part of performance improvement adjustments on .htaccess, but it can make a small difference.

The first line below will unset any previous E-tag value, while the second line removes that behavior.

Header unset ETag
FileETag None


Block IFrame Requests

This can be very valuable if you have no intention of having your website displayed as an iframe on someone else’s site. Enabling this would do a great deal to prevent cross site scripting (XSS) attacks.

Header set X-Frame-Options DENY

Content Security Policy

This line will configure your website to only scripts, images, stylesheets, fonts, and all other resources to only be executed only from the current domain. Anything that requires access to another site like Google fonts, analytics, YouTube videos, and so on will not function. Please note that this policy affects both the front end and back end WP admin area, which severely affects how the admin area works. A proper implementation of this would almost completely prevent the possibility of an XSS vulnerability, while maintaining usability of your website.

Header set Content-Security-Policy "default-src 'self'; script-src 'self'

If you prefer a less draconian solution which allows for blacklisting all external sites by default, while still allowing the commonly used resources mentioned above to work, use this instead:

Header set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; connect-src 'self'; style-src 'self' * 'unsafe-inline'; font-src 'self' data:; img-src 'self' data:; frame-src;"

Disable Mime Sniffing

Disable MIME type sniffing, which can make older browsers like Internet Explorer execute an innocent looking .img URL as a javascript.

Header set X-Content-Type-Options nosniff

Prevent Reflected XSS

Prevents pages from loading when they detect reflected XSS attacks. When the browser recognizes the possibility of a reflected XSS, it will stop the page from loading. This is a feature of Chrome, Safari, and  IE.

Header set X-XSS-Protection "1; mode=block"

Block Directory Browsing

This will prevents users from browsing your site’s file directories

Options -Indexes

Block Access to Sensitive Files

Many sites have old or legacy data stored as publicly accessibly URL’s that users are not aware of. Frequently during WP migrations from one web host to another, the entire site’s content is stored as an SQL file, dumped in the file structure, migrated, and forgotten. However anyone can easily access that SQL can get all the information about a site including usernames, passwords (hashed) and so on. This code snippet will prevent that possibility by blocking all request to .sql files, as well as the others listed. The lower snippet will block all requests from end users to the wp-config.php and xmlrpc.php files, which are both very frequently used to collect database information.

<FilesMatch "(\.(htpasswds|bak|config|ini|log|psd|sh|sql|swp)|~)$">
  order allow,deny
  deny from all
# Block WordPress xmlrpc.php requests
<FilesMatch "(xmlrpc|wp-config)\.php$">
  order allow,deny
  deny from all

Restrict Access to WP Login Based on IP Address

This code below blocks all requests to the WP admin area, unless they are coming from the 12.345.56.789 IP address. So even if a hacker had the ability to collect the username/password data, they would not be able to login because they would be coming from a different IP address. I recommend using this if there aren’t many users who login to a site regularly, otherwise it can be difficult to maintain the allowable IP addresses.

I find this to be the most effective means of securing a WordPress website, however many users may find it inconvenient because it makes it more difficult for many users to login. If there are only a few regular users of a site, I recommend this policy, but it can obviously be very difficult to manage if there are dozens of users, each of whom can change their IP frequently.

<Files wp-login.php>
  order deny,allow
  deny from all
  allow from 12.345.56.789

Strictly Enforce HSTS (Mandatory HTTPS)

Over the past few years, it has become increasingly important to use HTTPS for security, and now search rankings. However even if a site is properly set up for HTTPS, if one article posts an image from a non-HTTPS source, that compromises the HTTPS integrity of that page, making it effectively HTTP. The code snippet below will prevent any page from being rendered that is not fully HTTPS. This policy is known as HSTS, or “HTTP Strict Transport Security”. Another thing that is really cool about implementing this, is that you will be able to register your site on to have your site listed as HSTS compatible, and your site will load a bit faster because the browser knows it must use the HTTPS protocol, and not HTTP.

Header set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" env=HTTPS

Redirect From HTTP to HTTPS

This is always a commonly requested topic, so I thought I would post a simple way of doing this below.

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

As promised, the collection of the code snippets used above in a single download: .htaccess.txt. (It is not the exact one in use on this site)