I set up a blog. It was annoying.

Today I created this blog. It's hosted in AWS and runs on the free and open source version of Ghost - although if you take a glance at the Ghost homepage, you might not even realise that there is a free and open source version, given the prominent mentions of 14 day free trials and the slightly creepy focus on business...

But I don't have an audience! I don't want to turn anybody into anything! I just want a blog.
Look, I'm sure Ali is very successful, whoever he is, but I don't want 25 million dollars. I just want a blog.

Why Ghost?

I considered a few possible alternatives to Ghost, and ruled them out:

  • I considered Substack, given its current popularity, but at least when I played around with Substack a few months ago, it had absolutely no support for syntax highlighting within code blocks. Since I'm a web developer, I'm probably going to want to post code on my blog at some point, and I'd like to use a system that just gives me nice syntax highlighting out of the box.
  • I considered Wordpress, but decided against it due to its terrible reputation for security vulnerabilities. Maybe this is an outdated prejudice on my part and things are much better now. Maybe. But I preferred to give a competitor a chance.
  • I considered a self-hosted Jekyll website. Jekyll is somewhat unlike typical blogging platforms in that it's a static site generator and doesn't use a database. Rather than providing a WYSIWYG editor in an admin section of the site where you draft and publish posts, instead you write Markdown files in an editor, push them to a Git repo or something, and let Jekyll generate the site HTML from them. This is an elegant paradigm that has the nice side effect that if you put your blog in a public GitHub repo, you get a publicly-accessible revision history on your posts for free.

    However, the thing I dislike about this paradigm is that it's inherently incompatible with allowing comments on your posts, at least as a properly integrated part of the blog; for that, you absolutely need some sort of database for the comments to get stored in, and some kind of admin panel (to let you log in and moderate comments, unless you fancy doing that via direct database edits or just doing without moderation and letting your comment section be a free-for-all for spammers, pornographers, and bitcoin scammers). Jekyll bloggers who want to allow comments get around this by doing something like including a Disqus widget on their blog, but I kind of hate that; it means that your comments are being hosted on a third-party platform separate from the rest of your blog content and might vanish one day based on that third party's whims and moderation policies. Seems bad. I'd like the ability to host my own comments (eventually, when I get round to turning that on), and that means I need a traditional blogging platform with a database - like Ghost.

After ruling out the options above, Ghost seemed like the next-most-famous option - at least, the only remaining option I could recall having heard of - and I didn't see any major problem with it, so I settled for Ghost. I decided to set up Ghost myself in an AWS instance instead of signing up for Ghost Pro, the paid version that Ghost (the company) will manage for you, because, for goodness' sake, I'm a web developer, and it'd be embarrassing not to self-host. Besides, how hard could it be?

The answer, it turned out, was "somewhat harder than I expected".

Fighting with MySQL - and with terrible documentation

