Skip to content

Instantly share code, notes, and snippets.

@mvtango
Forked from cecilemuller/letsencrypt_2020.md
Created January 14, 2018 17:07
Show Gist options
  • Save mvtango/7e5d8f952c7bd26ae99c5adceecdc51e to your computer and use it in GitHub Desktop.
Save mvtango/7e5d8f952c7bd26ae99c5adceecdc51e to your computer and use it in GitHub Desktop.
How to setup Let's Encrypt for Nginx on Ubuntu 16.04 (including IPv6, HTTP/2 and A+ SLL rating)
domainname.txt
#! /bin/bash
export DOMAIN=jupyter.newsradar.org
if [ "$EUID" -ne 0 ]
then echo "Please run $0 as root"
exit
fi
if [ ! -f /root/letsencrypt-renew.sh ] ; then
sudo cat <<'__end__' | envsubst '$DOMAIN' >/root/letsencrypt-renew.sh
#!/bin/bash
systemctl reload nginx
# If you have other services that use the certificates:
# systemctl restart mosquitto
__end__
chmod +x /root/letsencrypt-renew.sh
echo -n Created:
else
echo -n Already existed:
fi
ls -l /root/letsencrypt-renew.sh
if [ ! -f /etc/cron.daily/letsencrypt ] ; then
sudo cat <<'__end__' | envsubst '$DOMAIN' >/etc/cron.daily/letsencrypt
#!/bin/bash
certbot renew --noninteractive --renew-hook /root/letsencrypt-renew.sh
__end__
chmod +x /etc/cron.daily/letsencrypt
echo -n Created:
else
echo -n Already existed:
fi
ls -l /etc/cron.daily/letsencrypt

How to setup Let's Encrypt for Nginx on Ubuntu 16.04 (including IPv6, HTTP/2 and A+ SLL rating)

There are two main modes to run the Let's Encrypt client (called Certbot):

  • Standalone: replaces the webserver to respond to ACME challenges
  • Webroot: needs your webserver to serve challenges from a known folder.

Webroot is better because it doesn't need to replace Nginx (to bind to port 80).

In the following, we're setting up mydomain.com. HTML is served from /var/www/mydomain, and challenges are served from /var/www/letsencrypt.


Nginx snippets

First we create two snippets (to avoid duplicating code in every virtual host configuration).

Create a file /etc/nginx/snippets/letsencrypt.conf containing:

location ^~ /.well-known/acme-challenge/ {
	default_type "text/plain";
	root /var/www/letsencrypt;
}

Create a file /etc/nginx/snippets/ssl.conf containing:

ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;

ssl_protocols TLSv1.2;
ssl_ciphers EECDH+AESGCM:EECDH+AES;
ssl_ecdh_curve secp384r1;
ssl_prefer_server_ciphers on;

ssl_stapling on;
ssl_stapling_verify on;

add_header Strict-Transport-Security "max-age=15768000; includeSubdomains; preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

Create the folder for the challenges:

sudo mkdir -p /var/www/letsencrypt/.well-known/acme-challenge

Nginx virtual hosts (HTTP-only)

We don't have a certificate yet at this point, so the domain will be served only as HTTP.

Create a file /etc/nginx/sites-available/mydomain.conf containing:

server {
	listen 80 default_server;
	listen [::]:80 default_server ipv6only=on;
	server_name mydomain.com www.mydomain.com;

	include /etc/nginx/snippets/letsencrypt.conf;

	root /var/www/mydomain;
	index index.html;
	location / {
		try_files $uri $uri/ =404;
	}
}

Enable the site:

rm /etc/nginx/sites-enabled/default
ln -s /etc/nginx/sites-available/mydomain.conf /etc/nginx/sites-enabled/mydomain.conf

And reload Nginx:

sudo systemctl reload nginx

Certbot

Install the package:

sudo apt-get install software-properties-common
sudo add-apt-repository ppa:certbot/certbot
sudo apt-get update
sudo apt-get install certbot

