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:

awesome post. THANK YOU!

Thanks!

kakopappa commented on Jan 24, 2017 • edited

Found a lazy way :)

Really thankful

Thank you :)

Thanks it worked really well.

rlam3 commented on Mar 24, 2017 • edited

does this work for 14.04 too? And what if I have cloudflare in front of nginx? How would this differ?

hshhhhh commented on Jun 8, 2017 • edited

thank you! this is perfect!

Shall I need to run this code separately for each of my domain?

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

cecilemuller commented on Jun 19, 2017 • edited

  • with multiple -d, it creates a single certificate for all domains specified, so it counts as only one request

  • if you ran the command separately for each domain, it creates a separated certificate for each domain, and each domain counts as one request.

It works. I am running the script to get certificate separately for each domain. Now when I set automatic renewal, will single cron will renew all of my certificates which I have generated separately for each domain?

cecilemuller commented on Jun 20, 2017 • edited

You can check it by running certbot renew --dry-run (which merely pretends to renew), it should mention all the certificates.

Thanks a lot. It is working. You have a such great technical and helpful person I had ever known.

RobinCsl commented on Jun 28, 2017 • edited

Thank you very much. Really helpful!!! I would like to add that if you are loading index.php by default, leave the lines concerning the location of how the server needs to interpret php files.

This is the most concise and helpful write-up I have found! Thank you for taking the time to publish this.

It just works! Thank you!

rajeevkannav commented on Jul 25, 2017 • edited

Hello, Thanks for this awesome thing.

Just one Question Why we are not writing server_name mydomain.com; like server_name mydomain.com www.mydomain.com; single Line ?? this will save two code blocks and iterations

Thanks in advance

Thanks! Save my time.

astr0naugh7 commented on Aug 6, 2017 • edited

Hi, This worked great for me. Now I want to add another domain onto the same server using it's own SSL cert. How can I edit this to allow for multiple domains/subdirectories? With this current setup, I can't get to the IP as it just 301s to the domain I first setup with this. Is it possible to do that? I would really like to have multiple domains on this one server.

Thanks again.

Awesome, thanks a lot!

Came to say, thanks so much! This was extremely helpful.

Regardless, plumbing the nginx docs might help.

I spend like 30 minutes a server generating the damn dhparam.pem file; is it not necessary to do so?

I tried this many times, and it's not working out. How can I debug what's the issue?

frenchbread commented on Aug 29, 2017 • edited

In addition, how would you add to this NGINX config an instance that works on port :8000 "locally" on a server and should be reverce-proxied to public :8000 port. Including same redirections as with :80 ?

nadj commented on Sep 8, 2017 • edited

nginx version is 1.10.3, server is Ubuntu 16.04.3 LTS

Can you give me some advice? Thanks in advance

Yes indeed, thanks!

yifeikong commented on Oct 8, 2017 • edited

I wish I could create a cert via --standalone and then renew it using --wwwroot.

I installed SSL on my 4 domains on the same day but suddenly certificates of 2 domains expired. I already run "certbot renew --dry-run" but in vain. For those 2 domains it is showing that certificate has been expired 21 days ago. Please suggest.

cecilemuller commented on Oct 9, 2017 • edited

The best advice I could give is looking at the output of the dry run command to see if it gives an error (like if it's able to connect), or even run certbot renew to see if it has an error only when it tries to truly renew the certificate (instead of a mere test run).

There should also be some log files in /var/log/letsencrypt you could look at when looking for errors, but they're very verbose.

And if it does renew but doesn't show up in the browser, restart the web server service (although reloading should be enough) and clear your browser cache.

The dry run command did not show any error and all the renew process was done successfully. Tanks a lot for your suggestion to restart server. All the 2 certificates started working as soon as I restarted my nginx server.

cecilemuller commented on Oct 9, 2017 • edited

Is there any reason why X-Frame-Options could not be SAMEORIGIN? Making that change doesn't seem to affect my score on SSLTest.

cecilemuller commented on Nov 2, 2017 • edited

I followed this guide and it works well. Thanks But after this, when I'm trying to access phpmyadmin page, it returns 403 Forbidden nginx/1.10.3 (Ubuntu)... Can anyone help me with this problem?

Thanks! Works great.

Firstly great stuff, I tried setting it up with odoo 10 on a vps but seem to be having some trouble with it, due to the sensitivity of the project can you provide some insight on how to get it working? ill gladly redact any sensitive info if successful to help other.

Thanks, i just need another fix that is not established here that it was open the firewall rules, sudo ufw status sudo ufw allow 'Nginx Full' sudo ufw delete 'Nginx HTTP' And works great!

tinusg commented on Jan 17, 2018 • edited

If you ever run into the issue where your acme-challenge folder seems to be publicly accessible (works in your browser), but Letsencrypt still returns 404 during the CA challenge, you should check to see if your IPV6 configuration is working correctly. I had added AAAA records to my DNS server, but accidentally removed listen [::]:80; from my site settings.

Oh and don't forget to add [::]:443 ssl; to your site settings when Letsencrypt is done. It doesn't do that automatically.

Amazing post, this worked for me :)