First I registered the domain markamery.com in Route 53, created an Ubuntu t2.small EC2 instance to run the blog (I tried a t2.micro first, but it was so feeble it hung forever when ghost-cli was trying to install Ghost's dependencies), created an Elastic IP and associated it with the EC2 instance, and set up an A record to point markamery.com at that IP address.

Next I set about simply getting Ghost running, both on my local machine (by following the instructions at https://ghost.org/docs/install/local/, and on my newly-created EC2 instance by following the instructions at https://ghost.org/docs/install/ubuntu/. The local setup went fine. The production one derailed frustratingly when trying to figure out how to configure MySQL. Here are the pertinent bits of what Ghost's docs, linked above, currently say:

Prerequisites

...

  • ...
  • MySQL 8
  • ...

Install MySQL

Next, you’ll need to install MySQL to be used as the production database.

...

# Install MySQL
sudo apt-get install mysql-server

...

Run the install process

Now we install Ghost with one final command.

ghost install

Install questions

During install, the CLI will ask a number of questions to configure your site.

...

MySQL hostname

This determines where your MySQL database can be accessed from. When MySQL is installed on the same server, use localhost (press Enter to use the default value). If MySQL is installed on another server, enter the name manually.

MySQL username / password

If you already have an existing MySQL database, enter the the username. Otherwise, enter root. Then supply the password for your user.

...

Set up a ghost MySQL user? (Recommended)

If you provided your root MySQL user, Ghost-CLI can create a custom MySQL user that can only access/edit your new Ghost database and nothing else.

So - the recommended approach is to specify root as your MySQL username when asked by the CLI's setup wizard, and then tell it to create a custom MySQL user that Ghost will use. But what are we supposed to enter as the password? The docs say to "supply the password for your user" - for the root user, presumably - but what is that? They don't say.

Well, actually, there isn't one. Since at least as far back as MySQL 5.7, released in 2015, the root user created by the MySQL installer gets configured with the auth_socket "plugin" (authentication methods, even default ones, are "plugins" in MySQL lingo, for some reason). The auth_socket plugin lets you authenticate over a Unix socket instead of over the network, and simply confirms that the Unix user you're connecting as has the same name as the MySQL user you're trying to authenticate as. So, if the root MySQL user has auth_socket auth set up, only the root Unix user (or someone with sudo privilege who runs sudo mysql) will be able to authenticate as the root MySQL user.

There doesn't seem to be any way to get the Ghost CLI to attempt to connect to MySQL using the root user when you run ghost install (which you'll note the docs do not instruct you to run with sudo!). Leaving the password blank doesn't do it and there's no way to skip the question. So if you follow Ghost's docs as written, you're guaranteed to hit an error like this:

ubuntu@ip-172-31-25-1:/var/www/marks-blog$ ghost install

Love open source? We’re hiring JavaScript Engineers to work on Ghost full-time.
https://careers.ghost.org



✔ Checking system Node.js version - found v16.19.1
✔ Checking current folder permissions
✔ Checking memory availability
✔ Checking free space
✔ Checking for latest Ghost version
✔ Setting up install directory
✔ Downloading and installing Ghost v5.42.2
✔ Finishing install process
? Enter your blog URL: https://markamery.com
? Enter your MySQL hostname: localhost
? Enter your MySQL username: root
? Enter your MySQL password: [hidden]
? Enter your Ghost database name: marks_blog_prod
✔ Configuring Ghost
✔ Setting up instance
+ sudo chown -R ghost:ghost /var/www/marks-blog/content
✔ Setting up "ghost" system user
? Do you wish to set up "ghost" mysql user? Yes
✖ Setting up "ghost" mysql user
Nginx configuration already found for this url. Skipping Nginx setup.
ℹ Setting up Nginx [skipped]
Nginx setup task was skipped, skipping SSL setup
ℹ Setting up SSL [skipped]
Systemd service has already been set up. Skipping Systemd setup
ℹ Setting up Systemd [skipped]
+ sudo systemctl is-active ghost_markamery-com
+ sudo systemctl reset-failed ghost_markamery-com
? Do you want to start Ghost? Yes
+ sudo systemctl start ghost_markamery-com
+ sudo systemctl stop ghost_markamery-com
✖ Starting Ghost
One or more errors occurred.

1) CliError

Message: Error trying to connect to the MySQL database.
Help: You can run `ghost config` to re-enter the correct credentials. Alternatively you can run `ghost setup` again.


2) GhostError

Message: Ghost was able to start, but errored during boot with: Access denied for user 'root'@'localhost'
Help: Unknown database error
Suggestion: journalctl -u ghost_markamery-com -n 50

Debug Information:
    OS: Ubuntu, v22.04.2 LTS
    Node Version: v16.19.1
    Ghost Version: 5.42.2
    Ghost-CLI Version: 1.24.0
    Environment: production
    Command: 'ghost install'

