Adding a free SSL certificate to a website hosted on nginx using Let's Encrypt

 
 
  • Gérald Barré

In the previous post, I showed how to publish an ASP.NET Core website to Linux. In this post, I'll show you how to secure your website using a free SSL certificate provided by Let's Encrypt.

#Get a free SSL certificate using Let's encrypt

certbot is the tool provided by let's encrypt to generate a certificate. First, you need to install it:

Shell
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install certbot

To generate a certificate, certbot will create a file in the .well-known/acme-challenge directory. This is to ensure you own the domain. To accept this request, you need to configure nginx to answer the request instead of forwarding it to localhost:5000.

First, let's create a directory to store the acme challenge:

Shell
sudo mkdir /var/www/well-known

Then, change the configuration of nginx:

Shell
sudo vim /etc/nginx/sites-available/default
nginx
server {
    listen 80;

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

    location / {
        proxy_pass http://localhost:5000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection keep-alive;
        proxy_set_header Host $host;
        proxy_pass_header Server;
        proxy_cache_bypass $http_upgrade;
    }
}

And reload the configuration:

Shell
sudo nginx -t
sudo nginx -s reload

Then, generate the certificate. You should provide your actual email address. Let's encrypt only send you emails when you forget to renew your certificates.

Shell
sudo certbot certonly --agree-tos -m "your email address" --no-eff-email --rsa-key-size 2048 --webroot -w /var/www/well-known/ -d www.youdomain.com -d youdomain.com

If the command has executed correctly you should see your certificate in the /etc/letsencrypt/live/ directory.

#Configure nginx to use the SSL certificate

The final step is to register the certificate in the nginx configuration. Plus, you should redirect the traffic from http to https and add some security headers such as HSTS.

Shell
sudo vim /etc/nginx/sites-available/default
nginx
upstream mysite {
    server localhost:5000;
}

server {
    listen 80;

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

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    # Enable https and http/2
    listen *:443 ssl http2;

    # The certificate served by Let's encrypt can contain more than one domain which is very convenient
    server_name yourdomain.net;
    server_name www.yourdomain.net;

    ssl_certificate /etc/letsencrypt/live/yourdomain.net/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/yourdomain.net/privkey.pem;

    # security
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
    ssl_ecdh_curve secp384r1;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    # Turn on OCSP stapling as recommended at
    # https://community.letsencrypt.org/t/integration-guide/13123
    # requires nginx version >= 1.3.7
    ssl_stapling on;
    ssl_stapling_verify on;

    # Uncomment this line only after testing in browsers,
    # as it commits you to continuing to serve your site over HTTPS in future
    # add_header Strict-Transport-Security "max-age=31536000";

    location / {
        proxy_pass  http://mysite;
    }
}

Finally, reload the configuration:

Shell
sudo nginx -t
sudo nginx -s reload

You can check you configure the server correctly using this free service: https://www.ssllabs.com/ssltest/analyze.html

With the above configuration, you may get a B grade. You can improve it to A using the following configuration:

Shell
openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048
nginx
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';

ssl_dhparam /etc/nginx/ssl/dhparam.pem;

Please read the following resources about best practices:

#Renew certificates automatically

Certificates are valid for only 90 days. This means you must renew them before their expiration date. You can use certbot to renew all certificates and reload nginx using the new certificate:

Shell
sudo certbot renew --renew-hook "service nginx reload"

Of course you don't want to execute this command by hand every time. It's more convenient to add a scheduled task to do it. From the official web site, you should try to renew certificates twice a day:

if you're setting up a cron or systemd job, we recommend running it twice per day (it won't do anything until your certificates are due for renewal or revoked, but running it regularly would give your site a chance of staying online in case a Let's Encrypt-initiated revocation happened for some reason). Please select a random minute within the hour for your renewal tasks.

Open the cron configuration file:

Shell
sudo vim /etc/crontab

Add the following line at the end of the file:

crontab
00 6,18    * * *   root    sleep $[RANDOM\%60]m; certbot renew --quiet --renew-hook "service nginx reload"

This will renew the certificates every day at 6AM and 6PM plus a delay between 0 and 59 minutes. The random sleep comes from this answer on StackOverflow: https://stackoverflow.com/a/16289693/2996339.

#Conclusion

Your website is now online on the 80 and 443 ports with a valid certificate which will renew automatically. Don't hesitate to leave a comment if I forgot something. Again, I'm not a Linux expert 😃

Do you have a question or a suggestion about this post? Contact me!

Follow me:
Enjoy this blog?Buy Me A Coffee💖 Sponsor on GitHub