最近开发了一个小程序-智名宝典,主要针对即将成为父母的人能为宝宝起一个心仪的名字。在开发的过程中,经历了很多磨难,主要是小程序的域名备案,部署这块。于是,整理成一篇文章,方便以后查看。
首先,你要拥有一台服务器和一个域名,域名还要备案,同时小程序也要备案,前前后后搞了1个月,小程序备案目前还没有完成。此处主要记录证书申请和服务部署。
我的部署架构为:docker,所有的东西都是依赖于docker容器,比如:mysql,redis,nginx等这些。假如域名为:xxx.xyz,小程序后台多个:a.xxx.xyz,b.xxx.xyz,nginx也是单独的一个docker容器,则:宿主机装acme.sh,Nginx 放在 Docker 里,证书文件放宿主机目录,再挂载进 Nginx 容器。
对你当前域名:
- 网站:xxx.xyz
- 小程序后台 A:a.xxx.xyz
- 小程序后台 B:b.xxx.xyz
最简单稳定的方案是先申请一张多域名证书(SAN 证书),一次覆盖这 3 个域名。
一:DNS准备
在域名服务商DNS里至少保证下面 3 条记录都解析到你的服务器公网 IP:
1 2 3
| xxx.xyz A 你的服务器IP a A 你的服务器IP b A 你的服务器IP
|
也就是:
- xxx.xyz -> 服务器IP
- a.xxx.xyz -> 服务器IP
- b.xxx.xyz -> 服务器IP
先确认:1 2 3
| ping xxx.xyz ping a.xxx.xyz ping b.xxx.xyz
|
二:宿主机准备目录
下面我统一用 /opt/xxx 作为宿主机目录。
1 2 3 4
| sudo mkdir -p /opt/xxx/nginx/conf.d sudo mkdir -p /opt/xxx/nginx/ssl/xxx.xyz sudo mkdir -p /opt/xxx/acme-challenge/.well-known/acme-challenge sudo chown -R $USER:$USER /opt/xxx
|
目录用途:
- /opt/xxx/nginx/conf.d:Nginx 配置
- /opt/xxx/nginx/ssl/xxx.xyz:证书文件
- /opt/xxx/acme-challenge:ACME 验证文件
三:安装acme.sh
在宿主机执行:
1 2 3
| curl https://get.acme.sh | sh -s email=admin@xxx.xyz source ~/.bashrc ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt
|
检查版本:
1
| ~/.acme.sh/acme.sh --version
|
四:Docker Compose 示例
假设现在有:
- 网站容器:site
- 小程序后台 A:miniapp-a
- 小程序后台 B:miniapp-b
- 网关 Nginx:gateway-nginx
可以参考这个docker-compose.yml:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| version: '3.8'
services: web: image: nginx:alpine container_name: my_static_website restart: always ports: - "80:80" - "443:443" volumes: - ./html:/usr/share/nginx/html:ro - ./conf/default.conf:/etc/nginx/conf.d/default.conf:ro - ./ssl:/etc/nginx/ssl:ro - ./acme-challenge:/var/www/acme:ro networks: - app-net
networks: app-net: external: true
|
五:先只配 HTTP,用来申请证书
先不要急着上 443。
先写一个仅 HTTP 的配置文件:
文件:/opt/xxx/nginx/conf.d/00-http.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| server { listen 80; server_name xxx.xyz;
location ^~ /.well-known/acme-challenge/ { root /var/www/acme; default_type "text/plain"; }
location / { proxy_pass http://site:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto http; } }
server { listen 80; server_name a.xxx.xyz;
location ^~ /.well-known/acme-challenge/ { root /var/www/acme; default_type "text/plain"; }
location / { proxy_pass http://miniapp-a:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto http; } }
server { listen 80; server_name b.xxx.xyz;
location ^~ /.well-known/acme-challenge/ { root /var/www/acme; default_type "text/plain"; }
location / { proxy_pass http://miniapp-b:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto http; } }
|
启动容器:
测试Nginx配置:
1
| docker exec gateway-nginx nginx -t
|
如果没问题,重载:
1
| docker exec gateway-nginx nginx -s reload
|
六:先测试 ACME 验证路径是否通
在宿主机创建一个测试文件:
1
| echo test-ok > /opt/xxx/acme-challenge/.well-known/acme-challenge/test
|
分别访问:
1 2 3
| curl http://xxx.xyz/.well-known/acme-challenge/test curl http://a.xxx.xyz/.well-known/acme-challenge/test curl http://b.xxx.xyz/.well-known/acme-challenge/test
|
如果都能返回:
说明验证目录打通了,可以正式申请证书。
七:申请 Let’s Encrypt 证书
用 webroot 方式,一张证书覆盖 3 个域名:
1 2 3 4 5 6
| ~/.acme.sh/acme.sh --issue \ --server letsencrypt \ -d xxx.xyz \ -d a.xxx.xyz \ -d b.xxx.xyz \ --webroot /opt/xxx/acme-challenge
|
如果成功,会看到类似:
1 2 3 4
| Your cert is in: ... Your cert key is in: ... The intermediate CA cert is in: ... And the full chain certs is there: ...
|
八:把证书安装到固定目录
这一步很重要。
不要直接让 Nginx 用 ~/.acme.sh/ 里的原始文件,
而是用 –install-cert 输出到你自己的目录。
1 2 3 4
| ~/.acme.sh/acme.sh --install-cert -d xxx.xyz \ --key-file /opt/xxx/nginx/ssl/xxx.xyz/key.pem \ --fullchain-file /opt/xxx/nginx/ssl/xxx.xyz/fullchain.pem \ --reloadcmd "docker exec gateway-nginx nginx -s reload"
|
说明:
- key.pem:私钥
- fullchain.pem:完整证书链,Nginx 必须用它
- reloadcmd:续期后自动重载 Nginx
九:切换为 HTTPS 配置
现在证书已经有了,把原来的 HTTP 配置换成 HTTPS 配置。
你可以删除原来的 00-http.conf,换成:
文件:/opt/xxx/nginx/conf.d/10-https.conf
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
| map $http_upgrade $connection_upgrade { default upgrade; '' close; }
server { listen 80; server_name xxx.xyz a.xxx.xyz b.xxx.xyz;
location ^~ /.well-known/acme-challenge/ { root /var/www/acme; default_type "text/plain"; }
location / { return 301 https://$host$request_uri; } }
server { listen 443 ssl http2; server_name xxx.xyz;
ssl_certificate /etc/nginx/ssl/xxx.xyz/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/xxx.xyz/key.pem; ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 20m;
location / { proxy_pass http://site:3000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } }
server { listen 443 ssl http2; server_name a.xxx.xyz;
ssl_certificate /etc/nginx/ssl/xxx.xyz/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/xxx.xyz/key.pem; ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 20m;
location / { proxy_pass http://miniapp-a:8080; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } }
server { listen 443 ssl http2; server_name b.xxx.xyz;
ssl_certificate /etc/nginx/ssl/xxx.xyz/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/xxx.xyz/key.pem; ssl_protocols TLSv1.2 TLSv1.3;
client_max_body_size 20m;
location / { proxy_pass http://miniapp-b:8080; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto https; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } }
|
然后检查并重载:
1 2
| docker exec gateway-nginx nginx -t docker exec gateway-nginx nginx -s reload
|
十:浏览器验证
测试:
- https://xxx.xyz
- https://a.xxx.xyz
- https://b.xxx.xyz
查看证书是否是 Let’s Encrypt,是否有效。
也可以命令行检查:1 2 3
| openssl s_client -connect xxx.xyz:443 -servername xxx.xyz openssl s_client -connect a.xxx.xyz:443 -servername a.xxx.xyz openssl s_client -connect b.xxx.xyz:443 -servername b.xxx.xyz
|
十一:小程序侧要注意的事
微信小程序里,除了服务器证书正确,还要注意:
1)配置合法域名
到微信公众平台配置:
- https://a.xxx.xyz
- https://b.xxx.xyz
如果上传、下载、websocket 也用这些域名,也要分别配上。
2)必须用 HTTPS
小程序请求不能用 HTTP。
3)一定要用 fullchain.pem
别只用单独的 cert.pem,否则有些客户端会报证书链不完整。
4)TLS 版本
Nginx 至少保留:1
| ssl_protocols TLSv1.2 TLSv1.3;
|
十二:自动续期
acme.sh 安装后一般已经自动加了 cron。
你可以看一下:
1
| crontab -l | grep acme.sh
|
手工测试一次续期:
1
| ~/.acme.sh/acme.sh --renew -d xxx.xyz --force
|
成功后它会自动执行:
1
| docker exec gateway-nginx nginx -s reload
|
附录
文件目录:
1 2 3 4 5 6 7 8 9 10 11
| project/ ├── docker-compose.yml ├── html/ │ └── index.html ├── conf/ │ └── default.conf ├── ssl/ │ └── xxx.xyz/ ├── acme-challenge/ │ └── .well-known/ │ └── acme-challenge/
|