Additional log info available in: /home/ubuntu/.ghost/logs/ghost-cli-debug-2023-04-11T11_45_48_981Z.log

Try running ghost doctor to check your system for known issues.

You can always refer to https://ghost.org/docs/ghost-cli/ for troubleshooting.

Aggravating! Okay, I thought: I'll try rerunning the installer, but first I'll explicitly set a password for the root MySQL user. How do I do that?

A quick google finds the MySQL docs page on resetting the root user's password, which warns that the root user having no password is insecure and links to another page that suggests setting an initial password with:

ALTER USER 'root'@'localhost' IDENTIFIED BY 'root-password';

So I tried that. But wait; nothing changes, and I can't connect using the password I just "set". What's going on?

The answer is that in order to set a password on a MySQL user, you need to change the auth plugin used by the root user, as well. Otherwise, MySQL just totally ignores your command. The right incantation to use to give the root user a password isn't the one indicated by the docs pages specifically about giving the root user a password; rather, it's this:

ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'my-new-password';

The IDENTIFIED WITH clause there changes the auth plugin to one that actually supports password auth. Once we do that, Ghost is finally able to complete its setup.

Naturally, this is infuriating! The MySQL docs I link to above are outdated to the point that their very existence is harmful; purging them outright would be a good thing. I suspect I am not the only one that they have confused; I have written a related answer on Security Stack Exchange, since these docs currently give wholly incorrect advice about securing the root account.

(12 April 2023 edit: it turns out this isn't entirely fair. If you install MySQL the way the MySQL docs tell you to, instead of just running sudo apt-get install mysql-server like the Ghost docs tell you to, then root doesn't get given the auth_socket auth method and the docs make more sense! The overall situation is still crappy since an ordinary user who just installs MySQL from their OS's default repos is likely to be left confused by the docs, but they're not outright objectively wrong in the way I believed them to be when I wrote this yesterday.)

Bah! How did I end up wrestling with lying MySQL docs and writing SSE answers? I just wanted a blog!

The rest of the setup process

Given my grumbling above, I should give Ghost its due: once I had won my fight with MySQL, I found setting up Ghost to be a fairly painless experience.

I found it pretty neat that the ghost install wizard has an option to set up Let's Encrypt for you to manage your SSL certificates; you simply press Y  when asked if you want this, it works, and you don't need to screw about with SSL certificates at all. Maybe other server software also supports this, these days, but I've never seen it before now; I expected to have to cron certbot manually. As someone who has been in web development just long enough to remember the days before Let's Encrypt - when there was no automated generation of SSL certificates for sites on the public internet, and every year or so you'd have to fork out a load of money to some certificate authority and manually swap out the certificates on your servers for new ones - it's a remarkable improvement in developer experience!

