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

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:

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:

sudo mkdir /var/www/well-known

Then, change the configuration of nginx:

sudo vim /etc/nginx/sites-available/default
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:

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.

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.

sudo vim /etc/nginx/sites-available/default
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:

sudo nginx -t
sudo nginx -s reload

You can check you configure the server correcly 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:

openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048
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:

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

Of course you don't want to execute this command by hand everytimes. 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:

sudo vim /etc/crontab

Add the following line at the end of the file:

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: http://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 😃

Comments

Mikołaj Trawiński -

Don't know if you can help me, I have a problem with my certificate. I followed all steps from article. When I try to go to my website, I am correctly redirected from HTTP to HTTPS, but I receive error ERR_CONNECTION_RESET. In nginx logs I found and error no "ssl_certificate" is defined in server listening on SSL port while SSL handshaking, client: (my server IP), server: 0.0.0.0:443. Do you know the cause of this error?

Mikołaj Trawiński -

The most ridiculous method solved my problem - I just copied and pasted you nginx config and replaced my domain names. Looks like I had a typo in my old file. Anyway, thanks!

Leave a reply