Fitting Ghost blog into a micro AWS EC2 server

The blog platform I'm currently using to write this blog post currently is a cool piece of open source tech called Ghost.

Ghost: The best open source blog & newsletter platform
Beautiful, modern publishing with email newsletters and paid subscriptions built-in. Used by Platformer, 404Media, Lever News, Tangle, The Browser, and thousands more.

Here's the link to it

As someone who has used a decent bit of open source software, I'd say it checks all of the boxes of being a good open source project, while keeping it simple and straightforward. Its rating is up pretty high on my list, even still if you include paid software.

The installation was pain free and simple. All of the basic blogging features you could want are there and polished. All of the more advanced features that are hidden behind a paywall are either services which you could do yourself (such as its PaaS) or features that you don't really need (like extra themes). Documentation was the only thing I'd take points off for, but it came pretty close to being great. Probably some of this credit for this is most likely owed to its small team focusing on a pretty specific stack, but I as the user have a much better ability to be flexible than they do, so I don't mind.

I installed my Ghost platform on AWS on a single t2.micro instance, with just 1vCPU and 1GiB of ram. This is the minimum amount of resources necessary to run just Ghost. However, I also stuffed the NGINX and MySQL server in there to boot. I chose t2.micro and this architecture because its a free tier eligible and I would like anyone reading this to be able to run this. It runs perfectly well and I'll show you how to do the same below.

It'll cost me about $8 per month to run, but if I use a 1 year reserved instance, it can cut it down to about $5. The 3 year reserved would cut it down to almost $3 per month, but you'd have to be sure you'd still be using that EC2 instance down the line, if not for Ghost then for something else. And of course, if you're on the free tier, its $0. The other services will also cost about $2-5 a month, if not on free tier.


Architecture

  • AWS EC2 - Where all the services are contained
  • AWS CloudFront CDN - To cache the webpages
  • Cloudflare - This is where I registered my domain and also where I managed DNS settings
  • AWS Certificate Manager - For TLS certificate
  • AWS SES - Manages sending mail from the blog to people using SMTP

The EC2 itself contains MySQL server and database, which is used to store everything about the blog. It also uses NGINX to reverse proxy the http requests on port 80 from CloudFront to the Ghost service. I figured my traffic would be low enough that I can get away that I can get away with putting everything in one box.

Side note: It's actually not dangerous* to not use https from CloudFront to the EC2 instance. All data is encrypted on AWS's backbone, and you're already trusting AWS with your other data anyways. AWS also spends a lot on their security. If you have compliance or sensitive data though, you should still use https, in case of a data breach.

There is no Elastic Load Balancer used here because I'm not planning to scale horizontally. This is because Ghost doesn't support clustering or sharding, so vertical scaling is the only possibility. Therefore, no load balancing is required. Additionally, a well configured CDN will handle most of the scaling needed.

Here is the diagram:

[Oops I don't have it yet]

This guide assumes you already have a domain that is ready to use and you can manage the DNS settings for this domain.

If you want the Terraform file to instantly bring it up, it's at the end.

Setting up the EC2 Instance

I set up the EC2 instance with basic settings running Ubuntu 22.04. Ghost doesn't support 24.04 yet. The only thing I changed are that I set the default partition to 25GiB, to handle the database itself.

Once I SSH into the server, the first thing I do is to create a swap file of 1 GiB. Why?

The first time around trying to install Ghost, I ran into an issue in that the server became unresponsive during the 4/5 phase when running Ghost's install. This is because even though a minimum of 1 GiB of ram is stated as a requirement, that was for running Ghost; the actual installation needed quite a few more MiBs (one of the few small issues I saw on their documentation).

I learned that AWS did not have swap by default, so you have to manually create some.

After that, I just followed Ghost's self host guide. Some things to note:

  • You don't need to create a new user, you already have ubuntu
  • You don't need SSL, CloudFront can handle that for you

β€” Ghost 301 Infinite Redirection Minor Issue β€”

I did have a minor issue where I could not log onto the site because of an infinite redirection loop. If you have an error with your site where it is a redirection issue, it's because Ghost is redirecting you to the https version, as http is insecure. This is because Ghost was designed to run with only NGINX as the reverse proxy, and not expecting an additional proxy. Because we are using CloudFront, it strips the headers (I also learned that while doing this!) and only sends an http request to NGINX. The fix to this, just add the forwarded protocol header to https in the NGINX file, which shows that the original request was made in https.

EDIT: Actually ONLY do this if you can guarantee HTTPS from the proxy side. This is by enabling viewer_protocol_policy = "redirect-to-https" behavior on CloudFront's side. If you have http enabled, you open yourself up to man in the middle attacks. For example, if they send an http request and put headers as https. You can also do proxy_set_header X-Forwarded-Proto $http_cloudfront_forwarded_proto, as I just learned that CloudFront has it's own forwarded proto header, but you need to add it in on CloudFront's end.

Ooh blurry terminal
Ghost reverse proxy configuration - avoiding infinite redirect loops
Getting an infinite redirect loops when configuring Ghost with a proxy and setting your url to https? Here’s a quick guide to get things working again.

The problem is also noted here. Thanks ghost!

Request and response behavior for custom origins - Amazon CloudFront
Describes how CloudFront processes viewer requests and responses for your custom origin.

List of headers CloudFront automatically removes 😦

Configuring CloudFront

This won't be a guide to configure each service, so I'll glance over the configurations.

Essentially, set your CNAMEs to redirect your domain to CloudFront, get your SSL certificate from AWS Certificate Manager (which also requires some DNS record configs), and the point the origin to your EC2's public domain name.

Setting up the mail server

β€” Can't login to Ghost admin? β€”

One thing that is not mentioned in the Ghost self host guide is that even if you are not using any of the subscriber features, you will still need to set up SMTP so that you can login. You see, Ghost now uses device verification and email 2FA, so if you were to login to a different device, Ghost would need to send an email. Otherwise, you get a server error.

Device verification & email 2FA
Security improvements for staff user authentication

I set up mine with AWS SES, but you can set it up with any SMTP service. This also requires some DNS records, as well as getting your SES out of sandbox mode.

Host is your region server. The config page is at https://ghost.org/docs/config/

Terraforming

GitHub - Thaileaf/AWSGhostTerraform: Terraform and script files to create EC2 instance that hosts ghost blog
Terraform and script files to create EC2 instance that hosts ghost blog - Thaileaf/AWSGhostTerraform

[The above files are currently a WIP. Use them as reference for now]

Here are my files for terraforming. This will create the all the infrastructure on Amazon needed to host the project. Make sure you have terraform installed and aws cli configured.

Note that you still need to configure the EC2 instance as well. I included a user_data.sh file that will include everything to actually configure the instance itself.

Conclusion

Overall, it was a nice experience. Ghost definitely makes writing blogs as seamless as possible, which then makes the whole experience that much more enjoyable.

My future plans involve an automated back up from my server to S3, as well as monitoring with Prometheus to monitor visitors, usage, and alerts for scaling. I don't expect to need to vertically scale or separate my database into it's own RDS instance anytime soon, but people here might prove me wrong (hopefully?).

Let me know in the comments about any thoughts on the process πŸ˜„