Protecting Your Website With HTTPS (SSL/TLS)

I’ve just released the first tutorialinux ebook. For those of you who want a sneak-peek, this post is basically a command-line-instructions only version of the practical content from the e-book. These instructions will get you a working configuration for serving HTTPS traffic to your website visitors.

Disclaimer: This post leaves out most of the background, theory, explanations, security and performance tuning, and additional considerations like backups, security, etc. All of this extra content is found in the e-book. If you want to support tutorialinux, buying the e-book is a great way to ensure that there’s a constant stream of new content coming out on YouTube and this website.

Okay, that being said, let’s get started!

If you’re a sysadmin, chances are that you feel strongly about the adoption of widespread encryption. Advertising companies, governments, and criminals are trying to track and record every move you and your website visitors make, every interest you show, and every thought you hint at. People are finally beginning to fight back by encrypting web traffic, even for pages that don’t absolutely require it, such as login or payment pages.

In this post, I’ll show you how.

If you run a website, installing an SSL/TLS certificate is one of the most important ways that you, as an average tech person, can help make the web a safer place. This tutorial is a super-short, trimmed-down excerpt from the first tutorialinux e-book, Let’s Encrypt on Linux and FreeBSD with Nginx (A simple HTTPS-enabled NGINX Setup for Linux and FreeBSD servers, using a free Let’s Encrypt Certificate), which will be released this coming week.

In this tutorial, we will configure an nginx server to serve HTTPS requests with a free SSL/TLS certificate signed by Let’s Encrypt. We will also set up a cron job that will automatically renew this certificate each month, so you can more or less forget about it once it’s configured.

Prerequisites

We expect you to already have a running website on the server you’re testing this on. If you don’t, it’s not the end of the world — the default website files which come with an nginx install are enough for testing purposes. However, it makes more sense follow this tutorial on a live website. It is likely that you already have some or all of the required packages installed:

  • nginx, the web server we are going to use
  • python2, to execute the ACME-Tiny script
  • sudo, to allow the server to be reloaded automatically

 

Setting Up A Website: Quick and Dirty

As already mentioned, you need to have a website which you’re serving with nginx. If not, you can check out nginx documentation on how to do this. Quick and dirty instructions:

  1. Create the /var/www/ directory if it doesn’t exist yet ( mkdir -p /var/www )
  2. Move your website directory there ( mv yoursite/ /var/www/ )
  3. Update your nginx configuration to serve them. You can do this in your nginx config file, located at /etc/nginx/nginx.conf or /usr/local/etc/nginx/nginx.conf. Change the ‘root’ directive for your ‘server’ or your ‘location’ block to: root /var/www/yoursite.
  4. Reload nginx ( service nginx reload || systemctl reload nginx )

If following these types of instructions (editing your nginx config file, moving files around, reloading services) make you uncomfortable, you may want to wait before trying these instructions on a live website.

 

Packages

On a recent Debian or Ubuntu install, you can ensure that the required packages are installed by running the following command as root:

apt-get install nginx python sudo wget

If you are running a different Linux distribution, or a BSD Unix system, substitute your own package management syntax for ‘apt-get install’.

 

Disclaimer

Please bear in mind that there is more to securing your nginx configuration. This tutorial focuses exclusively on setting up Let’s Encrypt. It does not focus on securing nginx or your services. It also does not try to keep up to date with mitigations of recent attacks, such as POODLE and others.

The e-book version of this tutorial contains instructions for Linux and FreeBSD, along with much more in-depth explanation of the topics covered and the actions we will take.

That said; let’s get started!

 

The 15-Second-Explanation

  1. First, we’ll create a user whose only job will be managing our certificates and dealing with Let’s Encrypt.
  2. Then, we’ll configure the nginx webserver so that it can respond to ACME challenges from Let’s Encrypt (ACME is the protocol that is used for certificate management).
  3. Next, we’ll create our SSL certificate using a small python script provided by the Let’s Encrypt developers.
  4. Once that’s done, we can configure nginx to use that new SSL certificate.
  5. Finally, we’ll set up automatic certificate renewals, since the Let’s Encrypt certificate authority (CA) provides certificates which are only valid for one month (this makes your life slightly less hellish, should your SSL certificate be compromised and misused).

 

Create a ‘letsencrypt’ User

With the ‘adduser’ command, let’s create a user:

adduser letsencrypt
Adding user `letsencrypt' ...
Adding new group `letsencrypt' (1000) ...
Adding new user `letsencrypt' (1000) with group `letsencrypt' ...
Creating home directory `/home/letsencrypt' ...
Copying files from `/etc/skel' ...
Enter new UNIX password: choose a password
Retype new UNIX password: choose a password
passwd: password updated successfully
Changing the user information for letsencrypt
Enter the new value, or press ENTER for the default
Full Name []: 
Room Number []: 
Work Phone []: 
Home Phone []: 
Other []: 
Is the information correct? [Y/n] y

 

Configure nginx for ACME

Edit your main nginx.conf file ( /etc/nginx/nginx.conf ) so it contains the following content:

worker_processes 1;
events {
    worker_connections 1024;
}

http {
    include mime.types;
    default_type application/octet-stream;
    sendfile on;
    keepalive_timeout 65;

    server {
        listen 80;
        server_name www.example.org; # Change this to your domain!
        root /var/www/path/to/your/website/files; # Change this to your website's path!

        location /.well-known/acme-challenge/ {
            alias /home/letsencrypt/challenges/;
        }
    }
}

 

Ensure that nginx is starting at boot time

If you’re using a systemd Linux distribution, the following commands will work. If you’re using another init, just follow instructions for enabling and starting services for that init system.

systemctl enable nginx
systemctl start nginx

 

Configure sudo

The last thing we need to do as root is to give the letsencrypt user permission to reload nginx. This is so that when we renew the certificates later on, the new certificates can be loaded and used by the server.

To configure this, run the visudo command (this lets you edit the sudo configuration file in a safe way) and add the following line:

On systemd Linux

letsencrypt ALL=(ALL) NOPASSWD: /usr/sbin/systemctl reload nginx

On FreeBSD + most non-systemd Linux Distributions

letsencrypt ALL=(ALL) NOPASSWD: /usr/sbin/service nginx reload

Now let’s switch to the newly created user:

su – letsencrypt

 

 

ACME-Tiny

ACME is the name of the protocol that Let’s Encrypt uses to register accounts and create certificates. The python script which we’ll use to manage our certificates and keys is called acme_tiny. We now have all the prerequisites installed so that the ACME-Tiny script will ‘just work.’

Let’s grab the latest version of ACME-Tiny:

wget https://raw.githubusercontent.com/diafygi/acme-tiny/master/acme_tiny.py

Now, we make the script executable:

chmod 755 acme_tiny.py

 

Check the Code

As the official documentation states, you should read the code. It is written in Python and at the time of this post, it’s only about 200 lines long. It shouldn’t be too hard to understand, even if you don’t have much prior programming experience.

That advice doesn’t just apply to this specific script; it’s a habit that’s good to get into as a system administrator. A quick glance at the source code of software or scripts that you use doesn’t hurt and can often reveal a lot about a project. It is therefore something you should do regularly, especially for small things that you are planning on running as root.

Never just copy and paste code or download code when you don’t have a general understanding of what that code will be doing on your system.

 

Create the account key

First, we need to generate an account key. The purpose of this key is to authenticate yourself to the ACME server. Consider it your password. Paste the following into your letsencrypt user’s shell:

# Generate the account key. It’s a 4096 bit RSA key.
openssl genrsa 4096 > account.key
# Make our new keyfile unreadable to other users
chmod 600 account.key

 

Create a domain key

For the domain itself, we need to create a separate domain key and a certificate signing request (CSR). In our example we will call it example.org, but of course you need to use your own domain name. While you can freely choose the name of the actual file, you should use the correct Common Name (CN) to ensure that things work correctly.

# Create the domain key, also a 4096 bit RSA key
openssl genrsa 4096 > example.org.key
# Protect your key
chmod 600 example.org.key
# Create the Certificate Signing Request and pipe it into a file
openssl req -new -sha256 -key example.org.key -subj "/CN=example.org" > example.org.csr

 

Create the Challenges Directory

Remember our ‘challenges’ directory from the nginx configuration file? That doesn’t exist yet, so let’s create it now:

mkdir /home/letsencrypt/challenges/

This directory is where our challenges — the content used to verify the ownership of the domain — will be stored.

Restart nginx

Before we run our letsencrypt script, we’ll need to make sure nginx has processed the configuration changes we’ve made. Reload your nginx configuration now:

sudo service nginx reload || sudo systemctl reload nginx

 

Request Your Letsencrypt Certificate!

Time to obtain our certificate! First, we use our account key and the certificate signing request and store the files for the challenge in the challenges directory that nginx is now serving. Finally, we pipe our certificate (the output of the command) into a .crt file.

Make sure to replace the example domain in bold with your actual domain:

./acme_tiny.py \
--account-key /home/letsencrypt/account.key \
--csr /home/letsencrypt/example.org.csr \
--acme-dir /home/letsencrypt/challenges/ \
> /home/letsencrypt/example.org.crt

 

You should see the following output:

Parsing account key...
Parsing CSR...
Registering account...
Registered!
Verifying example.org...
example.org verified!
Signing certificate...
Certificate signed!

Add the Intermediate Certificate

Now, we’ll download the intermediate certificate:

wget https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem

Now we add the intermediate certificate:

cat \
example.org.crt \
lets-encrypt-x3-cross-signed.pem \
> example.org.chain.crt

Let’s protect our certificate and make it unreadable to other users. Nginx will read this as root, so you don’t need any fancy ownership changes:

chmod 600 example.org.chain.crt example.org.crt

We have our certificate!

But we’re not quite done yet — since Let’s Encrypt certificates don’t last for very long, they need to be renewed frequently. This should be automated, which is the reason why the ACME protocol was created in first place.

 

Auto-renewal of the certificate

