<p><a href="https://github.com/nystudio107/nginx-craft" rel="noreferrer">Nginx-Craft</a> has an example setup that does this:</p>

<p><a href="https://github.com/nystudio107/nginx-craft/blob/master/sites-available/somedomain.com.conf#L60" rel="noreferrer">https://github.com/nystudio107/nginx-craft/blob/master/sites-available/somedomain.com.conf#L60</a></p>

<pre><code># 301 Redirect URLs with trailing /'s as per https://webmasters.googleblog.com/2010/04/to-slash-or-not-to-slash.html
rewrite ^/(.*)/$ /$1 permanent;
</code></pre>

<p>This is working on the nystudio107.com site, if you go here:</p>

<p><a href="https://nystudio107.com/blog/" rel="noreferrer">https://nystudio107.com/blog/</a></p>

<p>...the redirected URL will be this:</p>

<p><a href="https://nystudio107.com/blog" rel="noreferrer">https://nystudio107.com/blog</a></p>

<p>Or am I missing something?</p>

<pre><code>andrew@kotak ~ $ curl -I https://nystudio107.com/blog/
HTTP/1.1 301 Moved Permanently
Server: nginx/1.13.6
Date: Thu, 15 Feb 2018 19:39:49 GMT
Content-Type: text/html
Location: https://nystudio107.com/blog
Connection: keep-alive
Set-Cookie: abtest=green;Path=/;Max-Age=86400;secure; SameSite=strict
Strict-Transport-Security: max-age=15768000; includeSubDomains; preload
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer-when-downgrade
</code></pre>

CC BY-SA 3.0
2018-02-15T19:50:52.120Z
Ah! I totally misread that in the example. I'm trying to add trailing slashes, not remove them. Fast response! I've also amended the question/answer. The one thing I still run into is query strings on slashed paths, ie domain.com/path?query=string has issues, whereas domain.com/path/?query=string is fine. Any insight?
hisnameisjimmy
CC BY-SA 3.0
2018-02-15T23:29:39.420Z
Nvm, figured it out!
hisnameisjimmy
CC BY-SA 3.0
2018-02-15T23:56:22.707Z
Only heretics add slashes. We remove them 
andrew.welch
CC BY-SA 3.0
2018-02-15T19:37:28.583Z
2018-02-15T19:37:28.583Z
andrew.welch
<p>After some digging, there appear to be two ways that avoid hitting the admin and url query strings.</p>

<p>The first, (and apparently better one, as '<a href="https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil/" rel="nofollow noreferrer">if is evil</a>' according to nginx) uses location handlers:</p>

<pre><code># 301 Redirect for trailing slash
location ~ ^([^.\?]*[^/])$ {
  try_files $uri @addslash;
}

# 301 Redirect for trailing slash
location @addslash {
  return 301 $uri/$is_args$args;
}

# Root directory location handler
location / {
    try_files $uri/index.html $uri $uri/ /index.php?$query_string;
}
</code></pre>

<p>The second, more evil one that uses an if statement:</p>

<pre><code>if ($request_uri !~ "^/admin")
{
    rewrite ^([^.\?]*[^/])$ $1/$is_args$args permanent;
}
</code></pre>

<p>I included the context of the root location handler in the first solution so that you can combine it with your existing root location handling setup. I'm currently leaning on a lot of NYStudio's work (a truly incredible contributor to the open source ecosystem of Craft) with FastCGI, location handlers for foreign languages, etc and both of these solutions work with it. </p>

<p>The first also works with query strings, if a user visits a URL either with or without a trailing slash. For example:</p>

<ul>
<li>This: www.domain.com/page?string=234</li>
<li>Will go to this: www.domain.com/page/?string=234</li>
<li>And this: www.domain.com/page</li>
<li>Will go to this: www.domain.com/page/</li>
</ul>

<p>Interestingly, if you use query_string instead of is_args for the return url, it will rewrite it wrong. Only is_args worked in my experience. </p>

CC BY-SA 3.0
2018-02-15T19:27:16.200Z
2018-02-15T23:23:34.967Z
2018-02-15T23:23:34.967Z
hisnameisjimmy
<p>What's the best way to redirect to a trailing slash in Nginx with Craft CMS? I've seen some advice for Apache .htaccess, etc, but not Nginx. For those curious, here's why I might want to do this: <a href="https://webmasters.googleblog.com/2010/04/to-slash-or-not-to-slash.html" rel="nofollow noreferrer">To slash or not to slash</a>.</p>

