Hosting Two Ghost Blogs Under One Domain with Nginx

0x00 Requirements

Today, I want to host 2 different blogs on the same server under the same domain (xhyumiracle.com). It is extremely easy if you can endure accessing blogs with port number, like xhyumiracle.com:8000.

Apparently I can't, which means I want to access those two blogs by xhyumiracle.com and xhyumiracle.com/test. So basically, this is the core requirement.

This post includes the main process of how I solve the problem and what learned from it that.

0x01 Nginx Overview (ref)

  • installation: $ sudo apt-get install nginx -y
  • start/stop nginx: $/etc/init.d/nginx start|stop|restart
  • new configuration files can be added into /etc/nginx/conf.d/ (CentOS) or /etc/nginx/sites-enabled (Ubuntu).
  • if you find default sample Nginx configuration files under directories mentioned above, I would recommend deleting them.
rm /etc/nginx/sites-available/default
rm /etc/nginx/sites-enabled/default
rm /etc/nginx/conf.d/default
  • you can create configuration files like your-domain-name.conf
  • then place the following into your file:
server {
    listen 80;
    server_name your-domain-name.com;
    location / {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host      $http_host;
        proxy_pass         http://127.0.0.1:2368;
    }
}
  • Now restart Nginx to make your changes take effect: $ /etc/init.d/nginx restart, nginx should forward all the requests from port 80 to port 2368.

0x02 Access the Second Blog by /test

  • With the stuff mentioned above, it is easy to access a blog running on port 8000 by typing xhyumiracle.com (from port 80).
  • But it's a little bit different when access another blog by typing xhyumiracle.com/test.
  • First, we try to modify the location /:
server {
    listen 80;
    server_name xhyumiracle.com;
    location /test {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host      $http_host;
        proxy_pass         http://127.0.0.1:128;
    }
}
  • In this way, Nginx did transfer the request to the port 128, but the '/test' in the URI was also forwarded to ghost running on port 128 and ghost didn't know what does '/test' mean, so we have to remove it before handing it to ghost.

0x03 Nginx Rewrite

  • Goal: To remove the '/test' from the URI.
  • Syntax: rewrite regex replacement [flag]. flags: last|break|redirect|permanent
  • So just add one line before those proxy_set_header lines: rewrite ^/test(.*) /$1 break
  • In this way, ghost would be able to parse the URL forwarded by nginx correctly.
  • But if you open your blog in the browser, you might see a plain text page without any css or image loaded.
  • This is because the requests (including images, css, etc.) caused by index page are not start with /test, so nginx cannot forward them to port 128, so always lost them.
  • Looks like we need to forward requests to 128 even without /test prefix.

0x04 $http_referer

  • There are several ways to achieve that, you can modify the ghost core code to accept '/test' prefix, or prepend 'test' to every URL, or do things from the nginx side. Nginx way is better, so we're going to do it.
  • Recalling that there is a Referrer field in the headers of requests to record the site who generated them originally, we can use it to tell out which requests belong to /test.
  • In nginx, use $http_referer to get the referrer field.
  • So in location /, we need to place the following code:
location / {
   if ($http_referer ~ ^http://xhyumiracle.com/test.*){
       rewrite ^(.*) /test$1 redirect;
   }
}
  • In this way, we can identify requests from /test and redirect them to /test, then let location /test to forward them to port 128.

0x05 /test/ghost

  • For now, nearly all the features are enabled under /test. Even we refresh the page in browser, it will return the right result.
  • But the administrate page /test/ghost seems still 404 not found.
  • I test it by return 403 as followed, it did return a 403 page when I type xhyumiracle.com/test/ghost, which means the problem is on ghost itself.
location /test {
    return 403;
}
  • F12 to open debug mode of chrome, I find there is a 301 redirect response of GET /ghost and a GET /ghost/ followed.
  • In ghost code, I found that it will redirect /ghost to /ghost/. In this way, new request doesn't carry a referrer header, so that's why our nginx failed again.
  • Knowing the reason, we can fix it by preventing the redirect happens.
  • So we need to redirect it before it reaches ghost:
location = /test/ghost { 
    rewrite ghost$ ghost/ redirect; 
}
  • Restart nginx, you can see the /test/ghost worked.
  • But, the last but in this article I promise, if you are newly set your blog and do not have an admin account yet, you might see another 404 error (lovely:).
  • Let's fix it in a rude way this time.

0x06 /test/setup

  • The error mentioned above is caused by /setup redirected from /ghost if there isn't an admin account. The /setup request, again, do not have referrer field in headers and failed our nginx rules.
  • This time, I didn't come up with a nginx solution, so I dive into the ghost code trying to find out the redirect code.
  • I did find it, it's in your-ghost-path/core/server/middleware/redirect-to-setup.js
  • Here is the redirect code snippet:
if (!exists.setup[0].status && !req.path.match(/\/setup\//)) {
    return res.redirect(config.paths.subdir + '/ghost/setup/');
}
  • So I just prepend the '/test' to the URL.
return res.redirect(config.paths.subdir + '/test/ghost/setup/');
  • Restart your ghost blog, fixed.

0x07 Be Safe

  • I know it would be dangerous and silly to publish nearly all the configurations. But I treat sharing knowledge as a much more important thing to others. So please do not hack this site, at least do not hack it with the knowledge from this post.
  • Thank you for your visiting.