< Apache | Caching | Infrastructure | Web Server | WordPress />

The .htaccess file: the complete guide, from the first rule to the hidden cost

Eroan Boyer

September 4, 2026

22 minutes

Hand opening the door of a server rack with blue cables and indicator lights in a data center

A simple text file hidden at the root of your site can redirect URLs, compress resources, set cache headers, or lock a directory. The .htaccess file is the most manipulated configuration tool on the web, and probably the most misunderstood: blocks found online are pasted in, without always knowing what they do or what they cost.

The guides that dominate search results treat it like a toolbox: rules to copy, page after page. None mention a fact that is clearly documented by the Apache documentation: the .htaccess mechanism itself has a performance cost, paid on each request, on every page. Mastering this file therefore means knowing how to write it as much as knowing when to do without it.

This guide covers everything from the beginning, from creating the file to rules for compression, caching, redirection, and security, with each directive explained line by line. It also answers the question that no one asks and that should nevertheless come first: what does Apache actually do with this file on each visit, and how much does it cost you?

What is an .htaccess file?

An .htaccess file is an Apache web server configuration file, placed in a site directory, that modifies the server's behavior for that directory and all its subdirectories. It allows action without access to the main configuration, making it the go-to tool for shared hosting for over twenty years.

Its scope covers the essentials of what a site requires from its server: URL rewriting and redirection, response compression, cache and security headers, password protection, blocking addresses. The Apache documentation describes it as a decentralized, per-directory configuration method, and each technical term in this article is covered in the web performance lexicon if a reminder is needed.

The file doesn’t concern everyone: according to W3Techs, as of August 23, 2026, Apache serves 22.6% of websites whose server is identified, behind Nginx (31.4%). The total number of sites that read .htaccess files is actually larger, as LiteSpeed, compatible with Apache directives and present on 14.9% of sites, also interprets them: more than a third of the web still reads these files, including the vast majority of French shared hosting.

Where is the .htaccess file located?

The .htaccess file is located at the root of the site, in the directory containing the public files, often named www, public_html, or htdocs depending on the host. Additional files can exist in any subdirectory, and each applies to its directory and the levels below, in a cascade.

If it seems absent, it’s almost always a display issue: the leading dot in its name makes it a hidden file on Unix systems. In FileZilla, the “Server” menu offers the option “Force showing hidden files”; in cPanel or Plesk file managers and with shared hosts like OVHcloud, a “Show hidden files” checkbox serves the same purpose in display preferences. An invisible file is therefore not a missing file.

The cascade between multiple files deserves particular attention, as it is a classic source of unexplained behavior. An .htaccess file in a subdirectory inherits directives from higher levels and can contradict them: during an audit, it is not uncommon to find three or four stacked files, including one forgotten in a subfolder that cancels the compression configured at the root. A complete inventory of the site’s .htaccess files, from the root directory down to the extension subfolders, is the first step before any diagnosis.

How to create an .htaccess file?

An .htaccess file is created with a simple text editor: the exact name is “.htaccess”, including the leading dot, with nothing before or after. It has no extension, neither .txt nor .html, and saving it as “htaccess.txt” is the most frequent creation error.

Three technical precautions avoid hours of diagnosis: UTF-8 encoding without BOM, as a byte order mark at the beginning of the file can cause a server error; Unix line endings preferably; and a code editor rather than a word processor, which would add invisible and fatal formatting to the file. On Windows, naming the file “.htaccess.” with a trailing dot bypasses the explorer’s refusal to create a name starting with a dot.

What is the default WordPress .htaccess file?

WordPress generates a standard rewrite block in .htaccess as soon as permalinks are enabled, framed by the # BEGIN WordPress and # END WordPress markers. This block redirects all URLs to index.php, and anything written between these markers will be overwritten by WordPress when settings are updated.

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

