Ultimate Guide to Managing Your WordPress .htaccess File

One of the nice, but occasionally annoying, things about WordPress is that it modifies your .htaccess file automatically when you change certain settings or activate some plugins. In general, for people with a default setup, this is a good thing. It saves you work.

The downside is that it can have unintended effects that will break your site in some way.

Ultimate Guide? Seriously?

I know. I cringe too when something is billed as the ultimate guide. However, I did try to make this as comprehensive as possible with every method, good and questionable, that I know of. There are a couple of particularly ill-advised methods mentioned in the conclusion, but they don’t get discussed in detail because they aren’t even questionable. They’re just bad.

If you know of some additional method that might actually make sense to use, I’d love to hear about it.

What is the .htaccess file?

Simply put, an .htaccess file is an Apache server configuration file that lets you control many aspects of your server. You can block access to some directories, set other permissions and, most importantly for our purposes, rewrite URLs so they look nice and convey information.

The ability to rewrite URLs is what allows WordPress to have permalinks like https://example.com/my-great-post rather than https://example.com/index.php?p=123.

So what’s the problem?

WordPress handles this URL rewriting for you. You go in, you change your permalink settings and WordPress handles the rest. That includes modifying the code in your .htaccess file if necessary.

The problem arises when you have custom rewrites, such as 301 redirects because a URL has changed or a high-traffic referrer has the URL wrong. If you put your RewriteRule in the wrong place, WordPress might delete it. And even if you do everything right, a plugin might decide to mess with your file and, especially if there’s something non-standard in your setup, things could break.

This isn’t just a theoretical worry. Even the official WordPress page about the .htaccess file gives the following explanation for maintaining such a page:

This page may be used to restore a corrupted .htaccess file (e.g. a misbehaving plugin).

So what do you do?

There are many ways to handle this:

  1. Use server-level or virtual-host-level configuration files
  2. Set your directives outside of the WordPress section
  3. Use a filter to tell WordPress not to modify your .htaccess
  4. Write protect your .htaccess
  5. The fake Apache module technique
  6. Use nginx (or something else)
  7. Bonus: Add your rewrites by setting the non_wp_rules property of the global $wp_rewrite object

Which method is best?

That’s a loaded question, so here’s bit of a quick guide.

  • If you want the best server performance and you do not want WordPress or plugins to make changes automatically, use method 1 (server-level config files).
  • If you want the simplest method, use method 2 (set directives in the .htaccess file, but outside the WordPress block). This is what the WordPress and plugin developers expect you to do.
  • If you are having issues and need to prevent WordPress from making changes, method 1 is good if you have full server access. Method 3 (use a filter to tell WP to not change your file) is probably the most straight-forward for most users, but plugins may still change your settings.
  • Method 4 (set .htaccess to read-only) would suit the case where you want to be absolutely sure that no plugin can modify your .htaccess.
  • Method 5 (fake Apache module method) is best if you want to allow WordPress to make changes so you can see what they are, but you do not want them to take effect. I’ve never used this method, but it’s clever for that particular case.
  • Method 6 (use nginx) is if you want to try something other than Apache and maybe speed up your system.
  • Finally, the bonus method (hook into WP’s rewrite methods) is for the very particular case where you want one address within your WordPress installation to look like a different address within your WordPress installation. Practically speaking, there is very little use for this method.

Hopefully that gives a bit of guidance and now you can read up on the method(s) that seem most likely to fit your use case.

Options for managing your .htaccess file

1. Move your Apache directives to the virtual host file

If you have access to server-level configuration or virtual-host-level configuration, this may be the way to go. It gives a performance boost and is rock solid, but you have to apply any changes manually and you won’t have this level of access on a shared-hosting account.

.htaccess is not the only option

There are a number of places and contexts in which you can apply Apache configuration rules. They can be set to apply server-wide, to some files, directories, virtual hosts, and according to other criteria (full list of Configuration Section Containers here). From a practical point of view, you’re going to apply configuration settings in three different places:

  • .htaccess files
  • your main httpd.conf server configuration file
  • your virtual host configuration files

The performance problem

The .htaccess files offer the most convenience and the least performance. They are read and processed at runtime when a request comes in.

