Forum Moderators: phranque
Options +FollowSymLinks
#Specify IP Allowed to Login
<Files wp-login.php>
order deny,allow
deny from all
allow from 12.345.67.891
</Files>
#STRONG HTACCESS PROTECTION
<Files ~ "^.*\.([Hh][Tt][Aa])">
order allow,deny
deny from all
satisfy all
</Files>
# 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
<files wp-config.php>
Order allow,deny
Deny from all
</files>
#Prevent directory browsing
Options All -Indexes
#Redirect non-www to www
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]
#Redirect index.html
RewriteCond %{THE_REQUEST} ^.*/index.html [NC]
RewriteRule ^(.*)index.html$ http://www.example.com/$1 [R=301,L]
#Redirect IP Address
RewriteCond %{HTTP_HOST} ^123\.456\.789\.999$
RewriteRule ^(.*)$ http://www.example.com/$1 [L,R=301]
#Force Trailing Slash
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*)$ http://www.example.com/$1/ [L,R=301]
#Stop Hotlinking
RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(www\.)example.com/.*$ [NC]
RewriteRule \.(gif|jpg|jpeg|bmp|zip|rar|mp3|flv|swf|xml|php|png|css|pdf)$ - [F] [edited by: phranque at 8:26 am (utc) on Nov 1, 2014]
[edit reason] exemplified domain [/edit]
#Redirect non-www to www
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]
...
#Redirect IP Address
RewriteCond %{HTTP_HOST} ^123\.456\.789\.999$
RewriteRule ^(.*)$ http://www.example.com/$1 [L,R=301]
#Redirect non-canonical hostname requests
RewriteCond %{HTTP_HOST} !^(www\.example\.com)?$ [NC]
RewriteRule ^(.*)$ http://www.example.com/$1 [L,R=301]
#Prevent directory browsing
Options All -Indexes
Options -Indexes #STRONG HTACCESS PROTECTION
<FilesMatch "^\.ht"> will I need to use Options +FollowSymLinks
Cleaning up an htaccess file
Step 1: Organize. Collect all the directives for each module in one place. The server doesn't care, but you-- and anyone who comes along after you-- will appreciate it.
Tip: Use a text editor with a "Find All" window to pull up all lines beginning with the element "Rewrite..." That takes care of mod_rewrite; dump them all at the end for now.
Step 2: Get rid of all <IfModule> envelopes. Not their contents, just the envelopes themselves. These envelopes are hallmarks of mass-produced htaccess files that have to work anywhere, on any server. You are now on your own site. Any given mod is either available to you or it isn't.
Step 3: Sort by module. The server doesn't care what order the directives are listed in, or even if rules from different modules are all garbled together. Each module works separately, seeing only its own directives. But humans need to be able to find things.
For most people it will be most practical to group one-liners at the beginning:
Options -Indexes
is a good start. If your htaccess file contains only one line, that's probably it. Other quick directives are ones starting with words like AddCharset or Expires. Then list your error documents.
If you have any very short Files or FilesMatch envelopes, put them near the top too. For example:<Files "robots.txt">
Order Allow,Deny
Allow from all
</Files>
<FilesMatch "\.(css|js)">
Header set X-Robots-Tag "noindex"
</Files>
Be sure to have an "Allow from all" envelope for your custom 403 page. If you are on shared hosting and they provide default error-document names such as "forbidden.html", this has probably already been done in the config file. But it does no harm to repeat it.
Step 4: Consolidate redirects.
Step 4a: Get rid of mod_alias. If your htaccess file contains any mod_rewrite directives, it can't use mod_alias (Redirect... by that name), or things may happen in the wrong order. For large-scale updating, use these Regular Expressions, changing \1 to $1 if that's what your text editor uses. Each of these can safely be run as an unsupervised global replace.
# change . to \. in pattern
^(Redirect \d\d\d \S+?[^\\])\.
TO
\1\\.
# now change Redirect to Rewrite
^Redirect(?:Match)? 301 /(.+)
TO
RewriteRule \1 [R=301,L]
# and if needed
^Redirect(?:Match)? 410 /(.+)
TO
RewriteRule \1 - [G]
^Redirect(?:Match)? 403 /(.+)
TO
RewriteRule \1 - [F]
Step 4b: Sort your RewriteRules. At the beginning is the single line
RewriteEngine on
A RewriteBase is almost never needed; get rid of any lines that mention it. Instead, make sure every target begins with either protocol-plus-domain or a slash / for the root.
Sort RewriteRules twice.
First group them by severity. Access-control rules (flag [F]) go first. Then any 410s (flag [G]). Not all sites will have these. Then external redirects (flag [R=301,L] unless there is a specific reason to say something different). Then simple rewrite (flag [L] alone). Finally, there may be a few rules without [L] flag, such as cookies or environmental variables.
Function overrides flag. If your redirects are so complicated that they've been exiled to a separate .php file, the RewriteRule will have only an [L] flag. But group it with the external redirects. If certain users are forcibly redirected to an "I don't like your face" page, the RewriteRule will have an R flag. But group it with the access-control [F] rules.
Then, within each functional group, list rules from most specific to most general. In most htaccess files, the second-to-last external redirect will take care of "index.html" requests. The very last one will fix the domain name, such as with/without www.
Leave a blank line after each RewriteRule, and put a# comment
before each ruleset (Rule plus any preceding Conditions). A group of closely related rulesets can share an explanation.
Step 5: Notes on error documents.
Reminder: ErrorDocument directives must not include a domain name, or else everything will turn into a 302 redirect. Start each one with a / representing the root.
Caution: Since each module is an island, any module that can issue a 403 must have its own error-document override. "Allow from all" covers mod_authzzzz. If you have RewriteRules that end in [F], make sure your 403 documents can bypass these rules.
# Protect wp-content directory
order deny,allow
deny from all
<files ~ ".(xml|css|jpe?g|png|gif|js)$">
allow from all
</files>
<IfModule mod_rewrite.c>
</IfModule>
RewriteBase /
# 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
Options -Indexes
<files wp-config.php>
Order allow,deny
Deny from all
</files>
<files error_log>
Order allow,deny
Deny from all
</files>
<files readme.html>
Order allow,deny
Deny from all
</files>
<files license.txt>
Order allow,deny
Deny from all
</files>
# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
#Redirect non-www to www
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ [%{HTTP_HOST}...] [R=301,L]
#Redirect index.html
RewriteCond %{THE_REQUEST} ^.*/index.html [NC]
RewriteRule ^(.*)index.html$ http://www.example.com/$1 [R=301,L]
#Redirect IP Address
RewriteCond %{HTTP_HOST} ^999\.999\.999\.999$
RewriteRule ^(.*)$ http://www.example.com/$1 [L,R=301]
#Force Trailing Slash
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*)$ http://www.example.com/$1/ [L,R=301]
#Stop Hotlinking
RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(www\.)example.com/.*$ [NC]
RewriteRule \.(gif|jpg|jpeg|bmp|zip|rar|mp3|flv|swf|xml|php|png|css|pdf)$ - [F]
# only allow 88.888.88.888 IP to access /wp-login.php or /wp-admin/
RewriteCond %{THE_REQUEST} /(wp-login\.php|wp-admin/) [NC]
RewriteCond %{REMOTE_ADDR} !=88.888.88.888
RewriteRule ^ - [F]
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
Create a separate .htaccess file and put it in your /wp-content directory.
This code will allow access to images, CSS, java-script and XML files, but deny it for any other type.
# Protect wp-content directory
order deny,allow
deny from all
<files ~ ".(xml|css|jpe?g|png|gif|js)$">
allow from all
</files>
<FilesMatch "\.(xml|css|jpe?g|png|gif|js)$">
Allow from all
</FilesMatch>
It seems that two of your tips are conflicting.
I do not fully understand .htaccess
<files wp-config.php>
#Redirect non-www to www
# only allow 88.888.88.888 IP to access /wp-login.php or /wp-admin/
RewriteCond %{THE_REQUEST} /(wp-login\.php|wp-admin/) [NC]
RewriteCond %{REMOTE_ADDR} !=88.888.88.888
RewriteRule ^ - [F]
RewriteCond %{THE_REQUEST} /(wp-login\.php|wp-admin/) [NC]
RewriteCond %{REMOTE_ADDR} !^88\.888\.88\.888$
RewriteRule ^(wp-login\.php|wp-admin/) - [F]
RewriteCond %{REQUEST_URI} hogwash
RewriteRule . - [F] RewriteRule hogwash - [F] Are you on the most recent WP install? Do they really give this set of rules, in this order? Or did you absent-mindedly paste in rules that were originally located outside the WP section?
Note further that the line about THE_REQUEST is only necessary if the login/admin files can also be accessed in other ways,
for example by an internal rewrite or something the WP software does. If nobody but you uses them, you can omit this line because it's then redundant.
#Redirect index.html
RewriteCond %{THE_REQUEST} ^.*/index.html [NC]
RewriteRule ^(.*)index.html$ http://www.example.com/$1 [R=301,L]
Then, within each functional group, list rules from most specific to most general.
In most htaccess files, the second-to-last external redirect will take care of "index.html" requests. The very last one will fix the domain name, such as with/without www.
#Redirect non-www to www
RewriteCond %{HTTP_HOST} !^www\. [NC]
RewriteRule ^(.*)$ [%{HTTP_HOST}...] [R=301,L]
#Redirect index.html
RewriteCond %{THE_REQUEST} ^.*/index.html [NC]
RewriteRule ^(.*)index.html$ http://www.example.com/$1 [R=301,L]
#Redirect IP Address
RewriteCond %{HTTP_HOST} ^123\.456\.789\.999$
RewriteRule ^(.*)$ http://www.example.com/$1 [L,R=301]
#Force Trailing Slash
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*)$ http://www.example.com/$1/ [L,R=301]
Do you mean to completely delete line 2 (RewriteCond)?
Is the following correct:
#Redirect index.html
RewriteRule ^(.*)index.html$ http://www.example.com/$1 [R=301,L]
What is the difference between the below? In terms of ordering rules, which one should be near the top?
[R=301,L]
[L,R=301]
Is this order correct?
#only allow 88.888.88.888 IP to access /wp-login.php or /wp-admin/
#Stop Hotlinking
#Redirect IP Address
#Redirect non-www to www
RewriteCond %{HTTP_HOST} !^(www\.example\.com)?$
RewriteRule (.*) http://www.example.com/$1 [R=301,L] #Force Trailing Slash
#Redirect index.php
#Original Wordpress section
RewriteRule ^([^.]+[^./])$ http://www.example.com/$1/ [R=301,L] index\.(php|html) RewriteCond %{THE_REQUEST} [A-Z]{3,9}\ /(([^/]+/)*)index\.(html|php)
RewriteRule index\.(html|php) http://www.example.com/%1 [R=301,L] RewriteRule index\.php - [L] RewriteCond %{REQUEST_URI} !-f
RewriteCond %{REQUEST_URI} !-d
RewriteRule . http://www.example.com/index.php [L] RewriteRule ^forbidden\.html - [L] [edited by: phranque at 12:00 am (utc) on Nov 4, 2014]
[edit reason] typofix [/edit]
Have you done your research and determined that a final slash-- for files that are, in fact, not directories but pages-- is the best way to go?
The moment you create RewriteRules in [F] you need one more thing:
RewriteRule ^forbidden\.html - [L]
substituting the exact name and URLpath of your custom 403 page, assuming you've got one. (If the page is created by WP, things get more complicated.) Otherwise there will be an infinite loop. The unwanted visitor does get locked out, but only after the server has made 30 attempts to send out the 403 page.
which should list jpg|png etc in the body of the rule, not in a Condition
# Stop Hotlinking
RewriteEngine on
RewriteCond %{HTTP_REFERER} !^$
RewriteCond %{HTTP_REFERER} !^http://(www\.)example.com/.*$ [NC]
RewriteRule \.(gif|jpg|jpeg|bmp|zip|rar|mp3|flv|swf|xml|php|png|css|pdf)$ - [F]
#Redirect index.php
RewriteCond %{THE_REQUEST} [A-Z]{3,9} /(([^/]+/)*)index\.(html|php)
RewriteRule index\.(html|php) http://www.example.com/%1 [R=301,L]
#Domain-name canonicalization redirect
RewriteCond %{HTTP_HOST} !^(www\.example\.com)?$
RewriteRule (.*) http://www.example.com/$1 [R=301,L]
The trailing-slash redirect should come immediately before the WordPress section.
domain-name canonicalization redirect is always the very last of your redirects
Personally I dislike the slash but so many SEO tutorials suggested the slash
The links without file extension make browsers think about what the server returns. The server will first check if any such link exists. Then it will return the respective output. It can be HTML, text, image, a file or even a redirect. Hence browsers also have to check what is returned. This all increases the processing time and overall HTTP requests. Hence there a lag caused."
Is this true?
Is this code mandatory? I didn't make a 403 page.
are you saying there is a mistake in my #Stop Hotlinking? Or are you simply pointing out a general tip?
RewriteCond %{THE_REQUEST} [A-Z]{3,9} /(([^/]+/)*)index\.(html|php)
RewriteRule index\.(html|php) http://www.example.com/%1 [R=301,L]
Originally I had:
#Redirect index.php
RewriteCond %{THE_REQUEST} ^.*/index.php [NC]
RewriteRule ^(.*)index.html$ http://www.example.com/$1 [R=301,L]
In your URL you have /%1 instead of /$1, is the "percent sign" a typo?
And can you explain the difference in the two versions? Is your version more efficient, therefore the website will load faster?
In your version, should I put [NC] at the end of RewriteCond, like this?:RewriteCond %{THE_REQUEST} [A-Z]{3,9} /(([^/]+/)*)index\.(html|php) [NC]
Are (.*) and ^(.*)$ interchangeable?
is #Original Wordpress section considered a "redirect"? Therefore anything with "RewriteRule" are considered "redirect", correct?
Is it true that #Original Wordpress section takes Spot 4 because it's an internal rewrite? But according to your suggestion above, are you suggesting #Domain-name canonicalization to take Spot 4?
Also where would I put
RewriteRule ^forbidden\.html - [L]
is this order correct?
#Redirect index.php
#Domain-name canonicalization redirect
#Force Trailing Slash
#Original Wordpress section
No, it is nonsense. Scratch that site off your Trusted Information list. The server does not have to check anything, beyond the single "Does this file exist?"
Make sure WP's 403 page displays as intended in both of these situations:
-- a 403 created by something other than mod_rewrite, such as saying "Deny from {your own IP}"
-- a 403 created by mod_rewrite, such as denying requests for admin or login pages
In your version, should I put [NC] at the end of RewriteCond, like this?:
RewriteCond %{THE_REQUEST} [A-Z]{3,9} /(([^/]+/)*)index\.(html|php) [NC]
No. If someone asks for InDex.HtMl they deserve a 404.
RewriteRule ^[A-Z./-]+$ http://www.example.com/capslock.html [R=301,L] RewriteRule ^forbidden\.html - [L]
It's often appropriate to use the same physical page for both 404 and 410 errors, since your average human doesn't much care whether
403 pages are a whole nother issue
403 pages are for humans.
But it's the rare robot that bothers to read past the "403" response header.
[edited by: phranque at 1:19 am (utc) on Nov 3, 2014]
[edit reason] exemplified domain [/edit]
RewriteRule ^forbidden\.html - [L]
Tutorials use "ErrorDocument" instead of "RewriteRule" - are they interchangeable? For example:ErrorDocument 403 http://www.example.com/error-403/
ErrorDocument 403 /forbidden.html ErrorDocument 403 /boilerplate/forbidden.html <Files "forbidden.html">
Order Deny,Allow
Allow from all
</Files>
Build your own 403 page, give it a name and park it on the server.
Once this page physically exists, it will automatically be exempt from all WordPress activity because of WP's inherent !-f rule.
But you still need a RewriteRule with [L] flag so the page can be sent out at all.
Each module that issues 403s needs a separate exemption to cover requests for the error document.
<Files "forbidden.html">
Order Deny,Allow
Allow from all
</Files>
#Redirect index except I replaced html with (html|php) All along I was testing www.example.com/wp-includes/ which was showing 404 instead of 403 pageIt looks like your WordPress install is in the root directory.
But this time I tested all the other stuff in my .htaccess
such as www.example.com/readme.html and www.example.com/wp-config.php
#Redirect index
RewriteCond %{THE_REQUEST} ^.*/index\.(html|php) [NC]
RewriteRule ^(.*)index.(html|php)$ http://www.example.com/$1 [R=301,L]
# BEGIN WordPress
<IfModule mod_rewrite.c> </IfModule>
# END WordPress and there it was - a default 403 page!Yes, the server has a default 403 - almost every host gives you some basic error pages like 404, 403, 500. The whole point of creating your own to replace those with (and telling the server where they are) is to create a better, more useful user experience. A standard server 404 page does not help anyone find what they came to your site for, a 404 page with a smile helps them with the most likely links to get back to what they wanted to do instead of closing the browser and going back to search somewhere else. Same with a custom 403 that lets people know that sometimes mistakes are made and you apologize for the inconvenience and maybe offer a way to contact you about the problem.
the below triggered a 500 Internal Server Error for my Shared hosting account.
<snip>
RewriteCond %{THE_REQUEST} [A-Z]{3,9} /(([^/]+/)*)index\.(html|php)
RewriteRule index\.(html|php) http://www.example.com/%1 [R=301,L]
[A-Z]{3,9}\ /(([^/]+/)*)index\.(html|php) RewriteCond %{THE_REQUEST} ^.*/index\.(html|php) [NC]
RewriteRule ^(.*)index.html$ http://www.example.com/$1 [R=301,L]
^.*
Your site uses Wordpress for everything (home page, posts, articles, categories, etc)?
WP is installed in your root directory?
Does your site use any .html pages?
#Redirect index
RewriteCond %{THE_REQUEST} ^.*/index\.(html|php) [NC]
RewriteRule ^(.*)index.(html|php)$ http://www.example.com/$1 [R=301,L]
If there is no index.php or index.html in your root directory, you don't need this rule, it is handled inside the WP envelope.
Use the www rewrite to keep WP from needing to process every request.
Don't enclose everything in your htaccess file inside the WP envelope, that is the part that *might* be overwritten with a major update.
RewriteEngine On used to belong within the WordPress envelope but I removed it and pasted it before all the rules. Googlebot considers these as different URLs:
www.example.com
www.example.com/
www.example.com example.com example.com/directory/index.php
example.com/directory/
example.com/directory
In a hand-rolled HTML site this is not a problem, because an index redirect takes care of the first while mod_dir automatically deals with the third. In WordPress (or any CMS) you have to make sure that only #2 OR only #3 is valid, while the other redirects.