Line by line: RewriteEngine On enables the rewrite module; the next rule preserves the HTTP authorization header for APIs; the two RewriteCond directives check that the requested URL matches neither a real file (!-f) nor a real directory (!-d); if so, the request is sent to index.php, where WordPress resolves the permalink. It is this mechanism, documented in the WordPress administration manual, that makes your URLs readable, and that is why your custom directives are placed outside the markers, before the block.

A word about ready-made .htaccess files offered by some websites, to download and upload as-is: this is the worst way to configure a server. These files stack dozens of directives for modules that your hosting may not necessarily have, and each uncomprehended line is a potential 500 error and a cost per request. The good practice is the opposite: start with a minimal file, and only add what you understand.

Can the .htaccess file configure PHP?

Yes on hosting where PHP runs as an Apache module, no on the majority of current offerings that use PHP-FPM. The php_value and php_flag directives only work with mod_php: on FPM hosting, they cause an immediate 500 error on the entire site, and configuration is done via a .user.ini file.

# Uniquement si PHP tourne en module Apache (mod_php) :
php_value upload_max_filesize 64M
php_value memory_limit 256M

# Sur PHP-FPM, ces valeurs vont dans un fichier .user.ini :
# upload_max_filesize = 64M
# memory_limit = 256M

The test is simple: if adding a php_value breaks the site, your PHP is running in FPM, and the .user.ini file placed at the root takes over with the same syntax as php.ini. This distinction explains a good part of the "htaccess php" queries that end up in troubleshooting: the copied tutorial was from an era when mod_php was the norm, and the hosting has since changed its execution mode. One question remains that these fundamentals never address: what this file costs the server.

Does the .htaccess file slow down a site?

Yes, the mechanism itself has a cost, regardless of the rules written in it. When the AllowOverride directive permits it, Apache searches for a .htaccess file in each directory of the requested path, on every request, without caching this read. Apache's own documentation recommends avoiding these files completely when possible by accessing the main configuration.

The detail of the mechanism deserves to be understood, as it is counter-intuitive. To serve /blog/article/, Apache checks for the presence of a .htaccess file at the root of the authorized file system, then in each intermediate directory up to the target, reads each of the files found, merges their directives in the correct order, and applies the result. This mechanism, documented on the AllowOverride page of the Apache documentation, is repeated for each request received, including images and scripts : a page that loads 60 resources triggers 60 full traversals.

Diagram comparing the processing of an Apache request with AllowOverride enabled and disabled
With AllowOverride active, Apache searches for a .htaccess file in each directory of the path, for each request, without caching.

Virtual server configuration follows the inverse logic : it is read only once, when Apache starts, and then kept in memory. No disk access, no syntax analysis during the request. The difference is paid for in the initial response time, that TTFB for which Google sets the recommended threshold at 800 ms and for which we detail elsewhere the optimization levers : a few milliseconds per request, multiplied by the number of resources and visitors, form a permanent tax that no one measures because it is diluted everywhere.

The order of magnitude depends on three factors : the depth of the tree, the number of .htaccess files actually present, and the server load. On a site with a flat tree and a single short file, the overhead is counted in fractions of a millisecond per request and remains negligible ; on a deep tree with several long files and a busy disk, it becomes measurable by benchmarking, which explains why all high-performance hosting providers disable AllowOverride on their managed offers. Honest measurement is done on your end, with a load test before and after switching : this is the only figure that matters for your site.

Should you use .htaccess or server configuration?

Server configuration, without hesitation, as soon as you have access to it : same directives, moved into a Directory block of the virtual server, then AllowOverride None to cut off file searching. On shared hosting without access to this configuration, .htaccess remains the only available option, and it does the job.

The answer draws two audiences, and that is the core of the expert position on this subject. Those who manage their server, VPS, or dedicated server benefit from migrating each block of their .htaccess to the configuration, testing it, and then disabling AllowOverride: in audits, we regularly encounter managed servers that are dragging .htaccess files inherited from an old shared hosting plan, paying for the mechanism with no further reason. Those on shared hosting do not have this choice, and the rest of this guide is written for them: clean, understood rules, and as few as possible.

Does Nginx use an .htaccess file?