Simply enabling them, whether you use them or not, incurs a performance hit on your server. If you tell your server that .htaccess files are allowed, it means that for every request you make, it has to check to see if there’s an .htaccess file anywhere along the path. In other words, it has to check every directory along the path and if it finds a configuration file, it has to read and process it.

So let’s say you have a request for https://example.com/wp-content/uploads/2021/03/image.jpg. The server has to do a file lookup in the root directory, wp-content, uploads, 2021 and 03. That’s five file lookups and at least one .htaccess file to process just to serve up that one image.

If you set your configuration in the main server configuration file or a separate virtual host configuration file, those files are read and evaluated only when Apache reboots. So in addition to the fact that the server does not need to look through the entire file path, it also doesn’t need to read any files at runtime to process a request.

Disabling .htaccess files

On Ubuntu 18.04 (and many other systems), for a domain named example.com, the config file will be in /etc/apache2/sites-available/example.com.conf.

In the <Directory> section for your virtual host setup for the site in question, you would set

AllowOverride None
AllowOverrideList None

Many places say that it is sufficient to set just AllowOverride None, but the Apache documentation for AllowOverride is clear (emphasis mine):

When this directive is set to None and AllowOverrideList is set to None, .htaccess files are completely ignored. In this case, the server will not even attempt to read .htaccess files in the filesystem.

So it means that you get a performance boost and WordPress can’t mess up your server configuration. It also means that in any situations where WordPress would automatically configure things for you, you have to do it manually in the virtual host directory section.

Then you just add the standard WordPress directives and your custom directives and you’re set. When it’s all done, you end up with something like this and the .htaccess file is taken totally out of the equation:

<VirtualHost 123.234.234.234:443>

  ServerName example.com
  DocumentRoot /path/to/account/root/public_html

  <Directory /path/to/account/root/public_html>

    AllowOverride None
    AllowOverrideList None

    RewriteEngine On

    # CUSTOM rewrites - 301 redirects for pages moved to Idea Wrights
    RewriteRule ^oldpage/? https://example.com/new-page [R=301,L]
    RewriteRule ^oldpage2/? https://example.com/new-page2 [R=301,L]
        
    # BEGIN WordPress
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
    RewriteBase /
    RewriteRule ^index\.php$ - [L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule . /index.php [L]
    # END WordPress
  </Directory>
</VirtualHost>

Note that you may also have other .htaccess files for your website that get placed there for security reasons or otherwise by plugins. You’ll want to look through these and make sure to port any important rules over to your virtual hosts configuration file. To find them all, just go to your site document root (e.g. public_html) and type the following:

find . -type f -name .htaccess

This will recursively search through the entire file tree for your site and find any .htaccess files. Inspect them and if they have rules you want to keep, add a new <Directory> section to your virtual host config file and include those rules.

Pros

  • Stable and solid. WordPress can’t mess this up.
  • Improved server performance
  • Set it and forget it
  • WordPress and plugins can and will still modify your .htaccess file, so if you need to make changes, you’ll be able to see them and just copy them over as needed.

Cons

  • Requires full server access
  • Requires manual setup and maintenance
  • You need to manually port this from dev to staging to production with the possibility that you introduce errors unless you are using something like Docker or Vagrant to just push the entire environment from dev to staging to production.
  • May be confusing for admins who take over the site who won’t understand why their changes to the .htaccess file are having no effect.

2. Set your directives outside of the WordPress section

WordPress should leave everything alone in your .htaccess file except what is between the

# BEGIN WordPress
# END WordPress 

comments in the .htaccess. By default (at least in WordPress 5.6.1) WordPress will create the following .htaccess file.

# 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

You can simply add your directives above or below that code. Let’s say I want to enable better browser caching, so I want to say that some types of files can be cached for up to two weeks (1209600 seconds) since last access (A):

<IfModule mod_expires.c>
  <FilesMatch \.(jpg|png|gif|jpeg)$>
    ExpiresActive On
    ExpiresDefault A1209600
  </FilesMatch>
</IfModule>

# BEGIN WordPress

  ...

# END WordPress

Now, you might ask, what if I want to add another RewriteRule? Don’t I have to put that after RewriteEngine On and therefore inside the WordPress block?

Actually, no.

  1. You can have multiple RewriteEngine On declarations. Apache doesn’t care.
  2. Even if you do NOT add a second declaration and you depend on WordPress to turn on the RewriteEngine, you can actually have rules that come before the RewriteEngine On declaration and they will get executed in the order given.

That second statement is surprising, but you can test it for yourself. On a local server, spin up a virtual host and then add three files: index.html, index1.html and index2.html. The create a .htaccess file with the following:

RewriteRule ^index.html index1.html [R=301,L]
RewriteEngine on
RewriteRule ^index.html index2.html [R=301,L]

You might think that either the first rule would be ignored or that the second RewriteRule, the one after turning on the RewriteEngine, would execute first and then it would loop back and execute the first rule, but, having matched on the second rule, would never see it.

But that’s not what happens. When you turn on the RewriteEngine, it the feeds the set of rules to mod_rewrite (i.e. the RewriteEngine) and executes them in order. Note that the overall contents of a .htaccess file do not necessarily execute in order. That depends on when the Apache module responsible for each section gets loaded, but the RewriteRules and RewriteCond will execute in order, regardless of where the RewriteEngine On directive is placed.

All that to say that this works fine:

# Custom 301 redirects
RewriteRule ^old-page https://example.com/new-page [R=301,L]
RewriteRule ^old-url /new-url [R=301,L]

# Set Expires headers for images to two weeks after last access
<IfModule mod_expires.c>
  <FilesMatch \.(jpg|png|gif|jpeg|svg)$>
    ExpiresActive On
    ExpiresDefault A1209600
  </FilesMatch>
</IfModule>

# 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

That strikes me as bizarre though, so I add a second optional RewriteEngine On declaration with my custom rules.

Pros:

  • Good, solid, tried and true method that does it the way WordPress expects you to do it
  • Requires no special server access or manual configuration (beyond your custom directives, of course)
  • Allows WordPress to modify the .htaccess when it wants to, which is probably what you want

Cons:

  • Allows WordPress to modify the .htaccess file when it wants to, which may be exactly what you’re trying to avoid
  • Plugins might also modify code, but they should typically do it either just before or just after the WordPress auto-generated code.
  • Like every method that depends on a .htaccess file, it incurs some server overhead. I won’t repeat that comment, but it is true for all other methods and is true in a default WordPress installation

3. Use a filter to tell WordPress not to modify your .htaccess

This is an easy one and you can do this on any system. In your functions.php file, simply add the following line:

add_filter('flush_rewrite_rules_hard','__return_false');

The flush_rewrite_rules_hard hook allows you to control what happens when WordPress or a plugin invokes the flush_rules() method of the WP_Rewrite class. I’ve used this and it seems solid as long as all plugins are properly coded and use the native WordPress functions for any .htaccess modifications they make.

Pros:

  • Simple. Just one line of code in your theme’s functions.php file or your site-specific plugin
  • Everyone can use this. No special server access needed
  • Prevents WordPress from modifying file, which is presumably what you are trying to achieve

Cons:

  • May get overwritten by plugins in some circumstances
  • If placed in functions.php (rather than a site-specific plugin), it stops working if you change themes
  • Prevents WordPress from modifying the file, so any required changes need to be applied manually

4. Write protect your .htaccess

This is really simple. Change the permissions on the .htaccess file from the standard 644 (rw-r–r–) to 444 (r–r–r–). Done! WordPress can’t modify the file and neither can you until you change the permissions. Even better, if you have sufficient access, change the owner on that file to any valid user as long as it is not the owner of the directory the file is in or the PHP process, then nobody can mess with that. Just make sure you have access to the other user account or root so you can change it back.

To do this, just type the following from the command line in your root WordPress directory where the .htaccess file resides (minus the > prompt of course):

> chmod 444 .htaccess
> chown otheruser:otheruser .htaccess

Pros:

  • Simple and robust
  • You can allow WP to write to the file if needed in order to update it, and then set it back to read only, so you can still take advantage of automatic configuration done by WordPress or plugins. Of course, then you’re back where you started and you have to check that WP does not blow away your custom directives when it updates the file.

Cons:

  • You block WordPress’ automatic configuration, so you might have to do some manual configuration.

5. The fake Apache module technique

This is brilliant and simple and stolen 100% from Jeff Star’s clever post on the Perishable Press website. Jeff has a nice explanation, so there’s no need to repeat it here, but in brief it works like this:

  • Apache lets you wrap directives in <IfModule> sections and if it does not find the module you stipulate, it ignores those directives.
  • Jeff puts the auto-generated WP code (from the # BEGIN WordPress to # END WordPress comments) inside an <IfModule> section keyed to a module that does not exist, so it never executes.
  • When WordPress messes with your custom directives and rules in your .htaccess, it only touches the part that never executes anyway, so no harm done.
  • You then put your custom rules and directives below that and it should be immune to changes from WordPress.

Jeff’s final code looks like this, with a fake mod_ignore_wordpress.c module, which doesn’t exist so the <IfModule> code will never execute.

# PREVENT WP AUTO MODS
# @ https://m0n.co/08
<IfModule mod_ignore_wordpress.c>

# 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

</IfModule>

# CUSTOM WordPress
<IfModule mod_rewrite.c>
	RewriteBase /
	RewriteRule ^index\.php$ - [L]
	RewriteCond %{REQUEST_FILENAME} !-f
	RewriteCond %{REQUEST_FILENAME} !-d
	# Whatever custom rules, etc.
	RewriteRule . /index.php [L]
</IfModule>

For a full explanation and all the details of why this works, look at Jeff’s post from August 2020.

Pros:

  • Works for all users in any server setup
  • Allows WP to make automatic updates so you can see what they are and then copy them to your custom rules if needed.
  • Constrains WP automatic changes to a section of the file that gets ignored.

Cons:

  • Still lets WP alter the file, so there’s still some risk.
  • Plugins won’t necessarily make their changes within the fake module section, so there is still some risk

6. Use nginx

That’s a bit tongue-in-cheek, but Apache is not the only game in town. If you don’t use Apache, then WordPress can alter the Apache configuration files to its heart’s content. Since nginx does not use .htaccess files, altering those files will have no effect for good or ill. The flip side, of course, is that you will have to do that configuration manually. As an added bonus, nginx is faster than Apache for static resources (but about the same for dynamic pages) and can be tuned for improved WordPress performance.

You have to have to either have control of your server or use a host that offers it, such as Kinsta (I have never used Kinsta personally and have no knowledge of their hosting; I just know them because their blog articles are almost always thorough and excellent).

In my case, I usually run a classic Apache setup simply because most of my clients are on shared hosting with CPanel or Plesk and those are usually set up to run Apache.

Pros

  • Solid.
  • Possibly better website performance than with Apache

Cons

  • Not always an option
  • Must configure the server manually

Bonus: Add your rewrites by setting the non_wp_rules property of the global $wp_rewrite object

This is a cool option, but it only works in one very limited circumstance:

  1. You want to rewrite a URL, not redirect it, AND
  2. You are rewriting to another location within your WordPress install.

If you want to protect certain types of files, set Expires headers, do a 301 redirect or anything like that, this will not work.

You might conceivably want this if you wanted shorter file paths for assets so instead of https://example.com/wp-content/themes/my-theme/assets/images/icons/icon.svg you could have https://example.com/icons/icon.svg. Since this is rarely why people are adding custom rules and anyone who is doing this probably knows her way around a server already, rather than discuss it in detail, I will just link to the documentation:

  • Documentation for WP_Rewrite (search on “non_wp_rules”).
  • See also WP_Rewrite::mod_rewrite_rules() for the actual code that shows how the members of the non_wp_rules array will get rendered as rewrite rules.

Pros:

  • A solution fully within your WP code and if you’re tracking your theme in version control, this gets tracked too. If the function offered a parameter to set the R=301 flag in the rewrite rule, this would be really handy.

Cons:

  • There are very few situations where you are modifying the .htaccess where you would use this.

Conclusion

As with many things in WordPress and in server administration, there are tons of options. There are even a handful I’ve left out. You could, for example, commit your .htaccess file to git and then if WordPress overwrites, just check out the old version before you commit the new version. That’s a bad way to do things (generally environment-specific files should not be under version control), but it’s possible. You could set up a cron job that copies your .htaccess file from backup every 5 minutes so if WordPress auto-updates and breaks something, it will be fixed in 5 minutes. There are probably a dozen equally ill-advised things you could do, but let’s not go there. Ultimate has its limits.

As I said at the outset, for me, I usually either use server-level config files, let WordPress do its thing and avoid putting anything custom in the WordPress section, or using the filter method to stop WordPress from changing things I don’t want changed.

Leave a Comment