how to setup a self-hosted ghost blog » the essentials
When the dust settled, Ghost had won the battle of the blogging platforms 🎉. Choosing Ghost as my blogging platform, meant that self-hosting the blog would be the most cost-effective and scalable way to get started. For $5 per month, I could rent a virtual private server, use it to host my Ghost blog, and integrate the blog with essential tools that are simple, open-source and free, or tools that are free to start with and affordable to scale. Also, if my server resource needs change throughout the life of the blog, the virtual private server can be easily resized by upgrading or downgrading the pricing plan.
And, so I dove headfirst into the wonderful world of server administration. There were a ton of helpful guides online, but to get my desired configuration, I mixed and matched different resources. I also documented the entire process, so if you are interested in a similar setup, a complete step-by-step guide is included below to get you started.
If you are interested in using Ghost, but would rather have someone else take care of maintaining the server, you can check out one of the Ghost(Pro) managed hosting plans, which currently start at $9 per month.
IN THIS ARTICLE
- Register Custom Domain Name
- Choose Cloud Hosting Provider
- Configure Linux Server Running Ubuntu 22.04 LTS
- Setup DNS Records with Cloud Hosting Provider
- Test SSH Connection to Linux Server
- Install Ghost and Required Prerequisites
- Setup Access to Ghost Admin Panel
- Configure Ghost and Nginx to Redirect Non-Standard Site Requests
- Configure Website to Use Cloudflare (Optional)
- Setup Mailgun and Integrate Mailgun with Ghost (Optional)
- Customize Ghost Theme and Adjust Settings
- Keep Server and Apps Updated
1. Register Custom Domain Name
The domain name will be your unique name or identity on the Internet. It is the address at which users will be able to access your website by typing it into the browser's address bar.
To get started, you will need to purchase an available domain name from a domain registrar. There are many companies to choose from, but be wary of domain registrars that offer a deep discount for the first year to lure you in, and then charge much higher annual renewal prices.
Two registrars I've come across so far that seem to offer transparent, affordable pricing, and also include free domain privacy protection services are Squarespace Domains (previously Google Domains) and Cloudflare Registrar.
You can refer to HubSpot's guide to choosing a domain for information on best practices for choosing an effective domain name.
2. Choose Cloud Hosting Provider
The cloud hosting provider is the company that will host your web server, which is the actual computer on which all the files for your website are stored. The web server allows your website to be accessed from anywhere on the Internet.
Two of the popular cloud hosting providers for self-hosted Ghost blogs are Linode and Digital Ocean. Both offer a $5 per month plan that meets the minimum system requirements for hosting a Ghost blog. However, I currently use Linode.
I am a fan of the Linode StackScript system which allows users to schedule a custom shell script to be executed when a new server instance is first booted up. The script can contain commands that automatically configure and secure a new server. Once the script has been created, this feature is a huge time saver, both for the initial setup and in future cases where you may need to spin up a new server quickly because something went wrong.
To try out Linode's services, you can use my referral link to sign up for a new account. Once a valid method of payment is added to your account, you will receive $100 free credit which can be applied to any services used within the first 60 days. (Disclaimer: Once you spend $25 on Linode services and after you have been an active customer for 90 days, I will receive a $25 account credit as a thank you for the referral. This comes at no additional cost to you. 😊).
The rest of this guide assumes that you will be using Linode. The exact steps may differ for other cloud hosting providers.
If you run into any issues with creating an account, refer to the official Linode guide for Getting Started on the Linode Platform. Complete the steps in Section 1: Sign Up for an Account, and then continue on to Step 3 below, to create and configure your Ubuntu server.
3. Configure Linux Server Running Ubuntu 22.04 LTS
The officially recommended technology stack for self-hosting Ghost in a production environment includes an Ubuntu server with at least 1 GB of memory. Ghost currently supports Ubuntu 16.04, 18.04, 20.04 and 22.04 LTS.
The below steps will enable you to setup and secure a server running Ubuntu 22.04 LTS using a custom Linode StackScript to complete the initial configuration.
- Once your Linode account is created, log in to the Cloud Manager.
- Select StackScripts from the left navigation menu, and click on the blue Create StackScript button in the upper right hand corner of the StackScripts page. See Linode's Create a StackScript guide for a detailed walkthrough of the process.
- A sample custom script, partly based on the Linode guide for Setting Up and Securing a Compute Instance, is included at the end of this blog post. You can copy and paste, and then edit and save this script, or write your own from scratch. If you are using a different cloud hosting provider you may be able to adapt the script, or manually enter the commands to get the same configuration.
- Once the StackScript is created, click on the blue Create button at the top of the page, and select Linode from the drop-down menu.
- On the Linodes/Create page, select the StackScripts tab.
- On the Create From: page, select the Account StackScripts tab (default selection) and select the configuration script that you just created above in order to use it for deploying your new Linode instance.
- Scroll down to the StackScript Advanced Options section, and edit all the given fields. All fields are required to complete the initial configuration progress.
- Hostname: This is a unique, easy-to-remember name that will be used to identify your server. Example:
ghostserver
- Fully Qualified Domain Name (FQDN): This may be a combination of the above hostname and your custom domain name. It will be mapped to the Linode's IP address and enables the server to be uniquely identified and located within the local network. Example:
ghostserver.yourdomain.com
- System Timezone: This is the server's timezone and will be used to generate timestamps for any system logs. The timezone should be in the format Continent/Region. You can view a list of the valid time zone names in the TimeZone Catalog. Example:
America/Los_Angeles
- Non-Root Username: This will be the name of a limited user account created for everyday use. The user will be granted sudo or administrative privileges that can be activated when needed to complete administrative tasks. Such limited accounts reduce the risk of accidentally making destructive changes to the server. Example:
ghostuser
- Non-Root Password: This is a password to secure the above non-root user account and will be used to elevate privileges when needed. Example:
o9!eyt5$223TJ
- Public SSH Key: The secure shell (SSH) protocol will be used to remotely connect to your linux server in order to perform any administrative tasks. By default, most SSH clients use password authentication, but key-based authentication is deemed to be a more secure alternative. Key-based authentication will be configured by the StackScript, but you will need to provide the script with your public SSH key. If you do not already have one on your local machine, you can generate one by performing the below steps:
- Using a Terminal on Linux or MacOS, or PowerShell on Windows, enter the following SSH keygen command at the prompt and press the Return or Enter key. Replace
ghostuser@work-computer
with your own comment.# Generate SSH key pair ssh-keygen -t rsa -b 4096 -C ghostuser@work-computer
- Press Enter to use the default file names for saving the SSH key files. If you already have SSH keys stored on your computer, then you may want to specify a different file name to prevent the existing key files from being overwritten.
- As an extra layer of security, you can provide a passphrase to encrypt your private key so that if your local machine is compromised, the keys cannot be used without also knowing the passphrase.
- Once the key pair has been generated, use the following command to output the contents of the public key file, where id_rsa.pub is the name of the public key file. Then copy and paste the text output into the Public SSH Key field in Linode.
# Print out contents of public key file cat ~/.ssh/id_rsa.pub
- Using a Terminal on Linux or MacOS, or PowerShell on Windows, enter the following SSH keygen command at the prompt and press the Return or Enter key. Replace
- Hostname: This is a unique, easy-to-remember name that will be used to identify your server. Example:
- Fill in the other standard fields:
- Select an Image: From the Images dropdown menu, select the Linux distribution to use. The StackScript provided was created specifically for Ubuntu 22.04 LTS so that will be the only option listed here, but the image can be changed by editing the original StackScript on the StackScripts page.
- Region: Choose a Region that is closest to either you or your users. If you are unsure which to choose, you can also review the Linode guide for How To Choose A Data Center.
- Linode Plan: Select a Linode Plan. There is also a guide for How to Choose a Linode Plan, but the $5 per month Nanode 1 GB plan, with 1GB RAM, 1 CPU and 25GB Storage, should be sufficient to get started. This plan is found under the Shared CPU tab.
- Linode Label: Give your Linode a label to help you easily identify it within the Cloud Manager Dashboard.
- Add Tags (Optional): If desired, you can assign a tag such as webserver to the Linode, as another means of identification.
- Root Password: Create a strong password for your Linode's root user. This root password can be used to perform any action on your server, so it should be long, complex, and unique.
- Backups (Optional): You can also enable Backups for an extra $2 per month to help with easily restoring the server if something goes wrong.
- Once all required fields are filled in, then click the blue Create Linode button, in the Linode Summary sidebar on the bottom right side of the screen. You will be redirected to your new Linode’s Summary page which will report the status of your new server as it boots up.
- Take note of the IPv4 address for your new Linode. This will be used to complete Steps 4 and 5 below.
Summary of Tasks Completed by the Sample StackScript:
- Install software updates to apply the latest software patches and bug fixes.
- Set the hostname used to identify your server.
- Update the system's hosts file to associate the fully qualified domain name (FQDN) and local hostname with the IPv4 and IPv6 addresses for the new Linode.
- Set the timezone so that timestamps in log files can match your given timezone.
- Configure automatic security updates using
unattended-upgrades
to keep your server secured.- Configure postfix to locally deliver e-mail for
unattended-upgrades
notifications. - Install and configure the Mutt email client to enable you to read the notification emails.
- Use the below command to test
unattended-upgrades
:
sudo unattended-upgrade -v -d
- The notification email can then be accessed using the command:
sudo mutt -f /root/Maildir
- Use the below command to test
- Configure postfix to locally deliver e-mail for
- Create a limited user account with the non-root username and password provided.
- Harden SSH access by disabling root login over SSH, disabling SSH password authentication, and setting up key-based authentication for the non-root user over IPv4 only.
- Install and configure Fail2Ban to ban IP addresses after too many failed SSH login attempts.
- Configure the firewall to only allow incoming SSH connections.
4. Setup DNS Records with Cloud Hosting Provider
DNS records are files which facilitate the correct handling of requests for resources on the Internet by providing essential information about domain names. These records are stored on authoritative domain name servers. DNS records help with functions such as translating a domain name into the IP address for the server where the website's files are stored, so that queries can be routed to the correct server.
In this step you will be creating the DNS records and updating the settings required to route requests for yourdomain.com
to the Linode server you configured in Step 3.
4.1 Add Your Domain to the Linode DNS Manager
Select Domains from the left navigation menu, and click on the blue Create Domain button to add the domain you registered in Step 1, as a primary zone. Refer to Linode's guide to Create A Domain.
4.2 Add A and CNAME DNS Records
Add the below two DNS records. Refer to the Linode guide to Manage DNS Records.
- Create an A Record for the root domain
yourdomain.com
, by entering @ as the Hostname, and linking it to the IPv4 address for your server. This will allow Internet traffic to reach your server and is also required in order to have SSL configured as part of the Ghost setup process. - Create a CNAME Record for
www.yourdomain.com
as an alias for the A record, by entering www as the Hostname, and directing it to the A record by entering @ in the Alias to field. This will later allow us to redirect traffic fromwww.yourdomain.com
toyourdomain.com
.
4.3 Setup Your Domain To Use Linode's Name Servers
Go to your domain registrar's website and change the name servers for your domain to use Linode's name servers instead. Refer to the Linode guide to Configure Your Domain's Authoritative Name Servers for a list of the Linode name servers to use. The update could take up to 24 hours to complete, but is typically resolved within a few minutes.
5. Test SSH Connection to Linux Server
Using a Terminal on Linux or MacOS, or PowerShell on Windows, run the following command to connect to the server via SSH from your local machine using public key authentication. Replace ghostuser
with your non-root username, and replace 45.182.126.204
with the IPv4 address for your Linode server.
-
If there is only a single SSH key on your machine, run the below command:
ssh [email protected]
-
If you have multiple SSH keys stored on your machine, you may also need to specify the path of your private key using the
-i
flag:ssh -i /path/to/private/key [email protected]
If prompted, enter your passphrase, and press the Enter key. You should then see the connection established in your local terminal.
6. Install Ghost and Required Prerequisites
The Ghost website provides clear setup instructions in their How to install Ghost on Ubuntu guide. We will be using that guide as the main reference for this step, but a summary of the steps is included below, along with any additional tasks or points to note.
6.1 Prepare Server for Ghost Installation
-
Create a new user: Completed by the StackScript in Step 3, as part of the initial server configuration. -
Update packages: Completed by the StackScript in Step 3, as part of the initial server configuration. -
Install NGINX (minimum of 1.9.5 for SSL): Once installed, type your domain into the browser and confirm that you can see the "Welcome to nginx!" page before proceeding. This is to confirm that the DNS is updated.
-
Install MySQL 8.0:
-
Once installed, set the
root
password in order to secure the initial MySQL account that was created during the installation process. In MySQL 8.0,caching_sha2_password
is the default authentication plugin (notmysql_native_password
). Therefore, no password is set initially. Use the below commands to set theroot
password:# Confirm that the mysql service is running systemctl status mysql # Connect to the MySql server as `root` using no password sudo mysql -u root --skip-password # At the mysql prompt, assign a password for `root` ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'root-password'; # Exit MySql exit
-
Run the MySQL security script using the below command to help improve the security of the database.
sudo mysql_secure_installation
-
Respond to the questions when prompted:
- Yes, to setup the validate_password component.
- Select the STRONG level for the password validation policy.
- No, to changing the password for root, if the estimated strength is 100 (strongest rating). Otherwise, select Yes, to change the root password. The script validates the existing root password, if already set.
- Yes, to remove anonymous users.
- Yes, to disallow remote root login.
- Yes, to remove test database and access to it.
- Yes, to reload privilege tables now so that above changes take effect.
-
Permit both IPv4 and IPv6 connections on all server host IPv4 and IPv6 interfaces.
# Open the MySQL server option file sudo nano /etc/mysql/my.cnf # Add the following two lines. [mysqld] bind_address = 127.0.0.1,::1 # Close and save the file # Restart the MySQL server sudo systemctl restart mysql
-
-
Install Node.js 18.x (Node v18 Hydrogen LTS): Be sure to install the recommended version.
6.2 Install Ghost-CLI
- You may need to update npm to the latest version.
6.3 Install Ghost:
- When prompted, specify your blog URL in the form
https://yourdomain.com
. - Accept all other recommendations, including setting up SSL.
7. Setup Access to Ghost Admin Panel
If the Ghost installation was successful, you should receive the following message:
------------------------------------------------------------------------------
Ghost was installed successfully! To complete setup of your publication, visit:
https://yourdomain.com/ghost/
Go to https://yourdomain.com/ghost/
and follow the onscreen instructions to setup your Ghost admin account.
8. Configure Ghost and Nginx to Redirect Non-Standard Site Requests
If the official domain given for the blog is https://yourdomain.com, then if a user enters http://yourdomain.com or http(s)://www.yourdomain.com, they should ideally be redirected to the official domain, https://yourdomain.com.
Why use HTTPS? Using HTTPS as the default protocol and redirecting requests from HTTP to HTTPS is done to improve website security. It will also prevent certain browsers from displaying a "Not Secure" warning to your site's visitors.
Why redirect www URLs to non-www URLs? One reason is to permit the use of both URLs. This ensures that users reach your site whether or not they include www in the URL. The second reason is to help prevent the same web page from being indexed multiple times by search engine crawlers, if both versions (www and non-www) are allowed. Multiple indexes for the same page may cause your website to be considered a duplicate or spam site, which can hurt SEO rankings. For this reason, Ghost itself is designed to only ever have one domain pointed at it.
The below steps to redirect the requests are based on the SSL guide from the Ghost-CLI Knowledgebase and includes an extra step for the HTTP redirects. These steps assume that the non-www-prefixed domain is your official domain. If the www-prefixed domain is your official domain, then adjust all relevant steps accordingly.
8.1 Obtain an SSL Certificate for the www-Prefixed Domain
# Navigate to the ghost site directory created in Step 6.3
cd /var/www/sitename
# Temporarily change the ghost URL to your secondary URL.
# Note: The secondary URL should already have a DNS CNAME record
# pointing to the A record (both created in Step 4.2).
ghost config url https://www.yourdomain.com
# Get Ghost-CLI to generate an SSL setup for you.
ghost setup nginx ssl
# Change your URL config back to your official or canonical domain.
ghost config url https://yourdomain.com
8.2 Redirect Requests for the HTTP and www-Prefixed URLs to the HTTPS Non-www-Prefixed URL
Edit the first location block in each of the nginx config files for your unofficial domains to redirect any requests to your official HTTPS domain. The actual files are located in the /etc/nginx/sites-available
folder (NOT /etc/nginx/sites-enabled
— the sites-enabled
folder contains symbolic links to sites-available
).
# Open the config file for www-prefixed HTTP requests
sudo nano /etc/nginx/sites-available/www.yourdomain.com.conf
# Replace the content of the first location block with:
return 301 https://yourdomain.com$request_uri;
# Close and save the file
# Open the config file for www-prefixed HTTPS requests
sudo nano /etc/nginx/sites-available/www.yourdomain.com-ssl.conf
# Replace the content of the first location block with:
return 301 https://yourdomain.com$request_uri;
# Close and save the file
# Open the config file for non-www-prefixed HTTP requests:
sudo nano /etc/nginx/sites-available/yourdomain.com.conf
# Replace the content of the first location block with:
return 301 https://yourdomain.com$request_uri;
# Close and save the file
8.3 Update Nginx SSL Config (Optional)
-
Run the below command to open the SSL config file:
sudo nano /etc/nginx/snippets/ssl-params.conf
-
Edit the below two lines:
- SSL Protocols:
- REPLACE
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
- WITH
ssl_protocols TLSv1.2 TLSv1.3; # Drop TLSv1 TLSv1.1; Add TLSv1.3
- REPLACE
- SSL Prefer Server Ciphers:
- REPLACE
ssl_prefer_server_ciphers on;
- WITH
ssl_prefer_server_ciphers off; # Turn off
- REPLACE
- SSL Protocols:
-
Reference: https://ssl-config.mozilla.org/
8.4 Apply Updated Nginx Configuration Files
# Get nginx to verify that there are no syntax errors in your config file.
sudo nginx -t
# Reload nginx with your new config.
sudo nginx -s reload
9. Configure Website to Use Cloudflare (Optional)
Cloudflare helps with optimizing speed, security and scalability by mitigating DDoS attacks and by adding a global CDN and/or caching layer in front of your Ghost server.
To get started with Cloudflare, visit the Cloudflare Fundamentals Setup Guide and work through their guides for setting up your free Cloudflare account.
IMPORTANT NOTE:
As part of adding a site to Cloudflare, you will work through their Quick Start Guide. If you choose to enable the Cloudflare "Always Use HTTPS" setting (recommended) then you will run into issues with the Let's Encrypt SSL certificate auto renewals. By default, the renewal service uses the http-01 challenge which requires access to port 80 (HTTP port) on the server.
To prevent the http-01 challenge
from failing, use Cloudflare's Creating Page Rules guide to create a page rule that permits Cloudflare to access the challenge URL over HTTP. The HTTP redirects configured at the server level will not cause issues since the server itself is still setup to listen on port 80.
See below for recommended page rule settings:
If the URL matches:
*yourdomain.com/.well-known/acme-challenge/*
Then the settings are:
SSL
--> Flexible
10. Setup Mailgun and Integrate Mailgun with Ghost (Optional)
If you have no interest in starting a newsletter or in allowing readers to subscribe to your blog, then you can turn these features off in Ghost and safely skip the steps for setting up Mailgun. To turn off these features:
- Go to Settings -> (Members) Membership. Under Subscription Access choose Nobody to disable all member features, including newsletters. Once disabled, click the Save button at the top of the page.
- Go to Settings -> (Members) Email newsletter and use the toggle button to turn off Enable newsletter sending. Once disabled, then click the Save button at the top of the page.
However, if you are interested in starting a newsletter or in allowing readers to subscribe to your Ghost blog, then the rest of Step 10 will get you setup.
Mailgun is primarily an email delivery service. Ghost requires that you use Mailgun for bulk email delivery if you will be starting a newsletter using Ghost's built-in email newsletter feature. Mailgun can also be used to send transactional emails, such as emails for new member signups and member login links, but you have the option of using any other email delivery service for this purpose, including Amazon SES and SendGrid.
10.1 Setup Mailgun and Add Your Domain
To setup Mailgun to use your custom domain:
10.2 Use Mailgun for Transactional Emails
To configure Ghost to use Mailgun for transactional emails:
- Open the Ghost configuration file saved in the Ghost directory.
sudo nano /var/www/sitename/config.production.json
- Locate the SMTP credentials in Mailgun for your custom domain. Refer to the SMTP Credentials section of this guide.
- Update the Ghost mail configuration as shown here in the Mail config documentation for a secure SMTP connection.
- Use one of the ports supported by Mailgun. Mailgun's servers listen on ports 25, 465 (SSL/TLS), 587 (STARTTLS), and 2525.
- Use
port: 2525
to get setup quickly. - Linode restricts ports 25, 465 (SSL/TLS) and 587 (STARTTLS) by default to prevent spam. You can submit a support ticket to open one of these ports, but Mailgun also supports the use of port 2525 which is a mirror of port 587.
- Use
- Change the
secure
value tofalse
, if you are not using port 465.true
will cause email sending to fail. - Obtain the auth
user
andpass
from the Mailgun SMTP credentials page. - Save the changes.
- Navigate to the Ghost site directory
cd /var/www/sitename
. - Run
ghost restart
for your changes to take effect.
- Use one of the ports supported by Mailgun. Mailgun's servers listen on ports 25, 465 (SSL/TLS), 587 (STARTTLS), and 2525.
10.3 Use Mailgun for Bulk Email Delivery
To configure Ghost to use Mailgun for bulk email delivery:
- In your Ghost Admin panel, navigate to Settings -> (Members) Email newsletter.
- Ensure that the feature is toggled on, and then expand the Email newsletter settings section.
- Copy your Private API Key from your Mailgun profile. Refer to the Primary Account API Key section of this guide. Alternatively, you can use a Domain Sending Key, but that will only allow you to send emails. You will not be able to track open rates via Ghost.
- Add your Mailgun subdomain and the API key to the email newsletter settings for bulk mail, and then Save the settings.
11. Customize Ghost Theme and Adjust Settings
Once logged in to the Ghost admin panel, you can customize your new Ghost publication using the provided setting up guide and tutorials.
While working on the initial setup, you can make your site private by going to Settings -> (Website) General and scrolling to the bottom of the page to enable Make this site private. Once enabled, then click the Save button at the top of the page. While this setting is turned on, a password is required to access the blog's content when you are not logged in to the admin panel.
When ready to launch your website, just disable the Make this site private setting.
12. Keep Server and Apps Updated
Going forward, you will be responsible for keeping Ghost and all the other software on your Linux server up to date.
12.1 Linux Server Updates
- Security Updates: If the Stackscript was used to configure your server then security updates are configured to be automatically installed daily using the
unattended-upgrades
package. - Routine Package Updates: These can be manually updated weekly via the terminal so that you can see exactly what is being updated and actively handle any issues if something breaks.
- To update package lists:
sudo apt-get update
- To install new packages:
sudo apt-get upgrade
- To update package lists:
12.2 Ghost Updates (Minor)
-
Every 1-2 weeks, Ghost typically releases minor updates and you can run the ghost update command to upgrade to new versions of Ghost.
# Navigate to the ghost site root directory cd /var/www/sitename # Ensure your CLI is up to date sudo npm install -g ghost-cli@latest # Start the update process ghost update
-
If you encounter file permission issues, then you may need to run the following command from the site root directory to update the permissions. This error typically occurs if you have installed another theme in addition to the default theme.
sudo find ./content/themes/ -type d -exec chmod 775 {} \;
sudo find ./content/themes/ -type f -exec chmod 664 {} \;
12.3 Ghost Upgrades (Major)
-
Every 12-18 months, Ghost releases a new major version which breaks backwards compatibility and requires a more involved upgrade process, including backups and theme compatibility tests. For a smooth upgrade experience, use the How to update Ghost guide.
-
If running
ghost update
gives an error, try usingghost run
to debug the error.
Now What?
That's it (for now)! If you have made it this far, then Congratulations! on successfully setting up your new Ghost blog.
Appendix: Ghost Server Initial Configuration Script
#!/bin/bash
set -eu -o pipefail
# Basic StackScript for deploying and configuring a new Linode.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
#
#
# This script runs through Linode's guide for "Setting Up and Securing a
# Compute Instance" in order to complete the initial configuration of an
# Ubuntu server. It also configures automatic security updates, postfix
# for local mail delivery and the Mutt client for reading emails.
#
# Note: All "Advanced Options" are required fields.
###########################################################
# REQUIRED STACKSCRIPT VARIABLES
###########################################################
# <UDF name="HOSTNAME" label="Hostname" default="ghostserver" example="E.g. example-hostname" />
#
# <UDF name="FQDN" label="Fully Qualified Domain Name (FQDN)" default="ghostserver.yourdomain.com" example="E.g. example-hostname.example.com" />
#
# <UDF name="TIMEZONE" label="System Timezone" default="America/Los_Angeles" example="E.g. America/Los_Angeles" />
#
# <UDF name="USERNAME" label="Non-Root Username" default="ghostuser" example="E.g. nonrootuser" />
#
# <UDF name="USERPASSWORD" label="Non-Root Password" default="" example="E.g. o9!eyt5$223TJ" />
#
# <UDF name="PUBKEY" label="Public SSH Key" default="" example="E.g. ssh-rsa AAAAB3N..." />
###########################################################
# ENABLE LOGGING FOR THE STACKSCRIPT
###########################################################
exec >/root/stackscript.log 2>&1
###########################################################
# INSTALL SOFTWARE UPDATES
###########################################################
echo "Installing initial software updates ..."
apt-get update
apt-get --with-new-pkgs --assume-yes upgrade
###########################################################
# SET THE HOSTNAME
###########################################################
echo "Setting the hostname ..."
hostnamectl set-hostname "${HOSTNAME}"
###########################################################
# UPDATE YOUR SYSTEM'S HOSTS FILE
###########################################################
echo "Updating the system's hosts file ..."
# Extract the IPv4 and IPv6 addresses for the new Linode.
IPADDR4=$(hostname -I | awk '{print $1}')
IPADDR6=$(hostname -I | awk '{print $2}')
# Edit the system's hosts file
sed --in-place=.bak "/^127.0.0.1.*/a $IPADDR4 $FQDN $HOSTNAME\n$IPADDR6 $FQDN $HOSTNAME" /etc/hosts
###########################################################
# SET THE TIMEZONE
###########################################################
echo "Setting the timezone ..."
timedatectl set-timezone "${TIMEZONE}"
###########################################################
# CONFIGURE AUTOMATIC SECURITY UPDATES
###########################################################
echo "Configuring automatic security updates ..."
# Install unattended-upgrades package
apt-get --assume-yes install unattended-upgrades
# Install update-notifier-common package in order to
# automatically reboot when needed
apt-get --assume-yes install update-notifier-common
# Edit the /etc/apt/apt.conf.d/50unattended-upgrades file
# Select which packages should be automatically updated
# No change here: By default, only security updates are automatically installed.
# Allow Email Notification (if you have a mail server set up)
sed --in-place=.bak 's|^/*.*Unattended-Upgrade::Mail ".*|Unattended-Upgrade::Mail "root@localhost";|g' /etc/apt/apt.conf.d/50unattended-upgrades
sed --in-place 's|^/*.*Unattended-Upgrade::MailReport ".*|Unattended-Upgrade::MailReport "on-change";|g' /etc/apt/apt.conf.d/50unattended-upgrades
# Enable Auto-Removal of Unused Dependencies
sed --in-place 's|^/*.*Unattended-Upgrade::Remove-Unused-Dependencies ".*|Unattended-Upgrade::Remove-Unused-Dependencies "true";|g' /etc/apt/apt.conf.d/50unattended-upgrades
# Enable Automatic Reboots
sed --in-place 's|^/*.*Unattended-Upgrade::Automatic-Reboot ".*|Unattended-Upgrade::Automatic-Reboot "true";|g' /etc/apt/apt.conf.d/50unattended-upgrades
sed --in-place 's|^/*.*Unattended-Upgrade::Automatic-Reboot-Time ".*|Unattended-Upgrade::Automatic-Reboot-Time "02:00";|g' /etc/apt/apt.conf.d/50unattended-upgrades
# Enable Automatic Security Update
cp /etc/apt/apt.conf.d/20auto-upgrades /etc/apt/apt.conf.d/20auto-upgrades.bak
cat << EOF > /etc/apt/apt.conf.d/20auto-upgrades
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";
EOF
###########################################################
# ADD A LIMITED USER ACCOUNT
###########################################################
echo "Adding new limited user ..."
# Create a new limited user account with given username and password
adduser "${USERNAME}" --disabled-password --gecos ""
echo "${USERNAME}:${USERPASSWORD}" | chpasswd
echo "Granting user administrative privileges ..."
# Add user to sudo group
usermod --append --groups sudo "${USERNAME}"
###########################################################
# HARDEN SSH ACCESS
###########################################################
echo "Hardening SSH access ..."
# Create SSH directory for sudo user and copy SSH public key
mkdir --parents /home/"${USERNAME}"/.ssh
echo "${PUBKEY}" >> /home/"${USERNAME}"/.ssh/authorized_keys
# Give ~/.ssh directory and authorized_keys files appropriate file permissions
chmod 700 /home/"${USERNAME}"/.ssh
chmod 600 /home/"${USERNAME}"/.ssh/authorized_keys
# If the authorized_keys file was edited as root, then the .ssh folder and the
# authorized_keys file may be owned by root. Set limited user as owner.
chown -R "${USERNAME}":"${USERNAME}" /home/"${USERNAME}"/.ssh
# Disable root logins over SSH (must use limited user account)
sed --in-place=.bak 's/^PermitRootLogin.*/PermitRootLogin no/g' /etc/ssh/sshd_config
# Disable SSH password authentication (must use SSH key authentication)
sed --in-place 's/^PasswordAuthentication.*/PasswordAuthentication no/g' /etc/ssh/sshd_config
# Only allow SSH daemon to listen on one internet protocol
# inet to listen only on IPv4 or inet6 to listen only on IPv6
sed --in-place 's/^#*.*AddressFamily.*/AddressFamily inet/g' /etc/ssh/sshd_config
# Restart the SSH service to load the new configuration
systemctl restart sshd
###########################################################
# USE FAIL2BAN FOR SSH LOGIN PROTECTION
###########################################################
echo "Setting up fail2ban ..."
# Install and configure fail2ban
apt-get --assume-yes install fail2ban
cp /etc/fail2ban/fail2ban.conf /etc/fail2ban/fail2ban.local
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
systemctl restart fail2ban
###########################################################
# CONFIGURE A FIREWALL
###########################################################
echo "Setting up ufw ..."
# Install and configure ufw
apt-get --assume-yes install ufw
ufw default allow outgoing
ufw default deny incoming
ufw allow OpenSSH
ufw enable
###########################################################
# CONFIGURE POSTFIX FOR UNATTENDED UPGRADE NOTIFICATIONS
###########################################################
echo "Setting up postfix ..."
# Install postfix - also allows for local mail delivery
echo "postfix postfix/main_mailer_type select Local only" | debconf-set-selections
echo "postfix postfix/mailname string $FQDN" | debconf-set-selections
echo "postfix postfix/destinations string localhost.localdomain, localhost" | debconf-set-selections
DEBIAN_FRONTEND=noninteractive apt-get --assume-yes install postfix
# Configure postfix to listen only on the local interface
/usr/sbin/postconf -e "inet_interfaces = loopback-only"
# Configure the mailbox format for Maildir vs mbox
/usr/sbin/postconf -e 'home_mailbox = Maildir/'
# Restart the postfix daemon to use the new configuration
systemctl restart postfix
###########################################################
# CONFIGURE MUTT EMAIL CLIENT
###########################################################
echo "Setting up mutt email client ..."
# Install and configure mutt package to read notification emails
apt-get --assume-yes install mutt
cat << EOF > /root/.muttrc
set mbox_type=Maildir
set spoolfile="~/Maildir/"
set folder="~/Maildir/"
set mask="!^\\.[^.]"
set record="+.Sent"
set postponed="+.Drafts"
EOF
###########################################################
# PERFORM STACKSCRIPT CLEANUP
###########################################################
rm /root/StackScript
echo "Server configuration complete ..."
# Restart the server
sleep 5
reboot