I had a need to migrate this server to a different provider for cost reasons. This was also a good chance to document my process since I did NOT do that last time.
I ended up going with Digital Ocean. Amazon, Linode, and Digital Ocean all had the same specs for the same price. Honestly the thing that got me to pick Digital Ocean was simply the number of times I had used their documentation in the past to do server maintenance tasks. Their documentation is always up-to-date, easy to follow, and exactly what I am looking for at the time.
To migrate, I had a handful of tasks I needed to complete:
- Spin up, do basic configuration, and secure the server
- Install/Configure NGINX (reverse proxy)
- Install/Configure MySQL (database)
- Install/Configure Node.JS (Ghost JS engine)
- Install/Configure/Restore Ghost
- Install/Configure/Restore Confluence
- Update DNS for the new server
- Update my backup scripts
Spinning Up The New Server
Spinning up the server in Digital Ocean was super easy. The only thing that I did not like was that if you add an SSH key at deployment, it is for the
root user. I would have liked to be able to kick the server with a non-admin user as well.
I also configured Digital Ocean monitoring for:
- Inbound/Outbound bandwidth over 70Mbps for 5 minutes
- CPU over 70% utilization for 5 minutes
- Disk I/O over 70% utilization for 5 minutes
First thing was to get everything updated:
apt-get update && apt-get upgrade -y
Then set up automatic updates. I followed this article. In Ubuntu 18,
unattended-upgrades is installed by default. In the
/etc/apt/apt.conf.d/50unattended-upgrades file I also uncommented the
updates line. In
/etc/apt/apt.conf.d/20auto-upgrades I just added the two missing lines from their example into my file.
Create Low Privilege User
useradd -m -s /bin/bash trevor # Copy the key I uploaded at creation cp .ssh/authorized_keys /home/trevor/.ssh/ chown trevor:trevor /home/trevor/.ssh/authorized_keys passwd usermod -aG sudo trevor su trevor
Update SSH Config
Port 22 gets scanned and probed like it is going out of style. So I like to avoid that, updating these lines in
# My super secret SSH port (not really) Port 12345 PermitRootLogin no PubkeyAuthentication yes
UFW is installed by default in Ubuntu 18 so I just needed to add some rules. Specifically I want to allow SSH on my port and since I am going to be using Cloudflare, I only want to allow HTTP(S) traffic from their IPs. There is a good script here to populate the Cloudflare IPs.
ufw allow 12345 sudo ufw default deny incoming sudo ufw default allow outgoing sudo ./cloudflare-ufw.sh # Add this line to crontab 0 0 * * 1 root /home/trevor/cloudflare-ufw/cloudflare-ufw.sh > /dev/null 2>&1
The only considerations were to update the SSH port to my custom port, add my home IP (kind of static) to the
ignoreip directive, and change the ban actions to use
SSMTP and Sendmail
I had to install both in Ubuntu 18 with
apt-get install. Then I copied over my config file from the older server in
Installing Required Software
Because I had already done this on my old server, this part was fairly simple.
sudo apt-get install nginx
Copy config from old server in
sudo ln -s /etc/nginx/sites-available/ghost.conf /etc/nginx/sites-enabled/ghost.conf
Generate new cert from Cloudflare Origin Cert under the Crypto tab to enable proper (strict) TLS between Cloudflare and my server. Update NGINX config to use this cert and key by updating any
ssl_certificate_key lines in
/etc/nginx/sites-available/ghost.conf with the appropriate files.
To ensure ONLY Cloudflare can access the server, I also installed the client certificate available here. Place that certificate in
/etc/ssl/certs/cloudflare.crt and add these lines to the appropriate server blocks in
ssl_client_certificate /etc/ssl/certs/cloudflare.crt; ssl_verify_client on;
/etc/nginx/sites-available/ghost.conf to remove TLSv1 and TLSv1.1.
Just followed this guide.
Just follow their guide being sure to follow the steps to install the Build Tools.
Follow the Ghost Install Guide. I did have some issues with files in my home directory not having proper permissions, I think due to installing Node with
chown -R trevor:trevor /home/trevor did the trick.
In the guide, I skipped the UFW section since I already did that. Once the install is complete, login to the Ghost web portal to finish initial configuration. Due to my firewall rules and NGINX config, I had to set up a local SSH tunnel to the internal Ghost instance.
From here I exported the blog content using the native export function in the Labs tab. This export does NOT include images, so that have to be copied separately.
# Copy image to local host scp -r ghost:/var/www/ghost/content/images ./images # Copy inmages from local host to new server scp -r images/ ghost2:~ sudo mv ~/images/* /var/www/ghost/content/images # Reset permissions chown ghost:ghost /var/www/ghost/content
/var/www/ghost/config.production.json with the URL of your blog so that any internal links do not point to
Import the exported JSON content from the old blog and check any warnings that are produced. All of my warnings were benign and all data was properly transferred.
Delete the default Ghost posts.
Under the design tab, ensure that the links for any sections that are configured get updated to the public link instead of the internal link.
I also did a couple tweeks to the base installation.
- Added PrismJS code syntax highlighting per this guide.
- Added Disqus comments per this guide
- Added Table Of Contents to all my posts per this guide. This one required some additional tweaking. In the
toc.jsfile I had to make these changes:
- Line 10: Change value to 0. This seems to go along with including H1 and just makes the output format look correct
- Line 57: Per your comment, added h1 to the list.
- My full section from
post.hbslooks slightly different:
In the old Confluence installation, login as a system admin and create a new backup in the General Configuration > Backup and Restore section and download that to your local host. During the install process, you will be asked for this.
Get the binary download link here.
wget https://www.atlassian.com/software/confluence/downloads/binary/atlassian-confluence-6.11.2-x64.bin chmod +x atlassian-confluence-6.11.2-x64.bin sudo ./atlassian-confluence-6.11.2-x64.bin
During install, all defaults can safely be accepted. Once the installer is complete, install the MySQL drivers. And also take care of Step 2 here.
wget https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.47.tar.gz sudo cp mysql-connector-java-5.1.47/mysql-connector-java-5.1.47-bin.jar /opt/atlassian/confluence/confluence/WEB-INF/lib sudo /etc/init.d/confluence restart
Now browse to the web interface (again needing an SSH tunnel) and follow the setup instructions. On the database configuration page, select MySQL and use the "Connection String" method. Your connection string will look something like this (input proper DB name):
At the step to import from a backup, select the file downloaded earlier.
Login using the old user/pass and go to General Configuration to update the "Base URL". Also follow this article to update the Confluence proxy settings as well.
/opt/atlassian/confluence/conf/server.xml I needed to add
address="ip6-localhost" to the
Connector block to force Confluence to only listen on localhost.
Finally, for my backups I needed to add my standard user to the Confluence group:
sudo usermod -aG confluence trevor.
Follow the guide here to install OSSEC-HIDS as a local installation.
Follow this guide to configure POSTFIX to send OSSEC emails. I also changed
127.0.0.1 so that I did not have an SMTP server listening to the world.
Now that everything was set up and running, time to cut over my DNS configuration. This was amazingly easy: log in to Cloudflare, update IPs...done! The changes were almost immediate.
At this point I needed to move my backup scripts over to the new server, update my NAS
rsync jobs to point to the new IP, and copy over my cronjobs. This also required a new SSH key pair for my NAS.