这是一篇对于自己建站的总结,鉴于目前正规网站都需要 HTTPS,所以写下相关部署的知识,以供需要的人参考。我自己买的是一个小的服务器,并且买了一个域名,相关域名已经设置好了,如果你需要设置 DNS,请参考你购买域名的网站,会有相关文章。另外,我的网站架构是一个 nginx 在前,docker 部署的各种服务在后。nginx 用来设置 subdomain,然后把进来的流量分流给相关的服务。docker 来集成所需要的服务,这样封装性比较好,需要新服务,直接跑一个 docker 命令就好。

前提条件

  1. 有一个自己的 server,可以是 VPS,只要有公网的 IP 以及 root 的权限就行。
  2. 有一个购买好的域名,并且这个域名的 DNS record 已经设置好,指向你的 server。
  3. docker,nginx 已经安装好在 server 上,docker 已经有服务在跑,比如一个指向3000,一个指向3180

步骤

设置 subdomain

如果你的网站设置 DNS record 的时候就已经写了一个 wildcard record,比如一个A记录:* A 85.219.30.129(那个* 就是 wildcard,匹配所有字符)那么所有的 subdomain 都会先指向的固定 IP(85…)。所以我的 domian 是 paomian.de,那么api.paomian.de 也会先指向我设置的 IP,然后具体 subdomain 想指向哪儿我服务器上的 nginx 会接管并告知。

想要设置 subdomain,需要配置 nginx ,在他的官方文档可以获取如何安装并开启服务。完成后主要文件都在/etc/nginx下面。默认/etc/nginx/sites-available 会有一个default文件,是设置默认网页的(同时/etc/nginx/sites-enabled 是一个它的软链接,用来告诉nginx启动哪些配置)。为了方便看到所有设置,我就不在给每一个网站一个单独的文件配置,而是直接用默认的配置修改,并全部放在一起。

下图就是 /etc/nginx/sites-availalbe/default的配置:

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

		server_name blog.paomian.de;

        location / {
		        proxy_pass http://127.0.0.1:3180;
                proxy_set_header Host $host;
                proxy_redirect off;
        }
}

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

        server_name chat.paomian.de;

        location / {
		        proxy_pass http://127.0.0.1:3000;
                proxy_set_header Host $host;
                proxy_redirect off;
        }
}
  • 两片server的配置,分别用来指定两个 subdomain: blog.paomian.de, chat.paomian.de
  • 两个listen分别是指示nginx来监听IP4IP6的通用 HTTP80端口。
  • server.name用来指示nginx所属的subdomain,也就是说只要你在浏览器里输入相同的域名,你的浏览器就会先被 DNS 服务器指示来到你的这个 server 上(因为你的主域名),然后你的nginx就会看它的请求是哪个subdomain,然后分配到相关的server配置上。
  • location就是用来pathpattern matching/可以匹配所有的域名后面的path,也就是说,所有subdomain的请求都会进入这个配置,你也可以在另开一个location比如location /api/,那么所有$server_name/api/的请求都会由他来负责,然后其他的请求还是回归到/的配置。
  • proxy_pass http://127.0.0.1:3000用来指示你的请求通过nginx这个 proxy 去向何方,后面的http://127.0.0.1:3000就是指示它到我的目前server3000端口,而我的docker的一个服务就跑在这个3000的端口。这里就完成了一个收到请求,然后转向我docker跑的服务的配置。
  • proxy_set_header Host $host: 用来保留原请求header里的Host,在经过proxy后,依然是原来的 host, 也就是你的subdomain
  • proxy_redirect off: 告诉nginx不去更改response header里的Location

配置好后,可以重启nginx服务,在Debian下的命令是:

sudo systemctl restart nginx

这时你在浏览器里输入你的subdomain,你的docker下跑的服务就会返回相应的数据。至此,subdomain的设置就完成了,不过这时你的所有服务仍然是通过80端口,就是HTTP服务,想要转变为HTTPS,就需要给你的主机加上SSL certificate。如果需要,请继续看下去。

给网站加上 SSL Certificate

这里就不赘述SSL Certificate 是什么,简而言之,如果你的网站需要开启 HTTPS,你就需要这个。你需要跟相关的Certificate Authority (CA)申请一个,取决于CA,有的需要付费,有的不需要。我这里用的是Let’s Encrypt,一个免费开放的CA。下面是如何通过它来获取你的认证以及如何安装给你的网站。

申请获得 SSL Certificate

