I'll start out by stating the obvious: I am not a web developer. Hell, I don't even play one on T.V.! At one point in my career, though, I could have been had I decided to embrace the world of front-end / UI Engineer, but my career took me down another path (User Experience, Customer Research, and Product Design).
I wrote a rather large post about rebooting my digital footprint in January of 2022. I had ditched WordPress for Ghost, which I chose to self-host at Digital Ocean on a 1-click droplet. It was easy enough, right? Push a button, pick a server, and let it rip!
But, me being me, I wanted to find something more complicated. I got curious about spinning up my own Ubuntu VPS with a cloud provider - mostly to see if I could get max performance out of a like $2/mo box - but also to see if I had the technical and analytical skills to actually do it.
You're reading this post on my new CloudFanatic (affiliate link), $6.99/mo VPS based out of Chicago, IL - just 300 miles north of St. Louis. I've got 4GB RAM, 2vCPU cores, and a 50GB SSD to play with for all the things I want to run.
Initial Setup
Remember how I'm not a web developer? I do have some familiarity with command line and I'm getting increasingly comfortable with the idea of moving around Linux without a GUI. This old designer is learning new tricks!
User Management
The first thing I had to do was ssh
in to the VPS as the root user:
ssh root@IP Address
Now that I'm in the box, I had to create a new user and give it appropriate permissions:
# adduser ghost-mgr
# usermod -aG sudo ghost-mgr
Next, I needed to log in as that new user:
# su - ghost-mgr
Upgrades galore
Now that I'm logged in as ghost-mgr
, it's time to upgrade my install of Ubuntu 22.04:
$ sudo apt update && sudo apt upgrade -y
Installing nginx
Next, I needed to install nginx:
$ sudo apt install nginx
Just for good measure, I wanted to verify that everything was ok with nginx by running the following command:
$ sudo systemctl status nginx
The output:
nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
Active: active (running)
Docs: man:nginx(8)
Process: 66019 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Process: 66020 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
Main PID: 66112 (nginx)
Tasks: 2 (limit: 2196)
Memory: 2.6M
CPU: 148ms
CGroup: /system.slice/nginx.service
├─66112 "nginx: master process /usr/sbin/nginx -g daemon on; master_process on;"
└─66113 "nginx: worker process"
Looks good enough to me (but again, I am not a developer).
MySQL Install
Now's where things got a little complicated for me. It was time to install MySQL. I am - again - barely comfortable with node.js and certainly not as comfortable with SQL as I'd like to admit, but I'm following the guide that was published by Ghost on how to self-install, so I went in with gusto. Worst-case scenario, it doesn't work and I have to go back and try again... and again... and again.
(Deep breath)
$ sudo apt install mysql-server
Success. Now, I needed to start it up and make sure that if I rebooted the VPS, it'd also automatically start (enable
, in the parlance of MySQL):
$ sudo systemctl start mysql
$ sudo systemctl enable mysql
Just for shits 'n giggles, I wanted to test to see if it was working by running the following command:
$ sudo systemctl status mysql
And the output?
mysql.service - MySQL Community Server
Loaded: loaded (/lib/systemd/system/mysql.service; enabled; vendor preset: enabled)
Active: active (running)
Main PID: 1083 (mysqld)
Status: "Server is operational"
Tasks: 41 (limit: 2797)
Memory: 434.0M
CPU: 1min 57.932s
CGroup: /system.slice/mysql.service
└─1083 /usr/sbin/mysqld
Again, looks good to me. It actually output something!
A lot of web searching tells me that MySQL is actually not hardened or secure out of the box. So, I ran the following command:
$ sudo mysql_secure_installation
And configured as follows:
- Set root password? [Y/n] Y
- Remove anonymous users? [Y/n] Y
- Disallow root login remotely? [Y/n] Y
- Remove test database and access to it? [Y/n] Y
- Reload privilege tables now? [Y/n] Y
At this point, I was just hoping that I didn't mess it up. It was now time to create the MySQL database, add a user, and give that user permissions:
$ sudo mysql -u root -p
Success, baby! I'm now seeing a mysql>
prompt instead of my usual $
prompt. I am truly hacking the planet. Time to get after it:
mysql> CREATE DATABASE ghostdb;
mysql> CREATE USER 'ghostuser'@'localhost' IDENTIFIED BY 'PASSWORD_HERE';
mysql> GRANT ALL PRIVILEGES ON ghostdb. * TO 'ghostuser'@'localhost';
mysql> FLUSH PRIVILEGES;
mysql> exit;
Time to remember some things, because the MySQL database that makes my Ghost install run is not the same as the root
user, with a different password, or my ghost-mgr
user with yet another password. It's really easy to get lost in the sauce, so write some of this stuff down so you don't regret it!
Database | User | Password |
---|---|---|
ghostdb | ghostuser | PASSWORD_HERE |
Node.js
Now that we've exited the database set-up portion of the install, we need to toss Node.js on the VPS. We can do that pretty easily by banging out the following:
$ sudo curl -sL https://deb.nodesource.com/setup_18.x | sudo -E bash
You'll get some errors that whine about "oh, no! this is outdated, blah blah blah" - just blow through those. Keep on truckin' - we're nearly there.
After your screen looks like it's glitching in The Matrix, you'll need to actually install Node.js:
$ sudo apt-get install -y nodejs
And, much like we've done before, you can verify the installed version with a simple command:
node --version
With an output of:
v18.18.2
Now, we need to install Node Package Manager (npm
), which is the engine that drives a lot of our Ghost install:
$ sudo npm install npm@latest -g
And, again, to verify:
npm --version
npm: '8.19.4'
Installing Ghost
It's finally time to do the thing. You've made it this far...
Now we need to install the Ghost command-line interface (CLI) tool. This will allow us to actually install Ghost. Can you feel the excitement? I sure could:
$ sudo npm install -g ghost-cli@latest
And, once again, we can check to see what version we have:
$ ghost -v
Ghost-CLI version: 1.25.3
Smells like success.
Now we get to finally - finally! - install Ghost and see all of this hard work pay off.
Installing Ghost
First, we make a new directory, grant our ghost-mgr
user permission to the directory, set up permissions, and move inside the directory we created:
$ sudo mkdir /var/www/ghost
$ sudo chown ghost-mgr:ghost-mgr /var/www/ghost
$ sudo chmod 775 /var/www/ghost
cd /var/www/ghost/
Drumroll, please. It's now time to press the button and do the thing!
$ ghost install
When done successfully, you'll see the following output:
✔ Checking system Node.js version - found v16.18.1
✔ Checking logged in user
✔ Checking current folder permissions
✔ Checking system compatibility
✔ Checking for a MySQL installation
✔ Checking memory availability
✔ Checking free space
✔ Checking for latest Ghost version
✔ Setting up install directory
✔ Downloading and installing Ghost v5.24.0
✔ Finishing install process
And then, you'll get to actually configure the install!
? Enter your blog URL: http://yourdomain.com
? Enter your MySQL hostname: localhost
? Enter your MySQL username: ghostuser
? Enter your MySQL password: [hidden]
? Enter your Ghost database name: ghostdb
✔ Configuring Ghost
✔ Setting up instance
+ sudo useradd --system --user-group ghost
? Sudo Password [hidden]
+ sudo chown -R ghost:ghost /var/www/ghost/content
✔ Setting up "ghost" system user
ℹ Setting up "ghost" mysql user [skipped]
? Do you wish to set up Nginx? Yes
+ sudo nginx -s reload
✔ Setting up Nginx
You'll also get to do a few other things with the install – mainly, set up your free Let's Encrypt SSL certificate for https
all over the place, and set up systemd
to keep ghost running smoothly. I promise you it's worth it.
And once you've got all that done, you can finally - at long last! - log-in via the web in the URL provided.
Pour yourself a drink, partner. You've earned it!