How to install Ghost alongside CyberPanel on Ubuntu 20.04 without Docker
If you have a Ubuntu server with CyberPanel installed, and want to install a Ghost blog on that same server, here are step by step directions on how to do that.
The latest version of Ghost (5.x) encourages (and soon requires) MySQL 8. If your CyberPanel server has MariaDB installed, you have a three options:
- Use remote database server with MySQL 8.
Pro: Meets requirements.
Con: Need to set up remote database. - Configure SQLite if you don’t want to bother with MySQL 8.
Pro: No need for remote database.
Con: Data stored on file system. Not scalable. - Install MySQL 8 on your CyberPanel in parallel to MariaDB.
Pro: Meets requirements.
Con: New database system just for Ghost.
I’ll be using a remote MySQL 8 database, simply because I already have a separate server with that set up, but I’ll include a note on how to set up SQLite if you just want to take Ghost for a spin.
Install Node, NPM, and Ghost CLI
As of this writing, Ghost recommends using Node 16.x to run Ghost, but it’s likely that the Node version that would automatically be installed on your server is lower (and perhaps even unsupported) compared the recommended version.
In order to see which version would be installed, SSH into your server and run:
apt-cache policy nodejs
If it displays something lower than Node 16.x, Ghost recommends adding the Node 16.x distribution from Node Source to your server:
curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
Next, install Node (which includes NPM):
apt-get install nodejs
Last, install the Ghost CLI via NPM:
npm install --location=global ghost-cli
Create website in CyberPanel
Login to CyberPanel and create a website as you normally would. This is not where the Ghost blog will live, but we get a few things for free by using this process: a Linux user for the Ghost blog, a SSL certificate, and a virtual host file to configure.
Here is what I used, but replace any values as needed, such as domain name and email:
- Select Package: Default
- Select Owner: admin
- Domain Name: blog.example.org
- Email: [email protected]
- Select PHP: PHP 8.0
- Additional Features: Check SSL
Make sure you already pointed the DNS of the domain name to your server, otherwise CyberPanel won’t be able to set up a SSL cert from Let’s Encrypt, but rather fall back on configuring a self-signed certificate.
If it fails, don’t worry, you can always attempt it again later after the DNS has propagated. If you’re using something like Cloudflare in front of your site, Cloudflare’s SSL cert will be sufficient and connect to your site using the self-signed cert.
Prepare for Ghost installation
During the Ghost installation, the Ghost CLI checks whether it has read and write permission to the directory, and all of its parent directories, in which the Ghost blog will be installed.
But everything in the /home
directory, where your sites normally live, is locked down to prevent websites from interfering with one another. This means Ghost CLI's check, and therefore the installation, will fail.
Because of this, we’re going create a new directory outside of the /home
directory for the Ghost blog to live. I’m choosing /var/www
out of convention, and will match the directory name to the one CyberPanel created, so that it remains clear to which CyberPanel website the Ghost blog belongs to:
mkdir /var/www/blog.example.org
Now list the directory contents of the CyberPanel site:
ls -al /home/blog.example.org
And look up the user CyberPanel created for the Ghost blog (e.g. bloge1234), which will be referred to as the Ghost user from here on out:
drwx--x--x 4 bloge1234 bloge1234 4096 Jul 26 02:17 .
drwx--x--x 21 root root 4096 Jul 26 02:17 ..
drwxr-x--- 2 root nogroup 4096 Jul 26 02:17 logs
drwxr-x--- 2 bloge1234 nogroup 4096 Jul 26 02:17 public_html
Let’s give the Ghost user access to read and write to the directory we just created:
chown bloge1234:bloge1234 /var/www/blog.example.org
chmod 775 /var/www/blog.example.org
Now set a password for the Ghost user:
passwd bloge1234
And add the Ghost user to the sudo group (required for Ghost to work):
adduser bloge1234 sudo
Next, switch over to the Ghost user:
su - bloge1234
And last, move into the directory where we’ll install the Ghost blog. Note that we’re moving into /var/www
and not /home
:
cd /var/www/blog.example.org
Install and configure Ghost
Install Ghost with the Ghost CLI:
ghost install --no-stack --no-setup-nginx --no-setup-ssl
- –no-stack: Avoid checking if server meets requirements.
- –no-setup-nginx: Don’t set up nginx; we’re using OpenLiteSpeed.
- –no-setup-ssl: Don’t set up SSL; we’re using CyberPanel.
You can learn more about the Ghost CLI and its options on Ghost's website.
--port 2369
up above (or a port other than the 2368
default) to run this new instance on a different port.Ghost will complain that it can’t find MySQL, which is fine, because we’re using a remote MySQL 8 database. Press y and ENTER:
Local MySQL install was not found or is stopped. You can ignore this if you are using a remote MySQL host.
Alternatively you could:
a) install/start MySQL locally
b) run `ghost install --db=sqlite3` to use sqlite
c) run `ghost install local` to get a development install using sqlite3.
Now enter your blog URL and database credentials:
? Enter your blog URL: https://blog.example.org
? Enter your MySQL hostname: 0.0.0.0
? Enter your MySQL username: blog_example_org
? Enter your MySQL password: [hidden]
? Enter your Ghost database name: blog_example_org
Enter the password you set for the Ghost user earlier:
? Sudo Password [hidden]
To automatically start Ghost after a server reboot, choose Y to set up systemd
:
? Do you wish to set up Systemd? Yes
And last, choose Y to start the Ghost blog now:
? Do you want to start Ghost? Yes
SQLite
If you want to install Ghost using SQLite, include the --db=sqlite3
with the other flags. You can start with SQLite and later edit config.production.json
within /var/www/blog.example.org
to configure MySQL 8.
Simply swap out the SQLite configuration:
"database": {
"client": "sqlite3",
"connection": {
"filename": "/var/www/blog.sechu.us/content/data/ghost.db"
}
},
With the MySQL 8 configuration:
"database": {
"client": "mysql",
"connection": {
"host": "0.0.0.0",
"port": 3306,
"user": "blog_example_org",
"password": "[hidden]",
"database": "blog_example_org"
}
},
And be sure to replace host, user, password, and database accordingly.
Configure virtual host for Ghost
CyberPanel configures websites with the intent of running a PHP-based website, which is not the case for our Ghost blog. In light of this, all of the PHP references can be removed.
Go to Websites > List Websites > blog.example.org > Manage and click on the vHost Conf button.
Here is what’s left after cleaning it up. Note that the adminEmails, keyFile, and certFile directives are unique to you, so don’t copy everything verbatim.
docRoot $VH_ROOT/public_html
vhDomain $VH_NAME
adminEmails [email protected]
enableGzip 1
enableIpGeo 1
errorlog $VH_ROOT/logs/$VH_NAME.error_log {
useServer 0
logLevel WARN
rollingSize 10M
}
accesslog $VH_ROOT/logs/$VH_NAME.access_log {
useServer 0
logFormat "%h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i""
logHeaders 5
rollingSize 10M
keepDays 10
compressArchive 1
}
extprocessor ghost {
type proxy
address localhost:2368
maxConns 100
pcKeepAliveTimeout 60
initTimeout 60
retryTimeout 0
respBuffer 0
}
context /.well-known/acme-challenge {
location /usr/local/lsws/Example/html/.well-known/acme-challenge
allowBrowse 1
}
context / {
type proxy
handler ghost
addDefaultCharset off
}
vhssl {
keyFile /etc/letsencrypt/live/blog.sechu.us/privkey.pem
certFile /etc/letsencrypt/live/blog.sechu.us/fullchain.pem
certChain 1
enableECDHE 1
renegProtection 1
sslSessionCache 1
enableSpdy 15
enableStapling 1
ocspRespMaxAge 86400
}
module cache {
storagePath /usr/local/lsws/cachedata/$VH_NAME
}
Additional Ghost blogs
If this isn’t your first Ghost installation on the server, there are three additional things you need to change in the vHost configuration above.
(1) Set a unique extprocessor
name (previously ghost
) so it’s different from your first installation. For example, add a keyword that defines your particular site: ghost-example
.
(2) Update the port number (previously 2368
) to whatever you chose during the installation with the --port
flag. I used 2369
in my example.
extprocessor ghost-example {
type proxy
address localhost:2369
maxConns 100
pcKeepAliveTimeout 60
initTimeout 60
retryTimeout 0
respBuffer 0
}
(3) Update the context
handler (previously ghost
) to match the extprocessor
name from step 1. This would be ghost-example
in our case.
context / {
type proxy
handler ghost-example
addDefaultCharset off
}
Visit Ghost blog
Your Ghost blog should now be up and running. Finish setting up Ghost by visiting the Ghost admin by appending /ghost
to the URL: https://blog.example.org/ghost.
Troubleshooting
If you don’t see your Ghost blog, a great place to start is the Ghost error log, which can be found at:
/var/www/blog.example.org/content/logs/https___blog_example_org_production.error
Be sure to replace blog.example.org with the domain you chose in both the directory (blog.example.org
) and the file name (blog_example_org
).
That's a wrap. If you have any questions or comments, leave them below.
Featured image by Gabriel Heinzer.