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 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:
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:
- SSL and TLS Deployment Best Practices
- How do you score A+ with 100 on all categories on SSL Labs test with Let's Encrypt and Nginx?
#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 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:
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: 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!