97

My website runs a script called -> WSS wallpaper script

My Problem -> I have been trying to force remove or add trailing slash to the end of my URL to prevent duplicated content and also to clean up my URLs.

I have tried all sorts and tried everything I could think of and loads from the interwebs but no such luck yet! It might be a quick fix but I have looked at it so much I am probably blind to something dead obvious.

So I present you with all my .htaccess code:

DirectoryIndex index.php

RewriteEngine on
RewriteRule ^download/([0-9]+)?/([0-9]+)x([0-9]+)/([^/\.]+) image.php?id=$1&width=$2&height=$3&cropratio=$4&download=1 [L]
RewriteRule ^file/([0-9]+)?/([0-9]+)x([0-9]+)/([^/\.]+) image.php?id=$1&width=$2&height=$3&cropratio=$4 [L]
RewriteRule ^preview/([0-9]+)?/([0-9]+)x([0-9]+)/([^/\.]+) wallpaper_preview.php?id=$1&width=$2&height=$3&name=$4 [L]
RewriteRule ^thumbnail/([0-9]+)?/([0-9]+)x([0-9]+)/([^/\.]+)/([^/\.]+)/([^/\.]+)/([^/]+) image.php?wallpaper_id=$1&width=$2&height=$3&cropratio=$4&align=$5&valign=$6&file=$7 [L]
RewriteRule ^cat/([0-9]+)?/([^/\.]+)/p([0-9]+) index.php?task=category&id=$1&name=$2&page=$3 [L]
RewriteRule ^cat/([0-9]+)?/([^/\.]+)/([0-9a-zA-Z?-]+)/p([0-9]+) index.php?task=category&id=$1&name=$2&sortby=$3&page=$4 [L]
RewriteRule ^cat/([0-9]+)?/([^/\.]+)/([0-9a-zA-Z?-]+)-([0-9]+) index.php?task=category&id=$1&sortby=$3&page=$4 [L]
RewriteRule ^cat/([0-9]+)?/([^/\.]+) index.php?task=category&id=$1&name=$2 [L]
RewriteRule ^tag/([^/\.]+)/([0-9a-zA-Z?-]+)/([0-9]+) index.php?task=tag&t=$1&sortby=$2&page=$3 [L]
RewriteRule ^tag/([^/\.]+) index.php?task=tag&t=$1 [L]
RewriteRule ^profile/([0-9]+)?/([^/\.]+) index.php?task=profile&id=$1&name=$2 [L]
RewriteRule ^profile/comments/([0-9]+)?/([^/\.]+) index.php?task=users_comments&id=$1&name=$2 [L]
RewriteRule ^page/([0-9]+) index.php?task=view_page&id=$1 [L]  
RewriteRule ^register index.php?task=register [L] 
RewriteRule ^lost-password index.php?task=lost_pass [L] 
RewriteRule ^links index.php?task=links [L]
RewriteRule ^news/item/([0-9]+)/([^/\.]+) index.php?task=news&id=$1 [L]
RewriteRule ^news/page([0-9]+) index.php?task=news&page=$1 [L]
RewriteRule ^members/([^/\.]+)-([^/\.]+)/page([0-9]+)? index.php?task=member_list&sort=$1&order=$2&page=$3 [L]
RewriteRule ^members index.php?task=member_list [L]
RewriteRule ^messages index.php?task=messages [L]
RewriteRule ^submit index.php?task=submit [L]
RewriteRule ^search/([^/\.]+) index.php?task=search&q=$1 [L]
RewriteRule ^search index.php?task=search [L]
RewriteRule ^submit index.php?task=submit [L]
RewriteRule ^r-([0-9]+)?-([0-9]+)? go.php?id=$1&ref=$2 [L]
RewriteRule ^r-([0-9]+)? go.php?id=$1 [L]
RewriteRule ^([^/\.]+)/([0-9]+)/([^/\.]+) index.php?task=view&id=$2&name=$3 [L]
RewriteRule ^news/([^/\.]+) index.php?task=news&name=$1 [L]
RewriteRule ^profile/([^/\.]+) index.php?task=profile&name=$1 [L]
RewriteRule ^news index.php?task=news [L]
RewriteRule ^page/([^/\.]+) index.php?task=view_page&name=$1 [L]  
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([^/\.]+)/([0-9a-zA-Z'?-]+)/([0-9]+) index.php?task=category&name=$1&sortby=$2&page=$3 [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([^/\.]+)/([^/\.]+) index.php?task=view&name=$2 [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^([^/\.]+) index.php?task=category&name=$1 [L]


## www reslove ##
RewriteCond %{HTTP_HOST} !^www\.
RewriteRule ^(.*)$ http://www.%{HTTP_HOST}/$1 [R=301,L]
## www reslove ##

## index reslove ##
Options +FollowSymLinks
RewriteCond %{THE_REQUEST} ^.*/index\.php
RewriteRule ^(.*)index.php$ http://www.epicwallpaper.net/$1 [R=301,L]
## index reslove ##
MrWhite
  • 43,179
  • 8
  • 60
  • 84
Luke
  • 973
  • 1
  • 7
  • 5
  • 1
    Also If anyone can fix the bad boy could you post a reply here Thank you – Luke Jan 28 '14 at 21:29
  • Please post the code here. – Surreal Dreams Jan 28 '14 at 21:30
  • 1
    Edit your post, paste your code into your post, highlight it, press "CTRL+K" to format it as code – Jon Lin Jan 28 '14 at 21:32
  • Do you really have **'** or **?** allowed in your URI? One of the RewriteRules (near the bottom) suggests so. I'm concerned that they might confuse the server even before .htaccess can process them. – Phil Perry Jan 28 '14 at 22:22
  • To remove the trailing from URLs that don't point to a directory , you can use this solution https://helponnet.com/2020/02/20/how-to-remove-traling-slashes-from-urls-using-rewriterule-url-rewriting-tips/ – Amit Verma Nov 11 '21 at 17:53

4 Answers4

224

Right below the RewriteEngine On line, add:

RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)/$ /$1 [L,R] # <- for test, for prod use [L,R=301]

to enforce a no-trailing-slash policy.

To enforce a trailing-slash policy:

RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*[^/])$ /$1/ [L,R] # <- for test, for prod use [L,R=301]

EDIT: commented the R=301 parts because, as explained in a comment:

Be careful with that R=301! Having it there makes many browsers cache the .htaccess-file indefinitely: It somehow becomes irreversible if you can't clear the browser-cache on all machines that opened it. When testing, better go with simple R or R=302

After you've completed your tests, you can use R=301.

Andrea Ligios
  • 49,480
  • 26
  • 114
  • 243
Jon Lin
  • 142,182
  • 29
  • 220
  • 220
  • 4
    God I could kiss you! Worked with enforce a no-trailing-slash the other one gave me 500 server error Thank you Jon Lin! – Luke Jan 28 '14 at 21:59
  • 28
    The no-trailing-slash rule is not working if the website is located in a directory (like example.org/blog/) – Gras Double Dec 03 '14 at 02:59
  • 10
    Be careful with that `R=301`! Having it there makes many browsers cache the .htaccess-file indefinitely: It somehow becomes irreversible if you can't clear the browser-cache on all machines that opened it. When testing, better go with simple "R" or "R=302" – Jonathan Weber Sep 22 '16 at 18:33
  • When VirtualDocumentRoot is off, remove the slash at the beginning, ie.: `$1` instead of `/$1` And, @JonathanWeber , the .htaccess file is not served, but the expires header tells the browsers how long to cache a resource. – twicejr Feb 22 '17 at 18:37
  • Is there any reason why you put the conditions in negative? I mean, why !d and not just f ? – Nrc Apr 29 '17 at 15:20
  • 1
    @Nrc because there are other types of mappings besides files and directories, and there is also the chance that a request maps to neither a file nor directory but needs to be rewritten. The negative ensures that something that has either been rewritten or already maps to something in the filesystem is skipped. – Jon Lin Apr 30 '17 at 00:00
  • @JonLin ,I tried adding this to .htaccess for no-trailing-slash but still the site opens at www.my-domian.com/en/controller/ . EDIT: I fixed it that was because there was some other rules in my .htaccess before the rule for no-trailing-slash – Mohammed Abrar Ahmed Dec 25 '17 at 14:38
  • @GrasDouble I also face this issue. I am working on a site locally and i also needed this because of my MVC. Because i am working in XAMPP i just rewrote the line: `RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(.*)/$ /mva/$1 [L,R] # <- for test, for prod use [L,R=301]` This works also fine for me. When i set my site on a live location i just remove the directory. Easy as that. – Gobbin Mar 22 '18 at 15:03
  • Not sure if this deserves its own question, but in the case of going from slash to no-slash, is there a way to keep the slash visible but have the code somehow pretend it isn't there? (Would I need to use the JS History API?) – Agamemnus Sep 04 '18 at 06:53
  • 1
    How do you add exceptions to the above rule? I.e. add trailing slash to all apart from /foo/bar? – Unbranded Manchester May 07 '20 at 10:42
  • Thank you, this is exactly what the TYPO3 .htaccess example is missing <3 – Naderio Jun 24 '20 at 06:19
102

To complement Jon Lin's answer, here is a no-trailing-slash technique that also works if the website is located in a directory (like example.org/blog/):

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [R=301,L]


For the sake of completeness, here is an alternative emphasizing that REQUEST_URI starts with a slash (at least in .htaccess files):

RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} /(.*)/$
RewriteRule ^ /%1 [R=301,L] <-- added slash here too, don't forget it

Just don't use %{REQUEST_URI} (.*)/$. Because in the root directory REQUEST_URI equals /, the leading slash, and it would be misinterpreted as a trailing slash.


If you are interested in more reading:

(update: this technique is now implemented in Laravel 5.5)