Note: there is also a letsencrypt package in APT, but it's a much older version of the client.


Get the certificate

Request the certificate (don't forget to replace with your own email address):

certbot certonly --webroot --agree-tos --no-eff-email --email YOUR@EMAIL.COM -w /var/www/letsencrypt -d www.domain.com -d domain.com

It will save the files in /etc/letsencrypt/live/www.mydomain.com/.

Note: The flag --no-eff-email opts out of signing up for the EFF mailing list, remove the flag if you'd like to signup.


Nginx virtual hosts (HTTPS-only)

Now that you have a certificate for the domain, switch to HTTPS by editing the file /etc/nginx/sites-available/mydomain.conf and replacing contents with:

## http://mydomain.com redirects to https://mydomain.com
server {
	listen 80;
	listen [::]:80;
	server_name mydomain.com;

	include /etc/nginx/snippets/letsencrypt.conf;

	location / {
		return 301 https://mydomain.com$request_uri;
	}
}

## http://www.mydomain.com redirects to https://www.mydomain.com
server {
	listen 80 default_server;
	listen [::]:80 default_server ipv6only=on;
	server_name www.mydomain.com;

	include /etc/nginx/snippets/letsencrypt.conf;

	location / {
		return 301 https://www.mydomain.com$request_uri;
	}
}

## https://mydomain.com redirects to https://www.mydomain.com
server {
	listen 443 ssl http2;
	listen [::]:443 ssl http2;
	server_name mydomain.com;

	ssl_certificate /etc/letsencrypt/live/www.mydomain.com/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/www.mydomain.com/privkey.pem;
	ssl_trusted_certificate /etc/letsencrypt/live/www.mydomain.com/fullchain.pem;
	include /etc/nginx/snippets/ssl.conf;

	location / {
		return 301 https://www.mydomain.com$request_uri;
	}
}

## Serves https://www.mydomain.com
server {
	server_name www.mydomain.com;
	listen 443 ssl http2 default_server;
	listen [::]:443 ssl http2 default_server ipv6only=on;

	ssl_certificate /etc/letsencrypt/live/www.mydomain.com/fullchain.pem;
	ssl_certificate_key /etc/letsencrypt/live/www.mydomain.com/privkey.pem;
	ssl_trusted_certificate /etc/letsencrypt/live/www.mydomain.com/fullchain.pem;
	include /etc/nginx/snippets/ssl.conf;

	root /var/www/mydomain;
	index index.html;
	location / {
		try_files $uri $uri/ =404;
	}
}

Then reload Nginx:

sudo systemctl reload nginx

Note that http://mydomain.com redirects to https://mydomain.com (which redirects to https://www.mydomain.com) because redirecting to https://www.mydomain.com directly would be incompatible with HSTS.


Automatic renewal using Cron

Certbot can renew all certificates that expire within 30 days, so let's make a cron for it. You can test it has the right config by launching a dry run:

certbot renew --dry-run

Create a file /root/letsencrypt.sh:

#!/bin/bash
systemctl reload nginx

# If you have other services that use the certificates:
# systemctl restart mosquitto

Make it executable:

chmod +x /root/letsencrypt.sh

Edit cron:

sudo crontab -e

And add the line:

20 3 * * * certbot renew --noninteractive --renew-hook /root/letsencrypt.sh

Conclusion

Congratulations, you should now be able to see your website at https://www.mydomain.com 🙂

You can now also test that your domain has A+ SLL rating:

I would also recommend setting up content-specific features like Content Security Policy and Subresource Integrity:

If Let's Encrypt is useful to you, consider donating to Let's Encrypt or donating to the EFF.

#! /bin/bash
if [ ! -f domainname.txt ] ; then
echo "Please create file domainname.txt in $(pwd) to proceed"
exit 1
fi
. domainname.txt
echo Preparing letsencrypt on nginx for ${DOMAIN}
if [ "$EUID" -ne 0 ]
then echo "Please run $0 as root"
exit
fi
if [ -d /etc/letsencrypt/live/${DOMAIN} ] ; then
echo /etc/letsencrypt/live/${DOMAIN} exists, refusing to overwrite
exit
fi
if [ ! -d /var/www/letsencrypt ] ; then
sudo mkdir -p /var/www/letsencrypt
else
echo /var/www/letsencrypt exists.
fi
if [ -r /etc/nginx/sites-enabled/default ] ; then
echo /etc/nginx/sites-enabled/default exists - please stash away or remove before prodeeding.
# sudo rm /etc/nginx/sites-enabled/default
exit 1
fi
if [ ! -f /etc/nginx/snippets/letsencrypt.conf ] ; then
echo creating /etc/nginx/snippets/letsencrypt.conf
sudo cat <<__end__ >/etc/nginx/snippets/letsencrypt.conf
location ^~ /.well-known/acme-challenge/ {
default_type "text/plain";
root /var/www/letsencrypt;
}
__end__
fi
if [ ! -f /etc/nginx/snippets/ssl.conf ] ; then
echo creating /etc/nginx/snippets/ssl.conf
sudo cat <<__end__ >/etc/nginx/snippets/ssl.conf
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:50m;
ssl_session_tickets off;
ssl_protocols TLSv1.2;
ssl_ciphers EECDH+AESGCM:EECDH+AES;
ssl_ecdh_curve secp384r1;
ssl_prefer_server_ciphers on;
ssl_stapling on;
ssl_stapling_verify on;
add_header Strict-Transport-Security "max-age=15768000; includeSubdomains; preload";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
__end__
fi
if [ ! -f /etc/nginx/sites-available/${DOMAIN}.conf -a ! -f /etc/nginx/sites-enabled/${DOMAIN}.conf ] ; then
echo creating HTTP Only config at /etc/nginx/sites-available/${DOMAIN}.conf
sudo cat <<'__end__' | envsubst '$DOMAIN' >/etc/nginx/sites-available/${DOMAIN}.conf
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
server_name ${DOMAIN} www.${DOMAIN};
include /etc/nginx/snippets/letsencrypt.conf;
root /var/www/${DOMAIN};
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
__end__
else
${DOMAIN}.conf exists:
find /etc/nginx -name "${DOMAIN}.conf" -ls
exit 1
fi
sudo ln -s /etc/nginx/sites-available/${DOMAIN}.conf /etc/nginx/sites-enabled/${DOMAIN}.conf
sudo systemctl reload nginx
sudo certbot certonly --webroot --agree-tos --no-eff-email --email martin.virtel@gmail.com -w /var/www/letsencrypt -d ${DOMAIN}
#! /bin/bash
if [ ! -f domainname.txt ] ; then
echo "Please create file domainname.txt in $(pwd) to proceed"
exit 1
fi
. domainname.txt
echo Installing minimal config on nginx for ${DOMAIN}
if [ "$EUID" -ne 0 ]
then echo "Please run $0 as root"
exit
fi
if [ ! -f /etc/nginx/sites-available/${DOMAIN}.conf ] ; then
sudo cat <<'__end__' | envsubst '$DOMAIN' >/etc/nginx/sites-available/${DOMAIN}.conf
## http://${DOMAIN} redirects to https://${DOMAIN}
server {
listen 80;
listen [::]:80;
server_name ${DOMAIN};
include /etc/nginx/snippets/letsencrypt.conf;
location / {
return 301 https://${DOMAIN}$request_uri;
}
}
## https://${DOMAIN}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name ${DOMAIN};
ssl_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/${DOMAIN}/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/${DOMAIN}/fullchain.pem;
include /etc/nginx/snippets/ssl.conf;
root /var/www/${DOMAIN};
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
__end__
echo -n Created:
else
echo -n Already existed, refusing to overwrite.
fi
ls -l /etc/nginx/sites-available/${DOMAIN}.conf
sudo systemctl reload nginx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment