Apache rewrite rule for, Query String replace / value with _

Source URL:


rewrite to below


opath value has / and this should be replaced with _, then this value is added as selector in request URI.

I tried below, but not working

RewriteCond "%{QUERY_STRING}" ^opath=(.*)&appId=(.*)
RewriteRule ^/my/service(.*) /my/service.%1.html?%{QUERY_STRING} [NE,NC,PT]



Please advise on how to change /content/home/page to _content_home_page. Need to replace / with _.

opath value is dynamic and has no restrictions, it can be anything opath=/a/b or /ab/a/abc/abz..

  • Is the path /content/home/page always in the same format? ie. 3 path segments: /path1/path2/path3 (slash prefix, no-slash suffix)? Or what are the restrictions here?
    – MrWhite
    May 6, 2020 at 10:32
  • no restrictions on the path , it can have any number of segments, always with a slash prefix.
    – Vamshi M
    May 6, 2020 at 11:04
  • Why are you using rewrite to convert one ugly URL into another ugly URL? Usually you would try to rewrite something like http://example.com/content/home/page. May 6, 2020 at 20:09
  • 1
    Thats a legacy application and already in place and we cant change that, and we are now applying caching strategy based on selector. Could you advise on my rewrite rule, what am i missing to change / into _
    – Vamshi M
    May 7, 2020 at 3:44

1 Answer 1


From your directives I assume you are working directly in the main server config or <VirtualHost> container (as opposed to a <Directory> or .htaccess context).

no restrictions on the path , it can have any number of segments

This slightly complicates matters as there is no "simple" way to replace arbitrary characters in the URL without using a recursive loop to replace each instance of the character using a selective regex.

Try something like the following in your vHost:

# 1. Copy URL parameter value to URL-path and replace the first slash with underscore
RewriteCond %{QUERY_STRING} ^opath=/([^&]*)&appId=
RewriteRule ^/my/service$ $0._%1.html

# 2. Replace subsequent slashes with underscores in copied value in the URL-path
# Query string is passed through unaltered
RewriteRule ^(/my/service\._.*)/(.*\.html)$ $1_$2 [N=20]

UPDATE: $0, $1 and $2 are backreferences to the captured subgroups (parenthesized subpatterns) in the RewriteRule pattern. $0 represents the whole match.

Since you are keeping the query string unaltered there is nothing special you need to do in this regard, since the query string is passed through by default (providing you don't state a query string in the substitution string).

The first rule rewrites the initial request to copy the URL parameter value to the URL-path, replacing just the first slash. From example, given an initial request of the form:


Gets rewritten to:


The next rule then rewrites this rewritten URL further and recursively replaces each slash in the second part of the URL-path with an underscore. The N (next) flag forces the rewrite engine to loop until all the slashes are replaced (at which point the rule fails to match and processing continues). Note that in this case the rewrite engine effectively starts over, so the order of directives is important.

The 20 in N=20 (requires Apache 2.4.8+) is a fail-safe and represents, in this scenario, an upper limit for the number of slashes that might need to be replaced. The N flag can be a bit tricky - if done incorrectly (with no option to fail) it can quickly run out of control and literally bring Apache to a grinding halt (before crashing). By default, it will fail after 32,000 iterations - but that is often too much for a server to handle!

Rewriting continues to:




Depending on whether /my/service._content_home_page.html represents a physical filesystem path or virtual path that is further rewritten, you may still have issues with path-info, so you might need the DPI (Discard Path Info) flag on the second rule.

UPDATE: ...help me understand in this rule, how does the replace of forward slash to underscore happen RewriteRule ^(/my/service\._.*)/(.*\.html)$ $1_$2

This rule only replaces one slash at once. It is called repeatedly to replace all slashes in the URL-path.

Note that the RewriteRule pattern (ie. ^(/my/service\._.*)/(.*\.html)$) only matches against the URL-path, so we can ignore the query string for now. (The query string is automatically appended later.)

The regex ^(/my/service\._.*)/(.*\.html)$ is broken down as follows:

  • (/my/service\._.*) - this captures the URL-path up to (but not including) the last slash in the URL-path. The pattern .* is "greedy", so it matches up to the last slash, not the first. This is saved in the $1 backreference. eg. Given the URL-path /my/service._content/home/page.html (after the first rewrite), this matches /my/service._content/home and saves this in $1.

  • / - this simply matches a slash (and effectively throws it away).

  • (.*\.html) - this captures the remaining URL-path after (but not including) the last slash and saves it in the $2 backreference. eg. Given the URL-path /my/service._content/home/page.html (after the first rewrite), this matches page.html and saves this in $2.

The URL is then internally rewritten to $1_$2 - the contents of the $1 and $2 backreferences (as mentioned above) joined together with an underscore (where a slash once was). For example, from the above example, this results in the URL-path /my/service._content/home + _ + page.html, ie. /my/service._content/home_page.html.

The N flag causes the rewritten URL to be immediately passed back to the rewrite engine for further processing - to replace the remaining slashes.

  • Thank you so much @MrWhite. you're awesome. this solution is working and very clearly explained. I'm not an expert and learning, could you please help me understand in this rule, how does the replace of forward slash to underscore happen RewriteRule ^(/my/service\._.*)/(.*\.html)$ $1_$2
    – Vamshi M
    May 7, 2020 at 17:19
  • You're welcome. I've updated my answer with further explanation regarding that rule.
    – MrWhite
    May 7, 2020 at 18:01
  • If this answered your question then please mark it as "accepted" by clicking the tick/checkmark next to the answer on the left below the voting arrows (to help other users and remove the question from the unanswered question queue). Thanks, much appreciated. :)
    – MrWhite
    May 13, 2020 at 16:37

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.