> ## Content Index
> Fetch the complete content index at: https://www.ctrl.blog/llms.txt
> Use this file to discover other available public pages before exploring further.

# Avoid Nginx’s merge_slashes option
- URL: https://www.ctrl.blog/entry/nginx-requesturi-proxycache/
- Published: 2018-08-08T16:02:00.000Z
- Updated: 2026-08-23T22:14:50.000Z
- Description: Don’t use Nginx’s merge_slashes option in your HTTP reverse caching proxy setup. Here’s an example of an unintended problem caused by blindly rewriting URLs.
- Author: Daniel Aleksandersen
- Tags: HTTP Caching, Hypertext

This story starts with two seemingly similar but distinctly different URLs: `//document` and `/document`. They’re semantically two distinct and unique URLs (RFC 386) and programs can’t assume they can be normalized by removing the “extra” forward slash. Extra slashes can sneak into URLs unintentionally from all sorts of websites and external software, so it can be tempting to attempt to combine seemingly redundant slashes into a single slash.

You can only safely [normalize and redirect duplicate slashes](https://www.ctrl.blog/entry/relative-double-slashes-url/) in situations where you know that there shouldn’t ever be any duplicate slashes in your URL design and planning. However, caching reverse proxies and content delivery networks (CDN) can’t arbitrarily decide to merge slashes without breaking expectations of the web platform.

But I’m getting ahead of myself. Before going into that in more details, I first need to establish some internal workings of the Nginx web server.

### Two variables

The first variable of note is `$request_uri` which contains the original URL as requested by the client including the path and query arguments. `$uri$is_args$args` are three separate variables that in combination recreates the same information that make up the `$request_uri` variable.

When an Nginx caching reverse proxy receives a request, it will lookup the URL in the cache based on the `$request_uri` variable (the cache key) and serve the client a cached copy of the resource if one exist. If the resource can’t be served from the cache, Nginx will pass on the `$request_uri` to the origin server and save the response in its cache and serve it to the client. These behaviors can be changed by modifying the `proxy_cache_key` or `proxy_pass` options.

The Nginx web server has an option called `merge_slashes` (on by default) that compresses duplicated forward slashes into a single forward slash. In practice this rewrites the internal `$uri` variable so that, e.g. `/hello///world` will become `/hello/world`.

Notably, it doesn’t trigger a client redirect which is the method I prefer, see the referenced article above. The original incoming URL is preserved in the `$request_uri` variable which remains unchanged. There are multiple other ways to rewrite the `$uri` variable, but this one option will serve as a stand-in for all of them in this article.

By default, even with the `merge_slashes` option enabled, Nginx will not modify the request URL as used for caching or the URL sent through the proxy nor the response coming back through from the origin server. So far, everything is humming along nicely.

### Mixing up the variables

A common strategy for improving a caching proxy’s cache hit ratio, the ratio at which a request can find a match to be served from the cache, is to remove and essentially ignore the query parameters from the URL. Meaning that instead of looking up results in the cache based on the full URL, only the path is used to evaluate cache hits (the `$uri` variable sans the `$is_args$args` variables in Nginx parlor.) This strategy is achieved by changing the `proxy_cache_key` **and** the `proxy_pass` option to `$uri`.

However, if you don’t change both options then the origin server and the cache will disagree on which URL is being served. Say we’ve enabled the `merge_slashes` option and only modified the `proxy_cache_key` option. If a user then requests `//document`, the proxy will look in its cache for `/document` and if it hasn’t been cached already, it will ask the origin for `//document`.

The origin will handle the request for `//document` and send it back to the proxy server which will store it as `/document` in its cache and serve it to the user. Any future requests for `/document` will get the same resource.

So, where does this go wrong? Well, if the origin server handles `//document` and `/document` differently (which is to be expected as they’re distinctly different URLs), then the proxy server will begin serving the wrong resource to any subsequent users that request `/document`. This may be okay in some situations but in general, it will cause issues. Let us look at a more concrete example.

### BunnyCDN and the infinite redirect

The broken setup in the above description is essentially what you’ll find at [BunnyCDN](https://bunny.net/?ref=ctrl.blog), the winner of my [budget CDN comparison](https://www.ctrl.blog/entry/budget-cdn-review/). They rewrote the URLs they use for their cache keys without informing the origin server that the URLs had been normalized in a non-standard way.

In my case, [my own normalization](https://www.ctrl.blog/entry/relative-double-slashes-url/) would result in a infinite redirect loop. Given my own knowledge of my URL structure I could work out where the visitors meant to go and send them to the right location. For example, turning `//document` to `/document`.

However, BunnyCDN would cache the redirect on `//document` as `/document`; causing an infinite redirect loop as `/document` would then serve a redirect to itself forever. Every visitor coming to the correct URL would also be served the cached version and stuck in a redirect loop. As discussed earlier, [redirect loops with HTTP/2 server-push](https://www.ctrl.blog/entry/http2-push-redirects/) can get pretty bad for some clients.

BunnyCDN have been positive to change their ways but hesitant to implement the changes. So, I’ve had to implement a work-around involving making all redirects from URLs containing `//` non-cachable. This reduces page load speeds where the cache would normally handle redirects.

However, this workaround should never have been necessary to implement. There are well established standards for how proxy servers should behave and BunnyCDN simply isn’t living up to those expectations in this instance.

### Take away lessons

- Don’t remove duplicate slashes in URLs other than your. Extra slashes are distinctly different addresses even though some servers might serve the same content in some situations. It’s a destructive and non-standard normalization operation.
- When rewriting URLs, be sure either do it consistently and apply the rewritten URLs everywhere or simply don’t do it at all.
- Chose service providers and software that deliver on web standards. You’ll save yourself having to debug weird and unexpected problems.

#### Sources

- [Module ngx\_http\_core\_module](https://nginx.org/en/docs/http/ngx%5Fhttp%5Fcore%5Fmodule.html?ref=ctrl.blog), version 1.14, 2018-04-17, Nginx Documentation, Nginx
- [Module ngx\_http\_proxy\_module](https://nginx.org/en/docs/http/ngx%5Fhttp%5Fproxy%5Fmodule.html?ref=ctrl.blog), version 1.14, 2018-04-17, Nginx Documentation, Nginx
- [RFC 3986: Uniform Resource Identifier (URI): Generic Syntax](https://www.rfc-editor.org/rfc/rfc3986.html?ref=ctrl.blog), 2005-01, Network Working Group, Internet Engineering Task Force