既然你要申请,那么你就得符合对方提出的条件。而SSL Certificate的核心要求就是你得证明你有相关域名的控制权。通过证明的方式就是你要通过相应的挑战 - challenge,最常见的两种 challenge 一个是HTTP-01, 一个是DNS-01HTTP-01的核心思想是你有了域名对应server的根控制权,那么你在里面放上一个我生成的文件,如果我去检查,它在,那你就通过了,我可以给你一个认证。DNS-01的核心思想是你既然控制了域名,那么你肯定也能在域名商那里写一个我指定的额外的 DNS record,我只需要检测下这个 record 跟我想的一样,那么我就承认你的控制权。

一般来说DNS-01比较方便一点儿,因为它可以设置Wildcard certificate,也就是说设置一个有 wildcard 的域名的认证,比如给*.paomian.de设置认证,那么它的所有subdomain就都有了这个认证,不需要再为下面的subdomain分别设置。

以下是如何给自己的域名申请Wildcard certificate,因为我们一次就要用两个subdomain,分别来完成HTTP-01挑战比较麻烦,一梭子搞完才是正道的光。

我用的CALet’s Encrypt,用的协议 client 是acme.sh,在你的server里直接安装acme.sh:

curl https://get.acme.sh | sh -s email=my@example.com

email 是你证书过期想被通知到的邮箱。

然后找到你的 dns 商对应的 API:https://github.com/acmesh-official/acme.sh/wiki/dnsapi, 前面说过,DNS-01的挑战主要是在DNS record上搞事情~ 那么你的dns供应商就需要有 API 来让来写文件,验证文件。在上面的网站里会有大部分域名供应商适配这个acme协议的 API 的用法,比如我的供应商是 netcup,我需要在我的控制面板里找到以下三个密码对应我的账户:

export NC_Apikey="<Apikey>"
export NC_Apipw="<Apipassword>"
export NC_CID="<Customernumber>"

然后在运行acme.sh时加上--dns dns_netcup,它就会自动引用这三个变量来向netcup来完成相应的认证。具体申请命令:./acme.sh --issue --dns dns_netcup -d paomian.de -d *.paomian.de,这里我一次申请两个的认证,包括了那个wildcard certificate

acme.sh现在用的是zeroSSL作为默认CA,但是我用这个会有错误,所以我还是用let's encrypt完成的,加参数即可:./acme.sh --issue --dns dns_netcup -d "paomian.de" -d "*.paomian.de" --server letsencrypt

它还会自动重复,每次等 10 秒左右,因为毕竟DNS record没有那么快就部署,我记得我第一次部署花了 4-5 分钟左右,一直在等待,最后终于全部通过。

题外话,关于 acme 协议的 client 之争,现在很多是用certbot,我是在读了下面两篇文章后选择用acme.sh的,毕竟依赖少,麻烦小。第一篇文章也有大量自己的实践步骤,我也学了很多,可以参考。

  1. Let’s Encrypt certificate with acme.sh instead of Certbot
  2. hacker_news related post

安装 SSL certificate 给网站

如果认证成功,你的acmefolder 里面应该会有你的域名的一个 folder,比如我的~/.acme.sh下就多了一个paomian.de_ecc的文件夹,里面有各种需要的认证,这些就是nginx需要用的:

➜  .acme.sh ls
account.conf  acme.sh  acme.sh.env  ca  deploy  dnsapi  http.header  notify paomian.de_ecc

➜  .acme.sh cd paomian.de_ecc
➜  paomian.de_ecc ls
backup  fullchain.cer   paomian.de.conf  paomian.de.csr.conf
ca.cer  paomian.de.cer  paomian.de.csr   paomian.de.key

但是官方强烈不建议直接链接这个里面的文件给nginx用,而是应该用--install来拷贝至你需要的地方。

下面我做的就是设置文件夹,把认证转移过去,并且这个文件夹需要对nginx用户组开放,以便nginx后面使用。因为nginx用的用户组/用户名是www-data:www-data,所以首先要把当前用户放进这个组里,这样就可以在当前用户下在里面写文件,在终端中输入:

sudo usermod -a -G www-data <你的当前用户名>

这样你的用户就属于www-data这个组里的一员了。记得登出,然后登入一下,因为你新设置的组,当前用户只有在进入新的 shell session 里后才会继承

然后在/home下设置一个相应的文件夹,给www-data组相关的权限,并把相关认证拷贝/安装过去:

sudo mkdir -p /home/www-data/certs/domain.dev
sudo chown -R www-data:www-data /home/www-data
sudo chmod -R g+rw /home/www-data/certs

./acme.sh --install-cert -d paomian.de \
            --key-file /home/www-data/certs/paomian.de/key.pem  \
            --fullchain-file /home/www-data/certs/paomian.de/fullchain.pem \
            --reloadcmd "sudo systemctl restart nginx"

sudo chown -R www-data:www-data /home/www-data/certs
sudo chmod -R g+rw /home/www-data/certs