No, Nginx completely ignores .htaccess files, by architectural choice: all configuration is centralized in files loaded at startup, and reloaded explicitly with each modification. An .htaccess file placed on an Nginx server is a dead file, never read by anyone, which many users who look for it do not know.

This choice explains part of Nginx's speed reputation, which serves 31.4% of sites according to W3Techs: no file searching by directory, no directive analysis during the request. Rules are translated, not copied: the official documentation offers a guide to converting rewrite rules, and most .htaccess redirects are expressed in a single line in the server block. If your WordPress host runs on Nginx, your cache and redirection rules therefore live elsewhere, generally in a dedicated interface or with the managed hosting provider. For everyone else, let's focus on useful directives, starting with the most profitable.

How to enable compression in the .htaccess file?

Compression is enabled with the mod_deflate module, by declaring the MIME types to be compressed. The gain is immediate and considerable: HTML, CSS, and JavaScript commonly lose 60 to 80% of their transferred weight, which directly benefits LCP. This is the directive with the best effort-to-gain ratio in the entire file.

<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/css text/plain
  AddOutputFilterByType DEFLATE application/javascript application/json
  AddOutputFilterByType DEFLATE application/xml image/svg+xml
</IfModule>

Every element counts: the IfModule condition prevents a server error if the module is not loaded, and each AddOutputFilterByType, documented with the mod_deflate module, adds a content type to the compression filter. The list is deliberately limited to text formats: JPEG, WebP, AVIF, MP4, and WOFF2 fonts are already compressed by their own format, and recompressing them wastes CPU for zero gain, sometimes negative.

On hosting that offers the mod_brotli module, the same logic applies with the BROTLI_COMPRESS filter, for an additional gain of around 15 % on text files. The choice between gzip, Brotli, and Zstandard, their levels, and their uses is a comparison of compression algorithms in its own right: remember here that the .htaccess directive only enables what the hosting makes available, and nothing more. Compression lightens the initial load; caching, on the other hand, eliminates subsequent ones.

How to configure browser cache in the .htaccess file?

Browser cache is configured with the mod_expires and mod_headers modules, which set the Expires and Cache-Control headers on responses. A duration of one year for versioned static resources, i.e., max-age=31536000, is the recommended reference value by browsers, and it eliminates dozens of requests on repeat visits.

<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType text/css "access plus 1 year"
  ExpiresByType application/javascript "access plus 1 year"
  ExpiresByType image/webp "access plus 1 year"
  ExpiresByType image/avif "access plus 1 year"
  ExpiresByType font/woff2 "access plus 1 year"
  ExpiresByType text/html "access plus 0 seconds"
</IfModule>
<IfModule mod_headers.c>
  <FilesMatch "\.(css|js|woff2|webp|avif)$">
    Header set Cache-Control "max-age=31536000, immutable"
  </FilesMatch>
</IfModule>

The first block relies on mod_expires to set a duration by resource type, with a decisive exception: HTML expires immediately, because a page must always be updatable. The second relies on mod_headers to add the immutable directive, which tells the browser to never revalidate the resource during its lifetime, as documented by the MDN Cache-Control reference. This strategy only works if files change names with each modification, a principle summarized better than anyone by Jake Archibald.

In this model, you never change the content of a given URL: you change the URL.

Jake Archibald, a recognized web platform engineer, in his article "Caching best practices & max-age gotchas" published on his blog on April 27, 2016

WordPress and its build tools already version most resources by a parameter or a fingerprint in the filename, making this model applicable as is. The complete operation of the chain, from the browser to the CDN to the database, is detailed in our guide to web caching at all levels: .htaccess only controls the first link, on the browser side, but it's the one that purely and simply avoids requests.

Should ETags be disabled?

Yes in a multi-server environment, no need to worry about it on a single server. ETag is a validation fingerprint generated by Apache: if two servers behind a load balancer generate different fingerprints for the same file, each switch invalidates visitors' cache, and the expected 304 responses become full 200s again.

<IfModule mod_headers.c>
  Header unset ETag
