Skip to content

Instantly share code, notes, and snippets.

@wastemobile
Last active May 13, 2018 20:01
Show Gist options
  • Save wastemobile/e79ab5fcc5643a23c6e6f3f3b5dffaef to your computer and use it in GitHub Desktop.
Save wastemobile/e79ab5fcc5643a23c6e6f3f3b5dffaef to your computer and use it in GitHub Desktop.
Nginx + Let's Encrypt

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

使用 Certbot 設定 Nginx SSL

Let's Encrypt client (Certbot) 有兩種運行模式:

  • Standalone: 啟動一個獨立伺服器回應 ACME challenges
  • Webroot: 使用我們的 nginx 某目錄來回應

Webroot 模式比較好 因為不需要替換 Nginx 服務的 80 埠。

下面的範例,我們將要設定 mydomain.com 這個網域,HTML 擺在 /var/www/mydomain.com 目錄,Let's Encrypt 的初始驗證(challenges)擺在 /var/www/letsencrypt 目錄。


Nginx snippets

預先建立兩個設定片段,需要時嵌入(include)即可。

新建 /etc/nginx/snippets/letsencrypt.conf 檔案並包含下列內容:

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

新建 /etc/nginx/snippets/ssl.conf 設定檔並包含下列內容:

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

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH";
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;

同時建立 challenges 目錄:

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

Nginx server blocks (HTTP-only)

一開始還沒有 SSL 證書,所以初始設定只能服務 HTTP。

新建 /etc/nginx/sites-available/mydomain.conf 設定檔並包含下列內容:

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

由於 nginx 可能有一個未設定網域對應的預設檔 server_name _ (default),須視自己的需求調整;若有,上面 mydomain.conf 設定檔中應該拿掉 default_server

編輯修改設定檔(下面是移除 default,將 mydomain 視為預設的狀況):

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

重新載入 Nginx:

sudo systemctl reload nginx

Certbot

安裝套件(與資源庫):

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

注意: 不要安裝 letsencrypt 套件,那是舊的工具程式。由於我們要設定的是 Nginx,所以安裝 python-certbot-nginx,使用 Apache 就是 python-certbot-apache,不指定任何、獨立使用,就是 certbot


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.

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