最后那个 --reloadcmd "sudo systemctl restart nginx"是用来指示acme.sh如果安装好自动重启nginx的。如果希望它能自动完成,你还需要给www-data一个sudo systemctl restart 的权利,在/etc/sudoers.d/www-data里写入:

%www-data ALL= NOPASSWD: /bin/systemctl restart nginx.service

这时你就配置好了HTTPS网站所需要的一切认证了,现在只需要你把这个新拷贝的认证链接到你的nginx配置上就好了。跟前面[[如何给自己 host 的网站设置 subdomain 并加上 SSL#设置 subdomain]]里一样,只需要在/etc/nginx/sites-available/default里修改即可:

server {
        # SSL configuration
        listen 443 ssl;
        listen [::]:443 ssl;

        ssl_certificate /home/www-data/certs/paomian.de/fullchain.pem;
        ssl_certificate_key /home/www-data/certs/paomian.de/key.pem;

		server_name blog.paomian.de;
        location / {
		        proxy_pass http://127.0.0.1:3180;
                proxy_set_header Host $host;
                proxy_redirect off;
        }
}

server {
        listen 443 ssl;
        listen [::]:443 ssl;

        ssl_certificate /home/www-data/certs/paomian.de/fullchain.pem;
        ssl_certificate_key /home/www-data/certs/paomian.de/key.pem;

        server_name chat.paomian.de;
        location / {
                proxy_pass http://127.0.0.1:3000;
                proxy_set_header Host $host;
                proxy_redirect off;
        }
}

因为我们一开始申请的就是wildcard certificate所以这里两个subdomain用的都是相同的认证。

现在就可以用https://开头来进入你的网站了。

renew 更新 ssl 证书

# renew command
./acme.sh --renew --dns dns_netcup -d "paomian.de" -d "*.paomian.de" --server letsencrypt

# install the certificate and restart the nginx
./acme.sh --install-cert -d paomian.de \
            --key-file /home/www-data/certs/paomian.de/key.pem  \
            --fullchain-file /home/www-data/certs/paomian.de/fullchain.pem \
            --reloadcmd "sudo systemctl restart nginx"

查看到期时间

echo | openssl s_client -connect paomian.de:443 2>&1 | openssl x509 -noout -dates

痛点

  1. 怎么去用 nginx 分流各个 subdomain
    1. subdomain 直接用自己的服务器上的 nginx 就可以设置,不需要额外的 dns (如果你的 dns record 上已经有一个 wildcard 记录的话,因为这个 wildcard 相当于把所有的 subdomain 都包含了,然后指向你 record 里的 ip)
  2. 怎么去加 SSL
    1. chown 给自己的用户后记得登出一下再进去,不然当前用户可能没有获得刚给的权限 idid lvsun 就能看出区别 - 当前用户是 lvsun 的话。
    2. certbot 和 acme 之争
      1. acme 是协议,而 certbot 只是一个客户端
      2. two articles:
        1. https://news.ycombinator.com/item?id=24430260
        2. https://decovar.dev/blog/2021/04/05/acme-sh-instead-of-certbot/
        3. pdf about the history: https://jhalderm.com/pub/papers/letsencrypt-ccs19.pdf
    3. 为了获得 certificate,怎么去做认证
      1. 需要通过一个 challenge,一个是 http 的,需要你证明你可以在主机上可以创建文件;
      2. 一个是 dns 的,需要你的网址注册商提供相应的 API 来证明你可以在 DNS 的 record 上加上一段文字。
    4. www-data 是 nginx 的用户群/名
    5. 用 acme issue 认证的时候,可以不用默认的 zeroSSL,而是指定为 let’s encypt
      acme.sh --issue -d domain.dev -d *.domain.dev --dns dns_porkbun --server letsencrypt
      
    6. 不要直接 copy 认证,而是用 acme.sh 自带的安装命令来完成证书的复制。
    7. wildcard 和 直接域名是有区别的:*.paomian.de vs paomian.de , 但是认证的时候可以直接一起认证了,这样,每个 subdomain 就不需要单独再来一次认证了。这个好像只支持 dns challenge

reference

  1. acme 的执行文件: https://github.com/acmesh-official/acme.sh/wiki/说明
  2. acme.sh 用法指导: https://decovar.dev/blog/2021/04/05/acme-sh-instead-of-certbot/
  3. netcup 用 acme: https://github.com/acmesh-official/acme.sh/wiki/dnsapi#51-use-netcup-dns-api-to-automatically-issue-cert
  4. netcup API documentation, 如何生成 API key: https://helpcenter.netcup.com/de/wiki/general/unsere-api/
  5. netcup 用户论坛帖子,前几个的来源: https://forum.netcup.de/netcup-intern/operations/11841-let-s-encrypt-wildcard-zertifikate-via-certbot/