A while back we moved our blog from Medium to a self-hosted Ghost setup which lived on a subdomain of pingly.com. However for aesthetics and SEO we wanted to move it from a subdomain to a subdirectory, but the challenge was our domain root was already being for our app Pingly. Here's how we did it...

I'm told subdomain vs subdirectory doesn't matter anymore for SEO, but I'm not convinced yet 😉

Since Pingly is served from the root of pingly.com we needed to setup a forwarding/proxy rule in our main nginx config to proxy requests to pingly.com/blog transparently to another server running our Ghost blog.

Technically you could run Ghost on the same box as your main app, but for security and isolation of our app, we wanted to install our blog on its own server.

Configuring ghost

You can install Ghost on your Linux distro of choice like normal, however there a few installation questions to pay attention to:

Blog URL

When Ghost asks you for your blog URL, put in the URL as it will be served to end users. Ex: We wanted our blog at pingly.com/blog so we enter that into Ghost's installation process. This ensures that links and images in Ghost are pathed and routed correctly.

Ghost subdirectory url setup


We did not setup TLS/SSL on the Ghost blog server itself, however this is not recommended in most cases. In our case, our servers are running in a VPC setup and private link traffic between servers is secured.

TLS is enabled on our main Nginx front-end and is proxied directly to Ghost's private IP in our VPC. The blog server is not publicly accessible without going through our main Nginx setup.

As far as I know, you cannot yet get a cert for an IP address with Let's Encrypt, thus this setup is much easier.


To connect everything, we use the Nginx config snippet below to proxy HTTP requests to pingly.com/blog to the private VPC ip to our blog server:

  location /blog {
    # Replace with the IP address that your
    # Ghost blog is listening on
    proxy_redirect off;
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;

Note: We did not install Nginx on the blog server as this is another layer not needed in our case.

Be sure to reload or restart Nginx and possibly your Ghost server to make sure everything is running on the correct config/ip/path etc.

If you have any feedback or suggestions feel free to email me at noah@pingly.com

Pingly is your team's communication hub with email and chat in one platform. Try it out for free.