</IfModule>
FileETag None

The two directives complement each other : FileETag None asks Apache to no longer generate fingerprints, and Header unset ETag removes those that another link might have added. Validation is still ensured by the Last-Modified header, which is sufficient to produce 304 responses, saving the response body ; the exact semantics of these codes and their server cost are the subject of our guide to HTTP codes, particularly the mechanics of 304 responses and redirects. These redirects, precisely, are also configured in .htaccess.

How to make a 301 redirect in the .htaccess file ?

A permanent redirect is written on one line with the Redirect 301 directive, followed by the original path and the full destination URL. The 301 code indicates a permanent move that search engines remember, while a 302 signals a temporary move that nothing consolidates : the wrong choice is paid for in search engine ranking.

Redirect 301 /ancienne-page/ https://www.exemple.fr/nouvelle-page/
RedirectMatch 301 ^/blog/[0-9]{4}/(.*)$ https://www.exemple.fr/$1

RewriteEngine On
RewriteCond %{HTTPS} !=on [OR]
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^ https://www.exemple.fr%{REQUEST_URI} [R=301,L]

Three levels of tools coexist in this example. Redirect handles an exact URL ; RedirectMatch accepts a regular expression, here to remove the year from a blog's permalinks by capturing the end of the URL in $1 ; and the mod_rewrite block forces HTTPS and the www prefix in a single rule. The double RewriteCond linked by [OR] is the detail that matters : it triggers the redirect if the protocol or host is incorrect, and sends directly to the final form, which guarantees a single hop instead of a chain of two successive redirects.

Every avoided hop counts, because a redirect costs a full network round trip before the page even starts loading, commonly 100 to 300 ms on mobile which adds to the TTFB. The semantics of 301, 302, or 308 codes and their respective costs are covered in the already mentioned guide to HTTP codes : here, remember the golden operational rule, always redirect to the final destination, never to a URL that itself redirects.

How many redirects can be put in an .htaccess file ?

A few hundred at most : beyond about 500 rules, our agency practice concludes that an alternative solution is needed. Each redirect adds a match attempt evaluated on each request, and the accumulation is paid for in processor consumption on all pages, including those that do not redirect anything.

The cost actually accumulates on two levels. The file is reread for each resource served, as explained above, and each rule constitutes an additional matching attempt to be evaluated in order: on a .htaccess file with 800 Redirect lines, a simple image undergoes up to 800 comparisons before being served. The load therefore increases with traffic multiplied by the number of rules, and the first reflex remains that of this guide: move them to the virtual server configuration, where they are loaded once into memory at startup, without rereading or analyzing the file for each request.

When this move is not possible, or would sacrifice the editing flexibility an team needs daily, the best alternative is an application-managed mechanism. On WordPress, the "Redirection" plugin or the equivalent functionality of Yoast Premium, with logging disabled, do this job optimally: the cost is shifted from 100% of visitors to only the visitors who actually follow a redirection. The trade-off is accepted, each redirection becomes a bit longer as it queries the database, and it is precisely this trade-off that makes the volume of rules the decision criterion. Security, the next step, even allows removing the HTTPS redirection itself for regular visitors.

Is your site as fast as your visitors expect?

Discover how we can help you

How to secure a site with the .htaccess file?

The .htaccess covers three security areas: restricting access to directories or files, blocking addresses or robots, and setting security headers on responses. However, it acts after Apache has accepted the connection, so part of the request cost has already been paid: blocking upstream is preferable.

How to password-protect a directory?

Protection relies on a pair of files: a .htaccess that requires authentication, and a .htpasswd file that stores encrypted credentials. The password file should be placed outside the public site tree, never in a directory accessible by URL.

AuthType Basic
AuthName "Espace restreint"
AuthUserFile /home/compte/.htpasswd
Require valid-user