We’ll create a simple shell script which will renew our certificate and restart nginx for us. We’ll set this up to run as a cron job.

Use your favorite editor to create the following script, and name it /home/letsencrypt/renew_cert.sh

#!/bin/sh
cd /home/letsencrypt/

# Renew the key
/home/letsencrypt/acme_tiny.py \
--account-key /home/letsencrypt/account.key \
--csr /home/letsencrypt/example.org.csr \
--acme-dir /home/letsencrypt/challenges/ \
> /home/letsencrypt/example.org.crt.new

# Make sure we got a new, non-empty certificate
if [[ ! -s /home/letsencrypt/example.org.crt.new ]] ; then
 echo “Error: New certificate empty or non-existent”
 exit 1
fi

# Everything worked out. Let’s move the certificate live
mv example.org.crt.new example.org.crt

# Add the intermediate
wget https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem
cat \
example.org.crt \
lets-encrypt-x3-cross-signed.pem \
> example.org.chain.crt

# Reload nginx 
/usr/bin/sudo /bin/systemctl reload nginx

Replace example.org with your domain name, and /bin/systemctl with your non-systemd service management program, if applicable. For example, on an old Ubuntu LTS version using upstart, this would be /usr/bin/service.
Once you’ve saved this file, make it executable and tighten up the permissions:

chmod 700 /home/letsencrypt/renew_cert.sh

Now we can configure a cron job that will run this script on the first of every month, at midnight (using crontab -e):

0 0 1 * * /home/letsencrypt/renew_cert.sh

 

Activating HTTPS on nginx

To actually activate HTTPS on nginx, become root again (either by typing Ctrl-d to get out of your letsencrypt shell, or by using ‘su – root’ or ‘sudo -i’). Open the nginx configuration file for writing (/etc/nginx/nginx.conf on Linux).

Change your server block to look like this:

server {
    listen 80;
    listen 443 ssl;
    server_name www.example.org;
    ssl_certificate /home/letsencrypt/example.org.chain.crt;
    ssl_certificate_key /home/letsencrypt/example.org.key;
    location /.well-known/acme-challenge/ {
        alias /home/letsencrypt/challenges/;
    }
 }

These changes will allow your server to be used both via HTTPS and without.

As always, reload your nginx server to make your configuration changes active. The following command works with Linux/systemd and FreeBSD/Linux/upstart/init.d/OpenRC:

service nginx reload || systemctl reload nginx

You’re done!

You can adapt the configuration file shown here to only allow HTTPS, setting up a redirect for clients asking for non-HTTPS URLs, but that is outside the scope of this tutorial.

Conclusion

Congratulations! You’ve configured your first SSL-protected site. This has all kinds of great benefits, from protecting your visitors to boosting your Search Engine rankings.

If you’d like to check out the full e-book (which this tutorial is a ‘teaser’ for), you can find PDF, EPUB, and .mobi versions on Gumroad. The full version, which you can get for the price of a New York City coffee, is a much more in-depth version of this tutorial with more than twice the content:

  • current best-practices for a secure nginx configuration (which TLS versions and settings to use for extra security)
  • a ‘further research’ guide with ideas for making your setup better — backups, key management, hardening, etc.
  • background information on TLS
  • explanations of what exactly is going on when we run these commands
  • instructions for FreeBSD
  • the differences between Debian-based Linux distributions (like Ubuntu) and other Linux distributions
  • references for getting started in understanding the cryptography behind TLS and key exchanges
  • free updates for the book, for life

 

Oh, and you’ll help support the creation of more free content like this :-). Thanks again for wanting to make the Web a safer, more private place. Have fun!

-Dave

1 reply
  1. Rocky says:

    Hi Dave, thanks for yet another great tutorial. I’m running into some problems editing my nginx config files in regards to the .well-known/acme-challenge part of the website.

    I’m editing my sites-available config and it looks like this

    server {
    listen 80 default_server;
    #listen [::]:80 default_server; # this was causing problems

    root /var/www/mrdg;
    index index.php index.html index.htm;

    server_name mrdg.tech;

    location / {
    try_files $uri $uri/ /index.php;
    }

    error_page 404 /404.html;

    error_page 500 502 503 504 /50x.html;
    location = /50x.html {
    root /usr/share/nginx.html;
    }

    location /.well-known/acme-challenge/ {
    alias /home/letsencrypt/challenges/;
    }

    }
    ###############
    However, it fails when I reload. just the location /.well-known part is what makes it fail. If I comment that out, reloading nginx is fine.
    Additionally, when I run acme-tiny.py I get this error:
    ValueError: Wrote file to /home/letsencrypt/challenges/_v8XFscDagrkh-He71ji4t4Q0tTUnY7abGqZ_BOhH8c, but couldn’t download http://mrdg.tech/.well-known/acme-challenge/_v8XFscDagrkh-He71ji4t4Q0tTUnY7abGqZ_BOhH8c

    Any troubleshooting tips? That alias part for the challenge is hanging me up for both nginx config and the the acme script.

    Many thanks

    Rocky

Comments are closed.