Hosting a Blog on Azure

Hosting a Blog on Azure

In this tutorial, you will deploy a Ghost blog onto an Azure virtual machine, and directs traffic from a domain name (purchased on Google Domains) to the newly created Azure instance.


  • [Tutorial] Learning by doing. Shameless plug for the professional tutorials made for a product I work on at Microsoft.
  • [which deploys] Deploying is the act of installing, testing and implementing a computer system or application.
  • [Ghost] An open source CMS for professional publishing. Open source meaning Ghost is free code that you can nearly do anything with. CMS meaning content management system, or an elegant way to manage articles online, people who subscribe to those articles, payment for those articles, and lots more:
The CMS for this website—set up with the steps outlined in this tutorial
  • [onto an Azure virtual machine] Another product I work on at Microsoft, a virtual machine (VM) is a way to emulate a computer. Similar to your personal laptop, a VM can start/stop/restart, uses memory/hard drives/processors, and can connect to the Internet. Because VMs are not physical devices, they use different software to keep them up to date, allow them to be accessed via 10s of thousands of people—or less like with this blog ;)—and make it easy to detect issues, and keep things secure.
  • [directs traffic from a domain name] A domain name is a way to make it easy for customers to find a VM on the internet. Rather than remembering, most people just type the domain name  
  • [to the newly created Azure instance] An 'instance' is just another word for a VM.
Do you want to create a website like this one? Do you want to learn about the cloud? Start here.

Getting started

For this tutorial, we will create an Azure virtual machine.

We will then deploy an open source blog platform called Ghost. Ghost has the benefit of being extremely low-code. Once configured, you can log in and visually manage all of your posts, tagging for posts, etc. It enables you to easily collect payment, monitor traffic, and more.

Later in the tutorial, I explore Ghost's theme marketplace and its integrations.

Considerations before using Azure

With robust design tools like Webflow, it is becoming less compelling to need full control over a website's design, deployment, and codebase. At $20 / month / website (or a discounted $16/month if you pay yearly), most individuals should consider building and deploying with Webflow, and not on Azure.

In the world of WYSIWYGs—i.e. "what you see is what you get" no-code website builders—Webflow is amongst the most mature. Before proceeding, consider reviewing some of Chris Do's video tutorials on Webflow to see if it might be an easier option than deploying your own server.


1.  When considering solutions, I am primarily considering cost, simplicity, and ease of maintenance for custom blog setup and deployment. I manage 7 different websites, so am also looking for an affordable solution to maintain them. These sites get cumulatively about 1,000 unique visitors per month, but I'm also considering which solutions can handle increased traffic with time.

2.  Most websites I manage are simple, static HTML—either custom or customized from a template on ThemeForest. GitHub Pages has recently become a strong contendor for hosting static websites. If yours is entirely static, consider this as an option.

3.  Some websites I manage are blogs on the open source platform Ghost—a no-code (or low-code) platform that we will deploy in this tutorial. This article was deployed with Ghost.

4.  Knowledge in Git, Bash, GitHub, and modern web programming (JavaScript, HTML/CSS) is not required, but certainly helps.

Alternatives to Azure VM

Before continuing, it is important to consider some alternative services that might be better fit for your use case:

1.  Azure Container Instance requires that you have Docker experience and package your website(s) up as containers. You publish your container(s) to Azure Container Registry (or Docker Registry). ACI is compelling if you plan to scale your web application later (using AKS or similar). Plans start at about $32 / month when running continuously, and containers have tremendous benefit over a typical virtual machine when it comes to scale. This article also does a great job comparing ACI vs. virtual machines.

2.  Azure Storage with Azure CDN allows you to host any static HTML website at extremely affordable rates, but for any server side code—e.g. a simple contact form or a blog content management system—you'll be out of luck. If you are deploying basic HTML code and nothing else, consider this tutorial.

3.  Azure Virtual Machine Scale Sets allow you to automatically scale instances, but require additional configuration of a load balancer to direct network traffic, and typically recommend automated approaches to building and releasing your code. You can host static or dynamic web applications on a virtual machine scale set, and pricing starts at about $15 / month.

Creating an Azure virtual machine

1.  Go to and create or log in with a Live ID or GitHub account.

2.  Once logged in, search for "virtual machine" in the search bar:


(New users on Azure will be presented with three options, the first of which will require your credit card information prior to proceeding, though you will start with a $200 free credit and Azure will not automatically charge you. Select 'Start', and once you have accepted the terms and conditions, an Azure "subscription" will be created for you and you will be redirected back to the Azure Portal. For a walkthrough, start with these docs. Come back to this tutorial and search again for "virtual machine" once you are signed up.)

4.   This brings a browse view of your existing virtual machines. Select 'Add'

5.  At this point, you'll be prompted for some basic information. You'll want to name your virtual machine, select a Linux image such as Ubuntu 18.04, and select a Size.

6.  For Size, you can sort on cost. For anything less than 2 vCPUs, you'll likely get away with 3-5 users accessing a basic HTML site simultaneously, but will need to increase size should you wish to download large files, packages, or updates on your virtual machine. Select a B2s or higher for minimal disruption. The blogging platform Ghost recommends at least 1GB RAM.

(AWS offers cheaper options using its click-to-deploy option Lightsail, and there are many private hosting providers that will offer considerably cheaper prices.  Microsoft does not win on pricing at the time of this post.)

7.  Ensure SSH is your authentication type and that under Inbound port rules you have HTTP (Port 80) and SSL (Port 443) as allowed. This allows traffic to Port 80 and Port 443 so that your website can be reached at and We'll configure SSL on the server directly later on.

(You can also select Port 22—the port used to connect to an instance via SSH. We will enable this port later on for specific IP addresses by adding a security group rule, rather than expose it to the entire Internet).

8.  Click on 'Review + create', and then 'Create'. You will be prompted to download a private key. Download it and store it in a safe location, as it is how you will access your virtual machine.

Connecting to your virtual machine

9.  Click Go to resource once your deployment succeeds (this should take about 2 minutes).

10.   Select the 'Networking' menu item, and then 'Add inbound port rule'. We'll be adding a rule for Port 22, which will enable us to connect to our instance.

11.  Before adding your inbound port rule, you will need to find your IPv4 address, which you can get by typing 'my IP address' into Google or going to Select Source of type 'IP Addresses', paste it into the 'Source IP addresses/CIDR ranges' field (where I have, and configure the fields as shown below. Then press 'Add'.

12.  Now that you have allowed your network to access your Azure VM, Select the 'Connect' menu item and then the 'SSH' tab.

13.  Open up 'Terminal' on a Mac, 'PowerShell' on a PC, or an SSH client of your choice. Recall where you saved your private key,and navigate to that directory. For example, if your private key is called azureuser.pem and is located in your user's Source folder, you can type on separate lines:

cd ~/Source
chmod 400 azureuser.pem

(You might need to right click and run your application as an Administrator, or type ‘sudo’ prior to setting the above file permissions. The chmod command will grant read access to owners-only.)

14.  Steps 3 and 4 in the Azure portal should walk you through the next command which combines your username and public IP address for the instance in a final command to execute, ultimately granting you access to your virtual machine:

20.  Once you run step 4 (above), you should be successfully connected to your instance!

Next, we'll install the underlying components necessary for a Ghost blog, we'll purchase a domain name, we'll purchase and attach a domain name to your VM, and we'll successfully create your blog!

Configuring a Ghost blog

In this part, we'll install the underlying components necessary for a Ghost blog, we'll purchase a domain name, we'll purchase and attach a domain name to your instance, and we'll successfully create your blog.

1.  Now that you are connected to your instance, it’s time to install NGINX which will route traffic from your IP address to this newly created folder. Within your SSH Shell, type each of the following commands separately and press enter. You will be prompted to proceed, and type ‘Y’.

sudo apt-get update
sudo apt-get upgrade
sudo apt-get install nginx
sudo ufw allow 'Nginx full'

(sudo commands force you into the ‘root’ user, where all changes will be immediate. Proceed with caution.)

2. Voilà! You might not realize it, but your website is live! Simply enter your instance’s public address—found in the connection string you used on step 19, or in the instance view—into a browser window:

3.  (Optional) If you would like to, you can edit the file located at /var/www/html/index.nginx-debian.html, and those changes will be live on the Internet. This will require either learning a text editor like vim (and basic shell navigation commands like cd) or downloading a more robust SSH client like Coda.

Purchasing a domain name

4.  At this point, you will want to purchase a domain name at a domain name registrar like Google Domains. This allows you to share a website address instead of an IP address—e.g. instead of, I can share,, or The nice thing about Google Domains is email forwarding to your personal inbox and domain privacy protection are included in the cost of the domain.

Microsoft only allows businesses to purchase domain names, but Google Domains, GoDaddy, and Uniregistry are good alternatives to consider. Google Domains consistently charges between $9 and $12 per year for a domain name, with certain top level domains—e.g. .blog, .app—costing more. Uniregistry has deals from time to time with domains costing as low as $2 / year.

Find a domain name that is available and make the purchase.

5.  Navigate to your domain’s DNS settings in order to connect it to the new web server. E.g.:

6. Under ‘Custom resource records’, add an A record with the default settings. Enter your instance’s public IP address, and press ‘Add’

7.  Wait for DNS propagation—i.e. for domain name system name servers to update with the IP address/domain name mapping you entered. You can check here to see it happen in real-time.

Setting up your blog

8.  At this point, I’ll be mostly following Ghost’s setup guide, found here. We will be setting up a basic blog at the domain name you directed toward the instance you created.

9.  Back to your terminal, we will be installing MySQL, Node.js, and Ghost CLI. MySQL is the database that hosts your blog content, Node.js manages routing and dependencies for your blog, and Ghost CLI is the underlying platform needed for your blog to run. Re-run the SSH command you retrieved in Step 19 if your SSH session has expired.

(Replace ‘password’ below with a secure password for your database server, and keep it in a safe place)

sudo apt-get install mysql-server
sudo mysql
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'password';

10.  MySQL is installed, next up is Node.js:

(These instructions install Ghost’s recommended version of Node.js)

curl -sL | sudo -E bash
sudo apt-get install -y nodejs

11.  Node.js is installed, next up is the command line tools for Ghost:

sudo npm install ghost-cli@latest -g

The final steps

You have created an instance on an Azure virtual machine, have configured a network interface to allow connections to your instance, have purchased and configured a domain name, and have connected to and installed all of the necessary prerequisites. Now it’s time to get your blog up and running!

12.  If your SSH session has expired, re-run the SSH command you retrieved in Part 1 of the tutorial. Next, you’ll decide where to setup your blog. I decided to place all my websites in a folder located at /var/www/blog. Go ahead and run the following commands to set up your file directory.

(Recall that the SSH connection string you used contains your username—azureuser in our example. When executing the command below, you will be granting directory ownership to your username. If you chose something other than azureuser for the username, update the second command.)

sudo mkdir -p /var/www/blog
sudo chown azureuser:azureuser /var/www/blog
sudo chmod 755 /var/www/blog
cd /var/www/blog

(chmod 755 will grant read/execute access for users and is typically used for public directories.)

13.  Install Ghost

ghost install

14.  When prompted for your blog URL, enter the domain name you purchased earlier.

15.  Next, you will be prompted with several questions.

(Retrieve your MySQL password and enter when prompted, and enter your personal email address when prompted. Other configurations can match what’s entered in blue below.)

16.  Voilà! You should now see the default Ghost blog when you visit your domain name:

Considerations post-creation

In this section, we'll cover more about Ghost and will configure a free SSL certificate for any static websites you own.

Theming, blog advice, and Ghost tips & tricks

1.  Go to in order to configure setup. Here, you’ll create a site title and the administrator user for your blog. Moving forward, will be where you go to login, update designs, add blog entries, etc.

2.  We’ll leave it to Ghost to teach you how to use their platform. I'll cover some highlights beyond the basics. Follow these docs for setting Ghost up.

Themes –just download the ZIP and import it on the Design menu item. You can always customize the code yourself directly via SSH or by running Ghost locally.

Integrations – I like Amplitude to track web traffic and users. If you do follow Ghost’s instruction guide for Amplitude, you can actually auto-add events by pasting the following line into your theme’s header (ultimately combining step 5 and 6)


This makes it possible to track full user journies:

Amplitude's robust tracking, easily set up with instructions above

Members – you can set up subscription pricing via Stripe and share Members-only posts.

How to change your configured Ghost site URL

If you're running into issues with a misconfigured protocol or domain, you can update it easily with the ghost config command

ghost config url
ghost setup nginx ssl 
ghost restart

How to configure a free SSL certificate on a (non-Ghost) website

1.  First, ensure you can access your virtual machine and can edit files using vim or via a client like Coda. This will be necessary in later steps.

2.  You must also have a domain name purchased, and the A record of your domain name must be pointed to your instance’s public IP address. We covered this earlier in the tutorial.

3.  You must have also copied your application code to a file location, e.g. to /var/www/siteName by using GitHub or some other means. This is left up to you as to how you copy the files—teaching GitHub is another tutorial but is highly recommended.

4.  Start by accessing your virtual machine via SSH or another means.

5.  Assuming this is a new website and is not live, you will first need to create an NGINX configuration for your site to be live without SSL. If the site is already live on Port 80—i.e. you can access it at—you can move to step 10.

cd /etc/nginx/sites-available

6.  Create a new file and paste in the following, replacing /var/www/sitename with the file path to your website and with your domain name.

server {
	listen 80;
	listen [::]:80;
	root /var/www/sitename;

	index index.html index.htm index.nginx-debian.html;

	location / {
		try_files $uri $uri/ =404;

7.     Create a symlink to enable the site on NGINX. Replace filename with the name of the file you created in step 6.

sudo ln -s /etc/nginx/sites-available/fileName /etc/nginx/sites-enabled/

8.     Check the NGINX configuration to ensure there are no errors

sudo nginx -t

9.     Restart the NGINX service

sudo service nginx restart

10.  At this point, you should be able to access your website via—and changes made to the HTML/application on your server should be reflected on the public Internet. If this is not the case, you will be unsuccessful in generating a certificate for the website. For the coming steps, we will be mostly following CertBot’s setup guide, found here.

sudo snap install core; sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot
sudo certbot --nginx

11.  Below is a summary of the questions asked when you run sudo certbox --nginx, and the success scenario—i.e. where your domain name is live on

12.  Lastly, test automatic renewal by typing

sudo certbot renew --dry-run

Setting up Livepatch for your Ubuntu VM

  1. Go to and create an account (select 'Get Livepatch' and 'Ubuntu customer' assuming you used an Ubuntu virtual machine when creating your VM in Part 1.
  2. Log in and generate your token.
  3. To enable live kernel patches on an Ubuntu machine, open a terminal and enter:
sudo snap install canonical-livepatch
sudo canonical-livepatch enable [TOKEN]