AuthType Basic activates basic HTTP authentication, AuthName defines the label displayed by the browser, AuthUserFile points to the credentials file using an absolute path, and Require valid-user accepts any declared user. The .htpasswd file is generated with the htpasswd -c command followed by the path and username, or by an online generator on shared hosting: the complete procedure is in the Apache authentication guide. This mechanism effectively protects a pre-production environment or a back-office, with one caveat: in basic HTTP, credentials are only encrypted by the HTTPS that transports the request.

How to protect sensitive WordPress files?

Three targets concentrate the majority of automated attacks on WordPress: the wp-config.php file which contains database access credentials, the xmlrpc.php entry point exploited for brute-force attacks, and the upload directories where a PHP script uploaded becomes an executable backdoor.

<Files wp-config.php>
  Require all denied
</Files>
<Files xmlrpc.php>
  Require all denied
</Files>

# Dans wp-content/uploads/.htaccess :
<FilesMatch "\.php$">
  Require all denied
</FilesMatch>

The Files blocks target a file by its exact name and Require all denied refuses any HTTP request to it, without hindering WordPress which reads wp-config.php via the file system, not via HTTP. The third block, to be placed in a .htaccess file in the uploads directory, neutralizes the execution of any PHP file that might slip in: the media library has no legitimate reason to execute code. Before blocking xmlrpc.php, check that no service uses it, such as the WordPress mobile app or Jetpack.

What security headers to add?

Five headers form the recommended foundation: Strict-Transport-Security, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and Permissions-Policy. The first has a dual effect, security and performance: HSTS memorized by the browser eliminates the HTTP to HTTPS redirection round trip on each subsequent visit.

<IfModule mod_headers.c>
  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
  Header always set X-Content-Type-Options "nosniff"
  Header always set X-Frame-Options "SAMEORIGIN"
  Header always set Referrer-Policy "strict-origin-when-cross-origin"
  Header always set Permissions-Policy "accelerometer=(), camera=(), geolocation=(self), microphone=(), payment=(self)"
</IfModule>

In order: HSTS enforces HTTPS for one year, including subdomains; nosniff forbids the browser from guessing a file type; SAMEORIGIN prevents the site from being displayed in a third-party iframe, a defense against clickjacking; Referrer-Policy limits the URL transmitted to third-party sites; and Permissions-Policy cuts off access to unnecessary sensors. Geolocation and payment remain authorized here for the site itself, a setting designed for stores where point-of-sale lookup and integrated payment depend on them, as we do on sites we support in e-commerce optimization.

How to block an IP address, a bot, or hotlinking?

Blocking is done using Require directives for IP addresses, and RewriteCond conditions for agents or referrers. Hotlinking, the practice of displaying your images from their URL on another site, is blocked by refusing requests for images whose referrer is external to the domain.

<RequireAll>
  Require all granted
  Require not ip 203.0.113.42
</RequireAll>

RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^https://(www\.)?exemple\.fr/ [NC]
RewriteRule \.(jpe?g|png|webp|avif)$ - [F]

The first block allows everyone except the listed address, to be multiplied per line with Require not ip. The second refuses (flag [F], for Forbidden, a 403 code) any image request whose referrer exists and is not yours: the first condition allows empty referrers, because browsers in private browsing mode do not send them, and blocking them would break display for legitimate visitors. The gain is in bandwidth and load, not in your page metrics.

However, keep the measure on these blockages: each rule is evaluated on each request, and a .htaccess transformed into a blacklist of 300 lines becomes a cost in itself. Truly effective blocking is done at the edge, by an application firewall or a CDN, before the request reaches Apache and eats into the 800ms TTFB budget: the .htaccess remains the last line of defense, not the first. Provided, of course, that all these rules haven't broken the site in the process.

Why is my site returning a 500 error after modification?

A 500 error after editing the .htaccess almost always means an invalid directive or a missing module: Apache rereads the file on the next request, fails to interpret it, and returns a server error for the entire site. Reverting is immediate: restore the copy of the previous file, which you should always have made before modifying.

