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

https://gist.github.com/cecilemuller/a26737699a7e70a7093d4dc115915de8

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

Virtual hosts

Let's say you want to host domains first.com and second.com.

Create folders for their files:

mkdir /var/www/first
mkdir /var/www/second

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

server {
	listen 80 default_server;
	listen [::]:80 default_server ipv6only=on;

	server_name first.com www.first.com;
	root /var/www/first;

	index index.html;
	location / {
		try_files $uri $uri/ =404;
	}
}

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

server {
	listen 80;
	listen [::]:80;

	server_name second.com www.second.com;
	root /var/www/second;

	index index.html;
	location / {
		try_files $uri $uri/ =404;
	}
}

Note that only the first domain has the keywords default_server and ipv6only=on in the listen lines.

Replace the default virtual host:

sudo rm /etc/nginx/sites-enabled/default
sudo ln -s /etc/nginx/sites-available/first.conf /etc/nginx/sites-enabled/first.conf
sudo ln -s /etc/nginx/sites-available/second.conf /etc/nginx/sites-enabled/second.conf

sudo nginx -t
sudo systemctl stop nginx
sudo systemctl start nginx

Check that Nginx is running:

sudo systemctl status nginx

Expected results at this stage:

  • http://first.com and http://www.first.com serve the files from /var/www/first

  • http://second.com and http://www.second.com serve the files from /var/www/second

  • https://www.first.com and https://www.second.com don't work yet

Certbot

Install Certbot for Nginx:

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

Setup the certificates & convert Virtual Hosts to HTTPS:

sudo certbot --nginx

It will ask for:

  • an email address

  • agreeing to its Terms of Service

  • which domains to use HTTPS for (it detects the list using server_name lines in your Nginx config)

  • whether to redirect HTTP to HTTPS (recommended) or not

You could stop here if all you want is HTTPS as this already gives you an A rating and maintains itself.

Test your site with SSL Labs using https://www.ssllabs.com/ssltest/analyze.html?d=www.YOUR-DOMAIN.com

Expected results at this stage:

  • http://first.com redirects to https://first.com

  • http://second.com redirects to https://second.com

  • http://www.first.com redirects to https://www.first.com

  • http://www.second.com redirects to https://www.second.com

  • https://first.com and https://www.first.com serve the files from /var/www/first

  • https://second.com and https://www.first.comserve the files from /var/www/second

Automatic renewal

There is nothing to do, Certbot installed a cron task to automatically renew certificates about to expire.

You can check renewal works using:

sudo certbot renew --dry-run

You can also check what certificates exist using:

sudo certbot certificates

HTTP/2

first.conf should now look something like this, now that Certbot edited it:

server {
	server_name first.com www.first.com;
	root /var/www/first.com;

	index index.html;
	location / {
		try_files $uri $uri/ =404;
	}

	listen [::]:443 ssl ipv6only=on; # managed by Certbot
	listen 443 ssl; # managed by Certbot
	ssl_certificate /etc/letsencrypt/live/first.com/fullchain.pem; # managed by Certbot
	ssl_certificate_key /etc/letsencrypt/live/first.com/privkey.pem; # managed by Certbot
	include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
	ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}

server {
	if ($host = www.first.com) {
		return 301 https://$host$request_uri;
	} # managed by Certbot

	if ($host = first.com) {
		return 301 https://$host$request_uri;
	} # managed by Certbot

	listen 80 default_server;
	listen [::]:80 default_server;

	server_name first.com www.first.com;
	return 404; # managed by Certbot
}

Certbot didn't add HTTP/2 support when it created the new server blocks, so replace these lines:

listen [::]:443 ssl ipv6only=on;
listen 443 ssl;

by this:

listen [::]:443 ssl http2 ipv6only=on;
listen 443 ssl http2;
gzip off;

There is already an open Github issue requesting Certbot to add http2 automatically, so hopefully this step can soon be removed.

Stronger settings for A+

Trusted certificate

The HTTPS server blocks in first.conf and second.conf contain these lines, added by Certbot:

ssl_certificate /etc/letsencrypt/live/YOUR-DOMAIN/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/YOUR-DOMAIN/privkey.pem;

The stronger settings use OCSP Stapling, so each virtual host will need a ssl_trusted_certificate as well.

Add this line (using the folder name that Certbot generated for your domain) after the ssl_certificate_key line:

ssl_trusted_certificate /etc/letsencrypt/live/YOUR-DOMAIN/chain.pem;

SSL

Now let's edit the shared SSL settings at /etc/letsencrypt/options-ssl-nginx.conf. It most likely looks like this initially:

ssl_session_cache shared:le_nginx_SSL:1m;
ssl_session_timeout 1440m;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS";

If you tested with SSL Labs, you probably noticed that quite a few ciphers were flagged as "weak".

So replace the contents of the file with:

ssl_session_cache shared:le_nginx_SSL:1m;
ssl_session_timeout 1d;
ssl_session_tickets off;

ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
ssl_ecdh_curve secp384r1;

ssl_stapling on;
ssl_stapling_verify on;

add_header Strict-Transport-Security "max-age=15768000; includeSubdomains; preload;";
add_header Content-Security-Policy "default-src 'none'; frame-ancestors 'none'; script-src 'self'; img-src 'self'; style-src 'self'; base-uri 'self'; form-action 'self';";
add_header Referrer-Policy "no-referrer, strict-origin-when-cross-origin";
add_header X-Frame-Options SAMEORIGIN;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";

Conclusion

You could further improve using content-specific features like Content Security Policy and Subresource Integrity, and Brotli compression to replace gzip.

Online testing tools:

Useful links:

kakopappa commented on Jan 24, 2017 • edited

rlam3 commented on Mar 24, 2017 • edited

hshhhhh commented on Jun 8, 2017 • edited

cecilemuller commented on Jun 19, 2017 • edited

cecilemuller commented on Jun 20, 2017 • edited

RobinCsl commented on Jun 28, 2017 • edited

rajeevkannav commented on Jul 25, 2017 • edited

astr0naugh7 commented on Aug 6, 2017 • edited

frenchbread commented on Aug 29, 2017 • edited

nadj commented on Sep 8, 2017 • edited

yifeikong commented on Oct 8, 2017 • edited

cecilemuller commented on Oct 9, 2017 • edited

cecilemuller commented on Oct 9, 2017 • edited

cecilemuller commented on Nov 2, 2017 • edited

tinusg commented on Jan 17, 2018 • edited

giantas commented on Jan 25, 2018 • edited

vwal commented on Feb 9, 2018 • edited

devansvd commented on Mar 4, 2018 • edited

bennetcq commented on Jun 29, 2018 • edited

cecilemuller commented on Jun 29, 2018 • edited

alexanderpetrenz commented on Jul 3, 2018 • edited

shipsource commented on Dec 25, 2018 • edited

robsch commented on Feb 13, 2019 • edited

Last updated