Gras Double
  • 15,901
  • 8
  • 56
  • 54
  • 2
    This particular variant is really useful, especially when using modern URL conventions. Hope you don't mind, I'm submitting it to the [htaccess snippets repository](https://github.com/phanan/htaccess). – alexw Mar 30 '15 at 04:54
  • 2
    This eliminates the problem when REQUEST_URI (root) is interpreted as a trailing slash also causing an infinite loop. Thank you – Ruslan Abuzant Feb 28 '16 at 17:26
  • 1
    Yes, accepted answer do not hande if website is located in a directory, but this answer with `RewriteCond %{REQUEST_URI} (.+)/$` removes just one last trailing slash (e.g. several redirects required from sth/// to sth// to sth/ to sth). To remove all trailing slashes in one redirection use better solution `RewriteCond %{REQUEST_URI} (.+?)/+$` – mikep May 11 '17 at 18:01
  • 1
    I had thought about this too. Though, the result is correct, and considering it's an edge case, I preferred code simplicity. – Gras Double May 13 '17 at 16:22
  • @GrasDouble , thank you having been looking for this solution for a long time. tried countless methods but your worked fine! a big thank you. –  Jul 20 '17 at 19:38
  • I wanted to put this is my laravel htaccess... then I found its there by default. – Adam Nov 15 '17 at 13:34
  • Indeed, it has been added to Laravel… finally! Refs https://github.com/laravel/laravel/pull/4344. Thanks for the notice :) – Gras Double Nov 16 '17 at 10:09
  • This is a better solution, because it depends on the relative path, and this works if the website is a part of another site – Mohamed hesham Nov 11 '18 at 01:51
  • I agree. This works on localhost while the accepted answer does not. – Jens Törnell May 09 '19 at 11:12
  • Could you add a version with a force trailing slash policy? – kapoko Oct 20 '19 at 23:40
  • @kapoko You may give this a try: [Force Trailing Slash](https://github.com/phanan/htaccess#force-trailing-slash), though I haven't tested it personally. – Gras Double Oct 21 '19 at 02:18
  • 2
    Tried that and it works for the frontend but unfortunately I run into another problem: got several Wordpress installations in subfolders: `example.com/wp1` `example.com/wp2` etc. and for those installations saving stuff in Wordpress is suddenly broken (POST requests somehow didn't land correctly). While writing this comment I figured it out: adding `RewriteCond %{REQUEST_METHOD} !=POST` to the rule worked for me, trailing slash is added and Wordpress is not broken. – kapoko Oct 22 '19 at 07:38
  • Perfect and simple. Good solution. – Art Geigel May 19 '20 at 05:22
  • Great answer. And the `RewriteCond %{REQUEST_METHOD} !=POST` was also helpful. Thank you all! – Avatar Nov 11 '22 at 12:36
4

This is what I've used for my latest app.

# redirect the main page to landing
##RedirectMatch 302 ^/$ /landing

# remove php ext from url
# https://stackoverflow.com/questions/4026021/remove-php-extension-with-htaccess
RewriteEngine on 

# File exists but has a trailing slash
# https://stackoverflow.com/questions/21417263/htaccess-add-remove-trailing-slash-from-url
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^/?(.*)/+$ /$1 [R=302,L,QSA]

# ok. It will still find the file but relative assets won't load
# e.g. page: /landing/  -> assets/js/main.js/main
# that's we have the rules above.
RewriteCond %{REQUEST_FILENAME} !\.php
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME}\.php -f 
RewriteRule ^/?(.*?)/?$ $1.php
Daniel Dewhurst
  • 2,533
  • 2
  • 21
  • 39
Svetoslav Marinov
  • 1,498
  • 14
  • 11
1
Options +FollowSymLinks
RewriteEngine On
RewriteBase /
## hide .html extension
# To externally redirect /dir/foo.html to /dir/foo
RewriteCond %{THE_REQUEST} ^[A-Z]{3,}\s([^.]+).html
RewriteRule ^ %1 [R=301,L]

RewriteCond %{THE_REQUEST} ^[A-Z]{3,}\s([^.]+)/\s
RewriteRule ^ %1 [R=301,L]

## To internally redirect /dir/foo to /dir/foo.html
RewriteCond %{REQUEST_FILENAME}.html -f
RewriteRule ^([^\.]+)$ $1.html [L]


<Files ~"^.*\.([Hh][Tt][Aa])">
order allow,deny
deny from all
satisfy all
</Files>

This removes html code or php if you supplement it. Allows you to add trailing slash and it come up as well as the url without the trailing slash all bypassing the 404 code. Plus a little added security.

  • Hi, How would you write this for nginx? I tried to run it in a converter here: https://winginx.com/en/htaccess but this result gives me infinite loop: ``# nginx configuration location / { rewrite ^(.*)$ /%1 redirect; rewrite ^(.*)$ /%1 redirect; rewrite ^/([^\.]+)$ /$1.html break; } location ~ ^.*\.([Hh][Tt][Aa]) { deny all; }`` – Asle Jan 10 '17 at 22:12