<p><a href="https://nystudio107.com/" rel="nofollow noreferrer">NYStudio107</a> <a href="https://github.com/nystudio107/nginx-craft" rel="nofollow noreferrer">recommendations</a> advise removing the trailing slash, but since we were formerly on Wordpress and had a trailing slash to begin with, I want to be consistent with the past history in Google Analytics.</p>

<redirect><nginx>
Trailing slashes with Nginx and Craft
# Trailing slashes with Nginx and Craft

(Asked by: username_0 on 2018-02-15)

What's the best way to redirect to a trailing slash in Nginx with Craft CMS? I've seen some advice for Apache .htaccess, etc, but not Nginx. For those curious, here's why I might want to do this: [To slash or not to slash](https://webmasters.googleblog.com/2010/04/to-slash-or-not-to-slash.html).

[NYStudio107](https://nystudio107.com/) [recommendations](https://github.com/nystudio107/nginx-craft) advise removing the trailing slash, but since we were formerly on Wordpress and had a trailing slash to begin with, I want to be consistent with the past history in Google Analytics.

**Tags:** redirect, nginx

---

## 2 answers

### Answer by username_1 on 2018-02-15. Score: 5

Nginx-Craft has an example setup that does this:
https://github.com/nystudio107/nginx-craft/blob/master/sites-available/somedomain.com.conf#L60
# 301 Redirect URLs with trailing /'s as per https://webmasters.googleblog.com/2010/04/to-slash-or-not-to-slash.html
rewrite ^/(.*)/$ /$1 permanent;

This is working on the nystudio107.com site, if you go here:
https://nystudio107.com/blog/
...the redirected URL will be this:
https://nystudio107.com/blog
Or am I missing something?
andrew@kotak ~ $ curl -I https://nystudio107.com/blog/
HTTP/1.1 301 Moved Permanently
Server: nginx/1.13.6
Date: Thu, 15 Feb 2018 19:39:49 GMT
Content-Type: text/html
Location: https://nystudio107.com/blog
Connection: keep-alive
Set-Cookie: abtest=green;Path=/;Max-Age=86400;secure; SameSite=strict
Strict-Transport-Security: max-age=15768000; includeSubDomains; preload
X-Frame-Options: SAMEORIGIN
X-XSS-Protection: 1; mode=block
X-Content-Type-Options: nosniff
Referrer-Policy: no-referrer-when-downgrade

> **Comments:**
> **username_0**: Ah! I totally misread that in the example. I'm trying to add trailing slashes, not remove them. Fast response! I've also amended the question/answer. The one thing I still run into is query strings on slashed paths, ie domain.com/path?query=string has issues, whereas domain.com/path/?query=string is fine. Any insight?  
> **username_0**: Nvm, figured it out!  
> **username_1**: Only heretics add slashes. We remove them   

---

### Answer by username_0 on 2018-02-15. Score: 4

After some digging, there appear to be two ways that avoid hitting the admin and url query strings.
The first, (and apparently better one, as 'if is evil' according to nginx) uses location handlers:
# 301 Redirect for trailing slash
location ~ ^([^.\?]*[^/])$ {
  try_files $uri @addslash;
}

# 301 Redirect for trailing slash
location @addslash {
  return 301 $uri/$is_args$args;
}

# Root directory location handler
location / {
    try_files $uri/index.html $uri $uri/ /index.php?$query_string;
}

The second, more evil one that uses an if statement:
if ($request_uri !~ "^/admin")
{
    rewrite ^([^.\?]*[^/])$ $1/$is_args$args permanent;
}

I included the context of the root location handler in the first solution so that you can combine it with your existing root location handling setup. I'm currently leaning on a lot of NYStudio's work (a truly incredible contributor to the open source ecosystem of Craft) with FastCGI, location handlers for foreign languages, etc and both of these solutions work with it. 
The first also works with query strings, if a user visits a URL either with or without a trailing slash. For example:

This: www.domain.com/page?string=234
Will go to this: www.domain.com/page/?string=234
And this: www.domain.com/page
Will go to this: www.domain.com/page/

Interestingly, if you use query_string instead of is_args for the return url, it will rewrite it wrong. Only is_args worked in my experience.


---

