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, install it:
Shell
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install certbot
To generate a certificate, certbot creates a file in the .well-known/acme-challenge directory to verify that you own the domain. To serve this request, configure nginx to handle it directly 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. Provide your actual email address. Let's Encrypt only sends you emails when your certificates are about to expire.
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 ran successfully, your certificate will be in the /etc/letsencrypt/live/ directory.
The final step is to add the certificate to the nginx configuration. You should also redirect HTTP traffic to HTTPS and add 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 verify your server configuration 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 an 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, so you must renew them before they expire. You can use certbot to renew all certificates and reload nginx with the new certificate:
Shell
sudo certbot renew --renew-hook "service nginx reload"
Running this command manually every time is impractical, so add a scheduled task to automate it. According to the official documentation, you should attempt renewal 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 renews certificates every day at 6 AM and 6 PM, with a random delay of 0 to 59 minutes. The random sleep approach comes from this answer on StackOverflow: https://stackoverflow.com/a/16289693/2996339.
#Conclusion
Your website is now running on ports 80 and 443 with a valid certificate that renews automatically. Feel free to leave a comment if I missed anything. As a reminder, I'm not a Linux expert 😃
Do you have a question or a suggestion about this post? Contact me!