A simple method eliminates most of the risk, and it's the one we apply when intervening, because every minute of a 500 error is a minute of total unavailability:

  • copy the file before any modification, for example to .htaccess.bak, to be able to restore in a few seconds;
  • add directives block by block, testing the site after each addition, rather than pasting twenty lines at once;
  • enclose each module with its IfModule condition, which turns a missing module into an ignored directive rather than a fatal error;
  • check module availability with the host, through the offer's documentation or a phpinfo call;
  • consult the Apache error log, named error_log or errors depending on the host, which names the exact line in question.

The error log is worth knowing how to locate before the incident, not during. On an administered server, it usually lives in /var/log/apache2/ or in the site's logs directory; on a shared host, the host provides it in their interface, under a title like "logs" or "statistics and logs" at OVHcloud. The line you are interested in starts with the word Invalid command or the name of the missing module, and it indicates the exact file and line at fault: the diagnosis then takes a single read.

How to test a rewrite rule without breaking the site?

The safe method combines three tools: an online rule tester to validate syntax outside of production, a pre-production copy to observe real behavior, and the curl -I command to check the result without browser caches. A faulty RewriteRule rarely produces a clear error: it produces an infinite redirect loop or silent misdirection.

The curl -I command followed by the URL displays the status code and Location header of the response, hop by hop if re-requested on each destination : this is the most direct way to verify that a redirect correctly returns a single 301 to the final target. The browser is a poor testing tool here, as it aggressively caches permanent redirects : a corrected rule may seem to always be broken due to the local cache, and a private browsing window is not always enough to be convinced. Cache extensions add their own layer to this mille-feuille.

What happens when a cache extension writes to the file ?

On WordPress, WP Rocket, W3 Total Cache, and their equivalents write their own directives in the .htaccess, framed by start and end markers similar to WordPress's. These blocks are regenerated with each setting change or update, so any manual edits inside will be lost without warning.

The conflicts observed in audits always follow the same scenario : a compression or cache rule added manually between extension markers, overwritten three weeks later by an update, and a site whose performance degrades without anyone understanding why. The cohabitation rule has two points : your directives live outside all markers, and a single source configures each function, either the extension or you, never both. WordPress multisite adds its own subtlety, with a rewrite block different from a single site, varying by subdomain or subdirectory mode : its rules are generated from the network administration, not from a template copied online.

.htaccess directives and their effect on performance

The following table lists the directive families covered in this guide, with the Apache module that supports them and the measurable effect to expect on a site's metrics.

DomainKey directivesApache moduleMeasurable effect
CompressionAddOutputFilterByType DEFLATEmod_deflate60 to 80 % less weight on text, improved LCP
Browser cacheExpiresByType, Cache-Control immutablemod_expires, mod_headersRequests eliminated on repeat visits
ValidationFileETag None, Last-Modifiedcore, mod_headersReliable 304 responses across multiple servers
RedirectsRedirect 301, RewriteRulemod_alias, mod_rewriteOne round trip saved per avoided hop
SecurityRequire, Header always set, HSTSmod_authz_core, mod_headersHTTPS redirect eliminated on subsequent visits
Mechanism itselfAllowOverride None (server-side)coreFile reads deleted on each request

The last line summarizes the paradox inherent in this guide: the best optimization related to .htaccess is to do without it when you can. All other lines remain fully valid for those who don't have control over their server, and this is precisely what makes this file both essential and temporary.

A file that says where your hosting stands

The state of an .htaccess file tells the story of a site: the stacked blocks of extensions uninstalled for years, rules copied without being understood, redirects pointing to redirects. In an audit, this file is often the first symptom of a larger configuration debt, both on the hosting and CMS sides.

Reading it also raises a question of trajectory. A growing site eventually hits the limits of shared hosting, and moving to a managed server changes the rules of the game: directives move into the configuration, AllowOverride is disabled, and the silent tax disappears. This tipping point, where the troubleshooting tool gives way to architecture, says more about the maturity of a hosting environment than any test score. The ideal .htaccess, deep down, is the one you've finally emptied because nothing, not cache, not compression, not redirects, still needed to live there.

Continue reading