Ghost and 301 Redirects Using Nginx

Ghost 404 Error

This is part seven in a series of blog posts on migrating a Wordpress blog to Ghost.

One of the problems I ran into when migrating my Wordpress blog to Ghost, was the difference in how both blogging engines approach URLs. Wordpress supports several URL structures. For instance, in Wordpress you can create a category before the slug. For example, here is one of my old Wordpress URLs: This Wordpress URL becomes an issue when all of your webpages have been indexed by Google and other search engines with it. All of the blog posts to your site will get a 404 error in Ghost because the page will not exist.

Since I am using Nginx as a webserver in front of Ghost, the solution is to do 301 redirects. A 301 redirect tells a user's browser that a URL has permanently moved to a new location and then provides the browser with the new address. In Nginx you can do that within the location section of your nginx.conf file. On Ubuntu the file is usually found under /etc/nginx/nginx.conf. Here is an example from mine:

location / {  
  rewrite ^/product-review/?(.*)$ /$1 permanent;
  rewrite ^/productivity/?(.*)$ /$1 permanent;
  rewrite ^/technology/?(.*)$ /$1 permanent;

Make sure you restart Nginx so that it will read the changes you made to your configuration file. For me that means doing this:

sudo service nginx restart  

Notice that if you put the URL in your browser, it will get redirected to And with curl you can test to see if all of the header info is correct:

curl -I -L

HTTP/1.1 301 Moved Permanently  
Server: nginx/1.1.19  
Date: Fri, 04 Apr 2014 16:39:01 GMT  
Content-Type: text/html  
Content-Length: 185  
Connection: keep-alive

HTTP/1.1 200 OK  
Server: nginx/1.1.19  
Date: Fri, 04 Apr 2014 16:39:01 GMT  
Content-Type: text/html; charset=utf-8  
Content-Length: 9532  
Connection: keep-alive  
Vary: Accept-Encoding  
X-Powered-By: Express  
Cache-Control: public, max-age=0  
ETag: "-1059492535"  

So now you know how to redirect users with old URLs to your new Ghost URLs.

But why stop there? Let's go ahead and protect the signup page from prying eyes as well like so:

location ~ ^/(ghost/signup/) {  
    rewrite ^/(.*)$ permanent;

Don't forget to restart Nginx.