Running Ghost on Scaleway node.js instance with HTTPS

  • Spin up new Node.js instance on Scaleway
  • SSH to your instance and install unzip
  • Create folders and install Ghost
  • Set up NGINX to serve through port 80
  • Set up forever and run Ghost under non-root user
  • Setting up HTTPS with Letsencrypt and non-WWW redirect
  • Auto-renewal of Letsencrypt certificate.

Unix commands used in this tutorial: cd, pwd, mkdir, touch, apt-get, wget, vim.

When going trough the tutorial, don’t just copy paste the commands, try to understand them and make sure you are using correct paths when executing. Replace with your own domain name in all commands and config files.

Spin up new Node.js instance on Scaleway

To connect to your instance with SSH, you will need to add your public key to Scaleway, just like for any other cloud provider. Generating the key in not in the scope of this tutorial.

Open your favorite terminal and let’s get started.

On macOS by default, the key generated with ssh-keygen is saved to file id_rsa id .ssh folder. To copy the key to your clipboard use this command and then add your key to credentials to your Scaleway profile. (

pbcopy < ~/.ssh/  

On Scaleway select create server and under ImageHub select Node.js. Click create and you should end up on you server summary page. You will probably need to wait a couple of minutes for your instance to boot up. Find your instance public IP and SSH from your local computer to that IP.

BTW; Scaleway also provides Ghost image, which you can use instead of Node.js, but I have no idea what you get with it.

SSH to your instance and install unzip

ssh root@  

If you are connecting to this IP for the first time you will need to add the ECDSA key to the list of knowns host. You will see a screen with some stats about the instance you just connected to.

When you first login in you end up in /root user directory. You can check this with pwd command.

root@node-web-server:~# pwd  

First, let’s make sure package manager is up to date and install unzip package that we will use to unpack our Ghost installation.

root@node-web-server:~# apt-get update  
root@node-web-server:~# apt-get install unzip  

Create folders and install Ghost recommends installing your blog to folder /var/www/. We can create and move to this folder by executing following commands.

root@node-web-server:~# mkdir /var/www  
root@node-web-server:~# cd /var/www  
root@node-web-server:~# pwd  

Make sure you are in /var/www directory with pwd.

I decided to place my blog into another subfolder, because in the future I might run more than one Ghost instance on the same server. I will name the folder ghostprimozxyz

root@node-web-server:/var/www# mkdir ghost_primoz_xyz  
root@node-web-server:/var/www# cd ghost_primoz_xyz/  

OK. Now with the folder structure in place. Let’s download and unzip Ghost package.

root@node-web-server:/var/www/ghost_primoz_xyz# wget  
root@node-web-server:/var/www/ghost_primoz_xyz# unzip  

This will unzip the content of the page, and your folder should look like this.

root@node-web-server:/var/www/ghost_primoz_xyz# ls  
config.example.js  core              Gruntfile.js  LICENSE              package.json  
content    index.js      npm-shrinkwrap.json  

You can now delete if you like, we don’t need it anymore. All the necessary files are extracted.

Next we need to install all production npm packages. Run the following command. This command will go trough package.json file and install all necessary dependencies needed for the production environment.

root@node-web-server:/var/www/ghost_primoz_xyz# npm install --production  

We also need to create Ghost config file by copying the example file in the folder. This command will create a new file called config.js which will be used by Ghost for configuration. If you mess up this file, you can always bring back the default by making another copy from config.example.js

root@node-web-server:/var/www/ghost_primoz_xyz# cp config.example.js config.js  

Use VIM to open config.js and edit production URL property to match your domain name.

Quick VIM refresh: i and ESC key to switch between insert and command mode. Type :write and :quit to save and exit.

root@node-web-server:/var/www/ghost_primoz_xyz# vim config.js  

At this point, you could start the server, but the site would not be accessible to anyone through a browser for security reasons. By default, Ghost server is running on port 2368 and Scaleway is not letting us access this port. We will use NGINX to proxy traffic to public port 80.

Set up NGINX to serve through port 80

Let’s start by installing NGINX package.

root@node-web-server:/var/www/ghost_primoz_xyz# apt-get install nginx -y  

-y takes care of automatically answering with yes to all questions asked by command line while installation process.

Let’s remove the default NGINX config next. We don’t need it there confusing us in the future.

root@node-web-server:/var/www/ghost_primoz_xyz# rm /etc/nginx/sites-enabled/default  

And then create our own config. You can use and other file name, but make sure you are referencing to correct file while following the tutorial.

root@node-web-server:/var/www/ghost_primoz_xyz# touch /etc/nginx/sites-enabled/ghost-primoz-xyz.conf  

Next let’s edit our new config file with the following configuration.

root@node-web-server:/var/www/ghost_primoz_xyz# vim /etc/nginx/sites-enabled/ghost-primoz-xyz.conf  

Make sure to change the property server_name to your own domain. We are not adding here. We will deal with proper www subdomain handling later on. But this is the config that will get you started.

server {  
    listen 80;
    location / {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host      $http_host;
    location ~ /.well-known {
        allow all;
        root /var/www/letsencrypt;

When pasting this snippet make sure you don’t miss the first character s in server. After all the editing and hopeful successfully exiting VIM we can restart NXING.

root@node-web-server:/var/www/ghost_primoz_xyz# service nginx restart  

If all went well out site should be accessible by the IP and port 80. But not before starting the Ghost server with

root@node-web-server:/var/www/ghost_primoz_xyz# npm start --production  

But let’s not stop here. Shut down the server with ctr+c. We will finish this by adding npm package forever and running the server from different unix user. This will have a couple of benefits. Security and you can run more than one Ghost site on the same cloud instance.

Set up forever and run Ghost under non-root user

Check out forever github page for more info.

Install it globally with the following command.

root@node-web-server:/var/www/ghost_primoz_xyz# npm install -g forever  

Adding new user and giving correct privileges. I chose the name ghost-primoz for my user. Feel free to use anything else.

root@node-web-server:/var/www/ghost_primoz_xyz# adduser --shell /bin/bash --gecos 'Ghost application for' ghost-primoz  
root@node-web-server:/var/www/ghost_primoz_xyz# chown -R ghost-primoz:ghost-primoz .  

Note the dot at the end of the command. This is giving privileges to current directory. Make sure you are using correct path here, you want to give privileges to correct folder.

Switch to ghost-primoz user by executing:

root@node-web-server:/var/www/ghost_primoz_xyz# su - ghost-primoz  

You are now logged in non root account, SWEET. Start Ghost by moving to /var/www/ghost_primoz_xyz folder and setting NODE_ENV variable to production and starting forever.

ghost-primoz@node-web-server:/root$# cd /var/www/ghost_primoz_xyz/  
ghost-primoz@node-web-server:/var/www/ghost_primoz_xyz$# NODE_ENV=production forever start index.js  

You can use forever list, forever stop {id} to manage forever processes.

Type exit to logout of ghost-primoz account back to root. Don’t worry your Ghost node process is still up and running.

We are all set up and ready to start with the next section.

Setting up HTTPS with Letsencrypt and non WWW redirect

You need to setup your DNS records to correctly point to your public Scaleway instance IP. Use type A DNC record.

At this point you blog should be accessible by visiting you domain name: in my instance. If you just edited your DNS settings give it a couple of minutes to update.

If we want to take advantage of Certbot’s automatic renewal we have to use --webroot method for creating the certificate. Because Letsencrypt certificate expire after 90 days we need to automatically renew them. Renewing them will be a lot easier if automated. When creating and renewing the certificate Letsencrypt is checking if you have control of the server and domain. They do this by checking against file in .well-known folder in the root of your domain.

Download package to root user folder. This is done by running.

root@ghost-web-server:~# cd /root  
root@ghost-web-server:~# wget  
root@ghost-web-server:~# chmod a+x certbot-auto  

Let’s install it with

root@ghost-web-server:~# ./certbot-auto  

Create letsencrypt folder in /var/www/. This is where certbot will create temporary file used for verification.

root@node-web-server:~# mkdir /var/www/letsencrypt  

Next we need to configure NGINX to serve .wel-known folder through our domain.
Update NGINX config file to support only https and redirect http to https.

Create the certificate. Use your own domain name. If you used NGINX config from step Set up NGINX to serve through port 80 everything shoudl go smoothly.

root@node-web-server:~# ./certbot-auto certonly --webroot -w /var/www/letsencrypt/ -d -d  

Next we will update our NGIX config to redirect all traffic from http to https and from to without WWW.

root@ghost-web-server:~# vim /etc/nginx/sites-enabled/ghost-primoz-xyz.conf  

Again when copying this config make sure to change servername and all sslcertificate paths.

server {  
    listen 443 ssl;
    ssl_certificate     /etc/letsencrypt/live/;
    ssl_certificate_key /etc/letsencrypt/live/;
    access_log /var/log/nginx/;
    location / {
        proxy_set_header   X-Real-IP $remote_addr;
        proxy_set_header   Host      $http_host;
        proxy_set_header   X-Forwarded-Proto $scheme;
    location ~ /.well-known {
        allow all;
        root /var/www/letsencrypt;
server {  
    listen 443 ssl;
    ssl_certificate     /etc/letsencrypt/live/;
    ssl_certificate_key /etc/letsencrypt/live/;
    access_log /var/log/nginx/;
    return 301 $scheme://$request_uri;

server {  
    listen 80;
    return 301$request_uri;

    location ~ /.well-known {
        allow all;
        root /var/www/letsencrypt;


Restart nginx with:

root@node-web-server:/var/www/ghost_primoz_xyz# service nginx restart  

We also need to update our ghost config file with new HTTPS URL.

root@node-web-server:~# vim /var/www/ghost_primoz_xyz/config.js  

And change production.url property to note the s there.

Auto renewal of letsencrypt certificate.

Check if auto renewal works by dry running wit this command.

root@node-web-server:~# ./certbot-auto renew --dry-run  

Create file in /etc/cron.houry

root@node-web-server:~# touch /etc/cron.hourly/certboot-auto-renew  
root@node-web-server:~# vim /etc/cron.hourly//certboot-auto-renew  

And paste this in.

~/certbot-auto renew --quiet --no-self-upgrade