Skip to content

Instantly share code, notes, and snippets.

@cecilemuller
Last active April 15, 2024 02:19
Show Gist options
  • Save cecilemuller/a26737699a7e70a7093d4dc115915de8 to your computer and use it in GitHub Desktop.
Save cecilemuller/a26737699a7e70a7093d4dc115915de8 to your computer and use it in GitHub Desktop.
How to setup Let's Encrypt for Nginx on Ubuntu 18.04 (including IPv6, HTTP/2 and A+ SSL rating)

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";

Now restart Nginx, and test the domain again with SSL Labs using https://www.ssllabs.com/ssltest/analyze.html?d=www.YOUR-DOMAIN.com&latest: it should now be rated A+, congratulations! 🙂


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:

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

@wallysoncarvalho
Copy link

Hi, thanks a lot for the tutorial.
The thing is, in my server everything works perfectly, except that afeter installation of the certificate with certbot the server stops to respond. It stays awaiting....
I have no ideia why and cant find a solution on the internet.
Any idea that could help ?!
Thanks !!

@peterneubart
Copy link

@cecilemuller
this is awesome thanks alot!

I have a question regarding redirects from WWW to non-WWW:
https://www.example.com isn't redirecting to non-WWW

When obtaining the SSL certificate:
sudo certbot --nginx -d example.com -d www.example.com
I have choosen option: 2

How can I get this working?
So all requests http(s)://www. will get redirected to https:// without WWW

@emjayess
Copy link

I have a question regarding redirects from WWW to non-WWW:
https://www.example.com isn't redirecting to non-WWW

When obtaining the SSL certificate:
sudo certbot --nginx -d example.com -d www.example.com
I have choosen option: 2

How can I get this working?
So all requests http(s)://www. will get redirected to https:// without WWW

Hi Peter,

Out of the box, the redirection managed by certbot concerns http to https redirection (ensures a secure connection). For the www redirection, the most common case I think you'll find is redirecting from a root domain to the www subdomain or variation...

Which could be achieved like this:

if ($host = root-domain.com) {
    return 301 https://www.$host$request_uri;
}

Or this:

server_name root-domain.com;
return 301 https://www.root-domain.com$request_uri;

And so to go the other way and always shift visits to the www variation onto the root domain, you'll want to reverse that logic:

if ($host = www.root-domain.com) {
    return 301 https://root-domain.com$request_uri;
}

Or:

server_name www.root-domain.com;
return 301 https://root-domain.com$request_uri;

Such redirects generally should be implemented in their own server { ... } block.

I hope this helps :)

@tpaullee
Copy link

You are meticulous for this nice write-up. Thanks so much!
May be the link URL can be aliased to a better self-explanatory name to reach more readership.

@hitjethva
Copy link

I ran this command: certbot --apache -d linuxbuz.com

I am getting the following error:

IMPORTANT NOTES:

The following errors were reported by the server:

Domain: linuxbuz.com
Type: connection
Detail: Fetching
https://linuxbuz.com/.well-known/acme-challenge/ewpBCX7N0nzDyBZZILYP-y9sKHI4seFGac4Se7TpwfA:
Connection refused

@ddoherty
Copy link

Is it now preferred to use python 3?

e.g.,

apt-get install python3-certbot-nginx

Thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment