What Does the .htaccess File Do for WordPress?
In Apache-based hosting environments, .htaccess is a configuration file used to change server behaviour on a per-directory basis. In WordPress installations, this file's most critical job is to hand incoming requests over to WordPress's routing logic through index.php. That is what lets WordPress match a readable address such as /2026/07/post-title/ with the right content. The section that provides this function sits inside the file between # BEGIN WordPress and # END WordPress.
What Is the BEGIN/END WordPress Block?
The section between these two markers is not a hand-written suggestion; it is real code generated by the WordPress core itself. Every time you save the Settings → Permalinks page in the admin panel, WordPress regenerates this block automatically, provided the file is writable. In a standard, single WordPress installation the block looks like this:
If this block has been broken by a plugin, accidentally deleted, or lost during a server migration, pasting the text above straight into the .htaccess file gives the same result, without having to log into wp-admin and re-save the Permalinks page.
Why Do Pretty Permalinks Require mod_rewrite?
WordPress's default link structure uses a query string such as ?p=123; those addresses are processed directly by PHP and need no rewrite rules at all. But when a readable structure such as "Post name" is chosen in the Permalinks settings, the server has to forward an address such as /2026/07/post-title/ straight to index.php without looking for it as a physical file or directory. The Apache module that makes this possible is mod_rewrite; the RewriteCond and RewriteRule lines in the BEGIN/END WordPress block do exactly that job: if the requested address does not correspond to a real file or directory (!-f, !-d), it hands the request over to index.php and WordPress resolves the rest internally.
Example: How a Request Reaches index.php
To make what the block above does concrete, let's follow a sample request. When a visitor opens the address example.com/2026/07/post-title/, there is neither a physical file nor a directory by that name on the server. The lines RewriteCond %{REQUEST_FILENAME} !-f and RewriteCond %{REQUEST_FILENAME} !-d check exactly that: if the requested path does not correspond to a real file (!-f) or directory (!-d), the RewriteRule . /index.php [L] line just below kicks in and forwards the request to index.php. From that point on, WordPress's own query parser reads the date and post name from the URL and pulls the right content from the database. If the requested file really does exist on the server — a static file such as /wp-content/uploads/image.jpg, for example — those conditions are not met, so the request goes straight to that file and WordPress is never involved.
Multisite: Subdirectory or Subdomain?
In WordPress Multisite installations, the .htaccess rules differ according to how the sub-sites in the network are addressed. There are two common methods: subdirectory (example.com/site2/) and subdomain (site2.example.com).
| Feature | Subdirectory | Subdomain |
|---|---|---|
| Example site address | example.com/site2/ | site2.example.com |
| How sub-sites are distinguished | By extra capture groups in the .htaccess rules | At the DNS/vhost level, before .htaccess |
| Rule complexity | Contains extra <code>([_0-9a-zA-Z-]+/)?</code> groups and a separate trailing-slash rule to work out which sub-site a request belongs to | Simpler, because the separation is already handled by DNS; similar to the subdirectory rules but without the extra groups and the trailing-slash rule |
In subdirectory installations the server has to tell from the URL itself whether an incoming request belongs to the main site or to a sub-site. That is why extra capture groups such as ([_0-9a-zA-Z-]+/)? are added to the rules; this way a request such as /site2/wp-admin/ is correctly routed to the wp-admin/ directory. This block also contains a separate RewriteRule that automatically adds the trailing slash to /wp-admin requests. In subdomain installations, since that separation is already handled at the DNS/vhost level, the .htaccess rules do not need this extra complexity.
Optional Hardening: Restricting Access and Forcing HTTPS
Outside the BEGIN/END WordPress block there are two more commonly recommended blocks that are not part of the WordPress core.
- Blocking access to wp-config.php and .htaccess: the
<Files wp-config.php>and<Files .htaccess>blocks use theDeny from alldirective to prevent these two files from being requested directly in a browser. Keeping wp-config.php, which holds the database credentials, and .htaccess, which holds the rewrite rules, from being viewed externally is a common and recommended hardening step. - Forcing HTTPS: an additional block that uses the
RewriteCond %{HTTPS} offcheck to move requests arriving over HTTP to HTTPS with a 301 redirect. This is not part of the core either; it is a common extra rule for sites that have a certificate.
Common Mistakes
- Adding custom rules inside the
# BEGIN WordPressand# END WordPressmarkers: every time the Permalinks page is saved, WordPress rewrites this block completely and the custom lines inserted into it are deleted. Custom rules should be added before or after the block. - Expecting pretty permalinks to work without noticing that the
mod_rewritemodule is not enabled on the server; if the module is not enabled, requests never reachindex.phpand you get 404 errors. - Not updating the
RewriteBasevalue after moving WordPress into a subdirectory; the rewrite then keeps pointing at the wrong directory. - Restoring an old .htaccess file that is incompatible with the target installation as it is, after a server migration or a restore from backup, and then meeting 404 errors on every internal page.
Rather than writing the correct block by hand, selecting your installation type and pasting the generated text straight into your .htaccess file both reduces the risk of error and offers a fast recovery route in situations where you cannot log into wp-admin (for instance when a site becomes inaccessible after a migration).