A while ago, Revue, the newsletter service we were using, announced that it was shutting down.
We had to look for other alternatives. I tried using Wordpress, but it’s true that this ancient system hasn’t evolved particularly much over the years, and then my editorial partner at Newsletter suggested Ghost, which looked really good.
The Ghost project was started by John O'Nolan, the former head of the Wordpress UI team, after he left the project, and in 2012 he said on the Blog that started the project:
WordPress is so much more than just a blogging platform
All I can say is that I couldn’t agree more.
Ghost as a platform is very easy to use, the design sense is great, of course the official host price is also very pleasing. The good thing is that Ghost is an open source project, and those who are willing to do the tossing can also drum up.
When I tossed Ghost over the weekend, I found that it would infinitely 301 redirect loop after setting up the https certificate, eventually the problem was solved but I wanted to figure out what was going on.
1. I suspect that Nginx has opened an infinite loop
Let’s start with the Nginx configuration. A complete Nginx Conf instance can refer to the official website here, theoretically we configure multiple different
servers when listen on port 80 ( http) and port 443 (https), and then do a
host judgment on the 80 server configuration and forward it all directly to https.
Now there is certbot which is easy to configure. I didn’t find any misconfiguration of Nginx after looking at it for half a day.
Last I looked this article mentioned the need to add this configuration:
The article also mentions that Cloudflare’s SSL rules may also lead to unlimited 301 but I had already transferred the domain name from Cloudflare DNS at that time, so it has nothing to do with Cloudflare.
Ghost is written in NodeJS and runs on port 2368 by default. We must forward the requests after Nginx access, but the official Ghost-CLI does not have this article after setup:
Referring to the official Nginx documentation, it is equivalent to redefining the HTTP HEADER for inbound requests, where
X-Forwarded-Proto is also a “de facto standard” Header, see the Mozilla documentation. The documentation says that this Header is mainly used by the client to tell whether the protocol used to connect to the proxy is HTTP or HTTPS; the real standard is detailed in the Forwarded field.
2. Why must I bring X-Forwarded-Proto Header?
Since the 301 redirect is not caused by Nginx, it is Ghost?
Let’s go through Ghost’s code to see. It turns out that Ghost uses ExpressJS as the Server, which
ghost/core/core/shared/express.js code has this paragraph:
Let’s look at express’s code and see what this
'trust proxy' has done.
lib/application.js, the method
app.set writes the configuration for the
Which is again done inside the
compileTrust() method by
proxy-addr, item in here.
This step is to translate the IP address description, e.g. :
The latter part of the argument is then processed by the
So the next parameter is empty, which means trust everything, because the express is proxied, so we have to rely on
proxyaddr to resolve the real IP of the client request.
So not only do you need
X-Forwarded-Proto, but in fact the Ghost-Cli Nginx configuration template is complete with several key fields:
3. How does Ghost forward http requests to https?
Ghost’s configuration file is managed using nconf, when we configure the URL of http protocol in Ghost directory there will be no problem of infinite redirect, but https will.
That is, if you configure the website url to
https://example.com, but visit
http://example.com, Ghost will automatically redirect to https for you.
The specific implementation is in
ghost/core/core/server/web/shared/middleware/url-redirects.js , and the core function is this:
Ghost has a front-end page for users (FrontendApp) and a back-end page for administrators (AdminApp). The front-end page is also started with an Express Server, and then uses the above mentioned middleware to check if each request is
secure. This secure or not comes from
req.secure, and this
req.secure will take the
X-Forwarded-Proto in the Header if the
trust proxy is started. The code is in the
lib/request.js file of the Express project, in
defineGettter about the
As you can see from the above code, express checks the
this.connection.encrypted property first. I guess Nginx forwarding is not encrypted, because the local express server just opens an http service, and Nginx forwarding to it really doesn’t need to be encrypted either. So it jumps to the step of taking the Header. So if our Nginx configuration doesn’t come with
X-Forwarded-Proto, express will think that the real client request is not https, and redirect to https, resulting in an infinite loop.
4. What’s next?
Although Ghost is a simple problem to solve, “just change the configuration”. However, it is interesting to trace the process of looking at the code of each project through the layers. In this article, the author mentioned that x-forwarded-proto is checked by Ghost, but since the whole service involves Nginx, Ghost, and Express, I wanted to find out what the problem was.
Through this tracing also a first glimpse of the general structure of the Ghost project, the process is still quite interesting. But it seems that Ghost is not particularly open, after all, the platform hosting is the most profitable part of the project.
Wordpress for so many years, has now become a giant with historical baggage, these years a variety of “alternative open source CMS” also emerged, but also the ups and downs, disappeared a lot. I feel great to see that Ghost is now making money, and the only way to keep going is to keep making money.
Ghost’s design sense is really in the current open source CMS without its right, hope Ghost can always be evergreen.