Thank you, very good work!

I have been trying this gist for a while now, and the response for the certbot command i keep getting is invalid response. If I try to access the /.well-known folder from the browser i get a 403 forbidden from nginx. Could someone help me with this?

giantas commented on Jan 25, 2018 • edited

Why is it creating a folder named ~ in the current path?

Created a user just to say awesome guide.

vwal commented on Feb 9, 2018 • edited

I have the following as default server config (named 00.default since the sites are loaded in alpha order) in nginx, and have created /var/www/nginx/_undefined/htdocs/.well-known/ directory for certbot to use for its verification. This way I can use certbot certonly -a webroot --webroot-path=/var/www/nginx/_undefined/htdocs -d somenewdomain.com command while I don't yet have that domain otherwise configured in nginx.

hi, when you use webroot technic for renewale certificats/keys, does it really need to reload nginx http server ? I read many tutorial, it is the one who create script for reload server http. Is it necessary to do it ?

Thanks a lot. Your guide is awesome.

The option --no-eff-email is unrecognized in last version of certbot (0.10.2).

devansvd commented on Mar 4, 2018 • edited

Nice Efforts man. Seeing a good A+ SSL Certificates made my day. Thanks a ton.

Very good tutorial. Thank you.

Found this guide nice and useful / to the point. But I remembered hearing about LE's new support for wildcard subdomains and gave that a shot. If anyone's interested in that check out my fork. And it worked even having just done this guide using non-wildcard. Certbot automatically linked to the new cert.

Really appreciate. Thank you for taking the time to share this.

Works splendid! Thank you! Helped me secure multiple nginx servers.

This is awesome, thank you so much!

Thank you so much for this. It worked like charm

best certbot tutorial. thanks

Thank you very much, I'm using this tutorial each time I need letsencrypt. P.S. Works fine for apache2 too.

Thank, it's very useful

awesome thank you!

Thanks a million! Very well explained.

Life saver! Thanks very much!

bennetcq commented on Jun 29, 2018 • edited

if uncomment above code, only result in SSL A, not A+

cecilemuller commented on Jun 29, 2018 • edited

Are you asking why you're getting only A rating when the add_header lines start with # ? If so, that's because the settings are being ignored by Nginx, # symbols in configs are for writing arbitrary comments.

Also the Content-Security-Policy line in your code looks incomplete, see the one form the tutorial:

alexanderpetrenz commented on Jul 3, 2018 • edited

enabled results in an empty page when I access the fritz box sub domain. When I comment this line everything works as intended. Do you have any suggestion how I could fix that?

Regards

EDIT: I just noticed that even when I disable the mentioned line I get an A+ rating.

letsencrypt-auto --nginx vs certbot --nginx which one is the better approach to install?

There was nothing.

Can you please confirm with the auto-renewal process.

For multiple virtual hosts, run command separately for each host.

shipsource commented on Dec 25, 2018 • edited

So correct redirect should be (here could be either everything to www or everything to non-www), like:

Thank you for posting useful gist.

robsch commented on Feb 13, 2019 • edited

Thanks a lot it works fine with my app on scaleway

I tried a dry-run for renewal but it failed with the below

Processing /etc/letsencrypt/renewal/domainname.com.conf

This is fantastic! Thank you soo much for doing such a great job.

Quick note about an issue I ran into recently: if your server fails to update certbot (0.28.0 minimum is now required as SNI is no longer supported), try sudo apt full-upgrade because the change from python 2 to 3 is holding back the certbot package on some servers.

Saved the day.

How can I do the same configurations and so on, if my ISP is blocking my port 80 and 443? I cannot find anything related to that :( To set my A record is already "impossible" because of that block... do we have some way to workaround that?

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 !!

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:

Or this:

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:

Or:

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

I hope this helps :)

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.

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:

Last updated