This post is about how I set up nginx and started serving pages over ssl after requesting an SSL certificate using acme.sh

Apart from securing the instance, these instructions assume a fresh install and that I am logged in as user who is a member of the sudo group. If you see a command prefixed with # assume I am either logged in as root, or prefixed the command with sudo.

Requirements

  • This blog is hosted on blog.notlocalhost.dev, modern browsers will not access a .dev top-level domain unless it’s over ssl. So I’m going to need an ssl certificate.
  • I have heard good things about nginx namely that it is less bloatware configuration than apache and that setting up a reverse-proxy (needed for when I host node-express or dotnet-core applications) is easier to setup and configiure. So I’m going to use nginx as the webserver.
  • As free certificates expire after a while I can use either certbot or acme.sh to install and manage the renewal process for me. I am using certbot on a raspberry pi I own but had trouble once before using certbot so I’m going to go with acme.sh
  • As usual I want this process to be as frictionless as possible in terms of maintenance and I want to pay no money for the certificate.

Setup

$ sudo apt update

ufw

ufw helps us open required ports on the VPS. We will need ports 22 (ssh), 80 (http) and 443 (ssl) to be opened. Port 22 is usually opened by default on a VPS. It is recommended to use a custom port for ssh, and the daemon to accept requests only from predefined ip-ranges but that’s another topic on its own.

$ sudo apt install ufw
$ sudo ufw allow 80/tcp
$ sudo ufw allow 443/tcp
$ sudo ufw allow 22/tcp

Now enter $ sudo ufw status to confirm the list of open ports.

nginx

$ sudo apt install nginx

Once it is installed enter command $ sudo systemctl restart nginx.service. You can check the status of the nginx service with # systemctl status nginx.service.

nginx keeps its configuration files in /etc/nginx/. If you look in /etc/nginx/sites-enabled you will see a default file, opening it up we can see the root from which html files are served is /var/www/html. You will also see that the server_name key is set to _, meaning it will bind to any domain reachable on port 80.

My VPS will have multiple domains and subdomains pointing to it. I want to have a seperate folder for blog.notlocalhost.dev as I do for notlocalhost.dev.

Since /var/www/ is a protected folder I need to give the currently logged in user (named casio in this document) write-privileges to this folder.

Enter command

$ sudo chown -R casio:casio /var/www

Next I enter $ mkdir /etc/www/blog.notlocalhost.dev (note since I now have write access to /var/www I do not need to sudo this command). I am also using the domain name for convenience as a way to remember what the folder means - we will see why later.

Enter this folder via your terminal and enter command

$ echo "this is blog.notlocalhost.dev" >> index.html

This will create a test page. If do a $ curl notlocalhost.dev and a $ curl blog.notlocalhost.dev you should by the end of these instructions see different content served.

The reason we first create the site before attempting to issue the certificate is that as part of issuing the certificate the installer will place a challenge-authenticate file on the server and attempt to download the file, proving you are in control of the domain.

In the folder /etc/nginx/sites-available create a file called blog.notlocalhost.dev.conf and use the following contents

server {
        listen 80;
        root /var/www/blog.notlocalhost.dev;
        index index.html;
        server_name blog.notlocalhost.dev;
        location / {
                try_files $uri $uri/ =404;
        }
}

Here, just like in the default file I pointed above there is an entry to serve files in folder /var/www/blog.notlocalhost.dev/ for the domain specified at server_name which is blog.notlocalhost.dev

Next I need to create a simlink to blog.notlocalhost.dev.conf in my sites-available folder in folder /etc/nginx/sites-enabled. Simlinking to a sites-enabled folder makes it easier to disable the site without losing its configuration data. Enter folder /etc/nginx/sites-enabled using your terminal and enter command # ln -s ../sites-available/blog.notlocalhost.dev.conf ./blog.notlocalhost.dev.conf This will create the symlink.

Now I restart nginx with # nginx -s reload. If you need to test the configuration use # nginx -t. Then I entering the both curl commands given above I should get different content.

acme.sh

Of course, I said earlier using a browser to access blog.notlocalhost.dev will not work as top-level .dev domains can only be accessed via https:// so I need to install a certificate and of course tell nginx to listen on port 443 as well.

You do not need to be sudo to use acme.sh (in fact it is not recommended to execute the following commands as sudo).

First we look at install instructions for acme.sh given at acme.sh but we don’t follow them just yet.

After installing the script and creating the alias for it, we’ll create folder /var/www/ssl-certs/blog.notlocalhost.dev where we’ll store our certificates. You can use any folder actually, but it must be readable by the nginx process writable without having to sudo.

Issue the certificates with command $ acme.sh --issue -d blog.notlocalhost.dev -w /var/www/blog.notlocalhost.dev The purpose of the -w parameter is to tell the script where to place a challenge-response file that the certificate-issuer will check is reachable at your site, confirming that you are indeed the owner of the domain. Note that this is same folder we instructed nginx to serve content from earlier. If this is successful the certificates are downloaded to a safe location.

Next we install the certificates which is to say we need to bind the certificate to nginx.

Enter command

$ acme.sh --install-cert -d blog.notlocalhost.dev --key-file /var/www/ssl-certs/blog.notlocalhost.dev/key.pem --fullchain-file /var/www/ssl-certs/blog.notlocalhost.dev/cert.pem

This command will copy the certificate files from the downloaded location to the above folders, as well set up a cronjob to renew the certificates every few weeks. You can check the schedule using $ crontab -e.

Next update the file /etc/nginx/sites-available/blog.notlocalhost.dev.conf so that it looks like

server {
        listen 80;
        listen 443 ssl;

        ssl on;
        ssl_trusted_certificate /var/www/ssl-certs/blog.notlocalhost.dev/cert.pem;
        ssl_certificate /var/www/ssl-certs/blog.notlocalhost.dev/cert.pem;
        ssl_certificate_key /var/www/ssl-certs/blog.notlocalhost.dev/key.pem;

        root /var/www/blog.notlocalhost.dev;
        index index.html;

        server_name blog.notlocalhost.dev;

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

Note the entries regarding the location of the ssl certificates. These locations should be the same used when entering the acme.sh --install-cert command above. Also note new options to tell nginx to listen on port 443.

Finally

Restart nginx with # nginx -s reload. If all is well you should be able to use Firefox to access https://blog.notlocalhost.dev.