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
.