I need to remove trailing slash from URLs ending with .xml/ only .. For this purpose I've created a Rewrite Condition and Rule which is working perfectly fine for the test link http://website.com/test.xml/

Test Link: http://htaccess.mwl.be?share=6fe08232-438a-53fa-8f1a-1f7f69b77b6f

The problem is when I place the rule in WordPress .htaccess file, it doesn't work at all! Seems like WordPress or YOAST Permalink structure is overriding the rule .. please help!

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_URI} /(.*).xml/$
RewriteRule ^ /%1.xml [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
# END WordPress


Please note that this is not a physical file .. using rewrite rules to generate sitemap on the run! This is a wordpress page in fact!


RewriteRule ^ /%1.xml [L]

This should be an external redirect, not an internal rewrite. (Otherwise it won't look like it's doing anything, as the visible URL won't change.) So, you need the R (redirect) flag. For example:

 RewriteRule ^ /%1.xml [R,L]

This is a temporary (302) redirect. Change R to R=301 to make it a permanent (301) redirect - but only when you are sure it's working OK. (Since 301s are cached hard by the browser and can make testing problematic.)

However, you don't need the RewriteCond directive. This can be done in a single RewriteRule (it will also be more efficient to do so). This should also go before your existing internal rewrites and ideally before the # BEGIN WordPress block, as otherwise your customisation could be overridden in future updates.

So, all you need is (at the top of your file):

RewriteRule ^(.+)\.xml/$ /$1.xml [R,L]

Remember to escape literal dots in your regex.

UPDATE: And yes, that should be $1 (as a backreference to the RewriteRule pattern) as opposed to %1 (a backreference to the last matched CondPattern).

  • 1
    Is it RewriteRule ^(.+)\.xml/$ /$1.xml [R,L] .. $1.xml instead of %1.xml? – Hassan Alvi Dec 27 '17 at 13:02
  • It's giving an error ... ERR_TOO_MANY_REDIRECTS When I use custom Permalink Structure /%postname% it works fine.. but /%postname%/ is required .. so I guess WordPress permalink structure is overriding the rule somehow! – Hassan Alvi Dec 27 '17 at 13:05
  • Sorry, yes - $1 instead of %1 in the updated rule. I've fixed that in my answer. – MrWhite Dec 27 '17 at 13:05
  • Please help out over redirect issue .. this was there before as well! – Hassan Alvi Dec 27 '17 at 13:07
  • Does this .xml URL not map to a physical file? It is generated by WordPress? What "redirects" do you see in the network traffic? – MrWhite Dec 27 '17 at 13:10

If you're outputting a sitemap, there's no reason to wait for the query for your page- which is what is producing the redirect.

Hook an earlier action and you won't need anything to counter the trailing slash, because it won't happen-


Here's a complete version with registering query vars, rules, and parse_request action:

// add the custom query vars
// WordPress will not parse query vars in rules
// if they are not added to the list of valid query vars
function wpd_sitemap_query_var( $vars ){
    $vars[] = 'svc-slug';
    $vars[] = 'svc-offset';
    return $vars;
add_filter( 'query_vars', 'wpd_sitemap_query_var' );

// add the rules
function wpd_sitemap_rewrite_rules() {
add_action('init', 'wpd_sitemap_rewrite_rules', 10, 0);

// intercept the query and test if our query var is set
// this means one of our rules matched and we should show the sitemap
function wpd_sitemap_parse_request( $query ){
    if( isset( $query->query_vars['svc-slug'] ) ){
        echo '<pre>';
        print_r( $query->query_vars );
        echo '</pre>';

        // halt further execution after sitemap is output
add_action( 'parse_request', 'wpd_sitemap_parse_request' );
  • The sitemap being generated is for dynamic pages which don't exist at all! I need to override WordPress's permalink settings from /%postname%/ to /%postname% .. – Hassan Alvi Dec 28 '17 at 9:04
  • I understand what your problem is and this solves it, have you tested it? This works in conjunction with your rule, to intercept and display your site maps without the trailing slash redirect. You must also be sure to register custom-slug query var with the query vars filter. – Milo Dec 28 '17 at 15:24
  • Tried it .. it didn't work! – Hassan Alvi Dec 29 '17 at 13:31
  • Tried what? Did you remove the code from .htaccess? This does not use htaccess. Did you add the custom-slug query var with the query_vars filter? If you simply look at the contents of $query within the parse_request filter, you will see your query var appear whenever your rule is matched, does this not occur? Again- I do this quite regularly for exactly this, it is tested and working with current WP version. – Milo Dec 29 '17 at 16:23
  • Yes, I removed the code from .htaccess and also used the custom slug for the selected page template .. it didn't work out for me! function my_site_map( $query ){ if( isset( $query->query_vars['svc-sitemap'] ) ){ echo 'output headers and sitemap here'; die; } } add_action( 'parse_request', 'my_site_map' ); – Hassan Alvi Jan 4 '18 at 8:39

What exactly worked out for me ..

The issue was due to WordPress canonical redirects .. The use of /%postname%/ in permalink structure was redirecting the custom template to the trailing slash URL.

I simply removed the template_redirect filter for canonical redirects .. and now the link works in both cases i.e. with or without trailing slash.

Here is my code for reference.. 1234 is the example page id for the template used to generate dynamic sitemap.

//Disabling canonical redirects for sitemap function disable_sitemap_redirect() { global $wp; $current_url = home_url(add_query_arg(array(),$wp->request)); $current_url = $current_url . $_SERVER['REDIRECT_URL']; $id = url_to_postid($current_url); if($id == 1234) : remove_filter('template_redirect', 'redirect_canonical'); endif; } add_action('init', 'disable_sitemap_redirect', 10, 0);

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.