Here's a list of further steps I took, for the sake of anyone interested in replicating my setup (perhaps my future self):

  • Visited https://markamery.com/ghost - the URL of the admin UI - and created a user with a password.
  • Went to Settings -> Design and selected the Journal theme. (Its styling is the most appealing to me of the build-in themes, although it's ostensibly a theme for a "newsletter" rather than a "blog" which leads to some annoying issues I address below...)
  • Went to Settings -> Membership and changed Subscription access from "Anyone can sign up" to "Nobody" ("Disable all member features, including newsletters"). I might turn this back on at some point - perhaps to allow commenting - but disabling it was the easiest way to turn off this annoying pink banner asking people to "subscribe":
No! Don't subscribe! This isn't a newsletter! There are no "members-only issues"! I just wanted a blog.
  • Went to Settings -> Navigation and deleted the "Sign up" link from the "Secondary navigation" section; otherwise, this shows up in the site footer, and is just a broken link with Subscription access turned off.
  • Deleted the autogenerated "Coming soon" post that comes with the blog, as well as the News tag, and updated the About this site page, which by default begs people to subscribe to allow the site to continue to exist.
What?! None of this after the first line is true. I just wanted a blog.
  • Modified the theme to change the word "topics" to "tags" and "issues" to "posts". (The docs I've been able to find on this aren't great, but the process is fairly simple: you run Ghost locally and configure it to use the Journal theme, which creates a copy of the theme in the /content/themes/journal directory, and then you modify the HTML templates in there, export your own modified theme via Settings -> Design -> Change theme -> Advanced -> journal -> Download, then import it on the live site via the "Upload theme" button on that same Design page.

Finally, I set up a Diary tag, which I'll put on posts that are just my record of stuff I've worked on but which I don't expect to be of interest to most of the world. I wanted these to be hidden from the homepage, and found some promising-sounding advice on how to achieve this on Ghost's forum at https://forum.ghost.org/t/filter-out-posts-by-tag-on-homepage/14913/4. The instructions weren't quite complete as given, but I figured out how to get them working - see my post there.

And that's it! I have a working blog, and here is my first post! There might be some more tweaks I want to make to it in due course, like enabling comments and setting up backups somehow, but this should get me started. Along the way, I've found several things broken in the world that need fixing:

Things to fix

Below is my To Do list of things I've discovered along the way to creating this blog that I'm going to go and either fix or ticket.

1: The MySQL docs

As I describe above, the docs at https://dev.mysql.com/doc/refman/8.0/en/resetting-permissions.html and https://dev.mysql.com/doc/refman/8.0/en/default-privileges.html are  almost a decade out of date and need either deleting outright or updating to reflect the fact that the root user on a new MySQL install uses auth_socket authentication these days. I'm also a little suspicious about the whole mysql_secure_installation utility, now. None of the four things the docs say it does need doing at all on a fresh install of MySQL on Ubuntu!

(12 April 2023 edit: I dug into this more. See MySQL docs bugs weren't really bugs in my diary.)

2: Ghost's handling of the root user using auth_socket

ghost-cli should be made compatible with the root MySQL user being created with auth_socket auth, since that's MySQL's default behaviour, and the docs at https://ghost.org/docs/install/ubuntu/ should be updated to indicate what you should enter as the MySQL password if your root user uses auth_socket auth.

(12 April 2023 edit: I ticketed this at https://github.com/TryGhost/Ghost-CLI/issues/1759?ref=markamery.com)

3. Ghost's WYSIWYG editor should support headings inside quotes

An annoyance I discovered in Ghost's editor: if you try to format some text as a heading inside a blockquote (either by selecting text and formatting as a heading via the formatting bar that appears when you select text, or by writing a # symbol at the start of a line within a quote), the blockquote disappears, and similarly if you try to quote an existing heading, it ceases to be heading. The only way to quote content that includes headings - like I did earlier when quoting from the Ghost docs - is to insert a "raw HTML card" and write in HTML instead of using the WYSIWYG editor. This seems crappy and might be easily fixable.

(12 April 2023 edit: per https://forum.ghost.org/t/paragraphs-inside-blockquotes-koenig-editor/4351/2, this is working as designed, and the Ghost devs suggest inserting a Markdown "card" to do blockquotes. I hadn't noticed those; that would've been more convenient than HTML. I still think this is sucky UI, but it's tolerable.)

4. The initial request to /ghost/api/admin/authentication/setup/ appears to fail

Specifically, the request that gets made when you click the "Create account & start publishing" button during setup times out after 1 minute with a 504 response from Nginx. The setup still actually works and a user gets created you can log in is, but something at the end of the process is hanging for a minute.

I didn't capture a screenshot of this when I was setting up this blog, so I made another one just to show this:

(My best guess - without having looked at the code at all yet - would be that it's trying to send an email to the address I entered, and failing because there's no email provider configured yet - that's not part of the setup process at https://ghost.org/docs/install/ubuntu/.)

(12 April 2023 edit: My guess above was right. See https://github.com/TryGhost/Ghost-CLI/issues/1760.)