HTTPS-PORTAL 是一个由 Nginx、Let's Encrypt 和 Docker 驱动的全自动 HTTPS 服务器。使用它,您只需添加一行配置,即可通过 HTTPS 运行任何现有的 Web 应用程序。
SSL 证书是通过 Let's Encrypt 自动获取和更新的。

前言

知道这个可 docker 容器化部署的全自动申请 SSL 证书项目,还是之前安装 outline 的时候,官方给的 docker compose 配置里加的。了解了 https-portal 作用功能之后,感觉到它就是一个宝藏,立马决定作为后续服务器部署项目必备项目了。

回顾搞开发的阶段,一开始是直接服务器裸装 nginx,PHP 等软件的,有时甚至还会搞编译安装。经常会因为安装过程中的某些问题困扰好几天,全网搜索解决方案而抓耳挠腮。后面遇到有同事使用 BT 面板之类的运维面板,可以直接通过 web 页面去安装需要服务器软件了。当时还感觉使用 BT 面板虽然方便,但在服务器上运行自己不掌握的指令脚本(BT 使用 python 开发),总感觉有点别扭。好像之前动则大几个小时的安装折腾突然变成了笑话,努力变成了无用功。

到了现在,接触了 docker 容器化部署之后,突然感觉软件安装进入了另一个新的阶段。只要配置好 docker-compose.yml 文件,执行启动,一个可以相当复杂,依赖 N 多个软件服务支持的项目,可以立马启动起来,软件部署难度下降了好几个 level。

之前 BT 面板就有这样的自动续约 SSL 证书的服务,https-portal 属于 docker 容器化部署的一类,也都是基于 Let's Encrypt 自签证书。

安装 https-portal

首先是创建对应的目录和映射文件夹。ssl-certs 是存放证书的,vhosts 是存放静态网站代码。https-portal 一般是反向代理到本地项目的服务端口,在共享网络内甚至不需要映射出来端口,只需要 expose 就可以通过服务+端口访问。

pwd
/root/https-portal_project
ll
total 28
drwxr-xr-x  4 root root 4096 Aug  4 16:00 ./
drwx------ 25 root root 4096 Aug  4 16:11 ../
-rw-r--r--  1 root root 1398 Aug  4 16:10 docker-compose.yml
-rw-r--r--  1 root root   55 Apr  2 14:54 .env
drwxr-xr-x 20 root root 4096 Jun  5 15:56 ssl-certs/
drwxr-xr-x  5 root root 4096 Jul  8 18:51 vhosts/
cat .env 
COMPOSE_PROJECT_NAME=https-portal
RENEW_MARGIN_DAYS=30

.env 配置了项目名称和证书更新边界天数,就是证书离过期日期的多少天开始自更新证书。

services:
  https-portal:
    image: steveltn/https-portal:1
    container_name: https-portal
    env_file: ./.env
    ports:
      - '80:80'
      - '443:443'
    networks:
      - internal
      - shared
    restart: always
    volumes:
      - ./ssl-certs:/var/lib/https-portal
      - ./vhosts:/var/www/vhosts
    healthcheck:
      test: ["CMD", "service", "nginx", "status"]
      interval: 30s
      timeout: 20s
      retries: 3
    environment:
      DOMAINS: >
        xx.seasidecrab.com -> http://xx:12345
      STAGE: 'production'
      WEBSOCKET: 'true'
      CLIENT_MAX_BODY_SIZE: '0'
    cap_add:
      - SYS_ADMIN
networks:
  internal:
    driver: bridge
  shared:
    external: true
    name: shared-services
  • internal 属于当前的 docker-compose 网络,相当于是局域网,可以访问在当前 docker-compose 定义的服务
  • shared-services 是手动创建的共享网络,使 https-portal 可以通过服务名+端口访问到其他服务

https-portal 配置

所有配置在项目 github 上有详细说明:SteveLTN/https-portal - github

域名定义

只要定义了域名,https-portal 启动的时候会检查对应的域名证书目录是否存在,如果不存在会自动申请并配置好。

默认的静态域名站点:DOMAINS: 'example.com'。会在 vhosts 下生成域名目录,然后将静态站点代码放进目录就可以正常访问。

多域名定义

多个域名是在每一个域名定义结尾加一个 ,

比如 DOMAINS: 'example.com, a.net, b.cn'

但这样如果很多站点的情况下,修改维护会比较麻烦,此时可以使用 YAML 语法支持的多行折叠操作符 >

https-portal:
  # ...
  environment:
    DOMAINS: >
      wordpress.example.com -> http://wordpress:80,
      gitlab.example.com -> http://gitlab
    OTHER_VARS: ...
需注意,末尾的 , 需要保留。

作者推荐的是 >,测试另一个多行折叠符 | 也是有效的。

几个多行折叠符区别:

  • |:保留换行
  • |-:保留换行,但去掉最后换行
  • >:折叠换行为空格(末尾会有一个换行符)
  • >-:折叠换行,并删除最后换行

反向代理

域名和服务之间增加一个 ->,表示反向代理。

wordpress.example.com -> http://wordpress:80

重定向

域名和服务之间增加一个 =>,表示重定向。

example.com => https://target.example.com

多上游源(Upstreams)

域名指向多个服务,服务之间使用 | 分隔。其他参数 [weight=2 max_conns=100] 表示权重和最大连接数。

https-portal:
  # ...
  environment:
    DOMAINS: 'wordpress.example.com -> http://wordpress1:80|wordpress2:80[weight=2 max_conns=100]

HTTP 基本认证

认证用户和密码定义在域名前,具体如下:

https-portal:
  # ...
  environment:
    DOMAINS: 'username:password@example.com -> <upstream>'

重载 nginx 配置文件

查看 https-portal 日志发现 www 的一些请求报错,核对确定是迁移到 blog 下了,此时需要对 /xx/xx.html 请求跳转到 blog 下。

2026/08/04 10:01:58 [error] 348#348: *555 open() "/var/www/vhosts/www.seasidecrab.com/amp/359.html" failed (2: No such file or directory), client: 223.88.136.245, server: www.seasidecrab.com, request: "GET /amp/359.html HTTP/1.1", host: "www.seasidecrab.com"
2026/08/04 10:02:01 [error] 348#348: *556 open() "/var/www/vhosts/www.seasidecrab.com/Web/747.html" failed (2: No such file or directory), client: 120.227.52.102, server: www.seasidecrab.com, request: "GET /Web/747.html HTTP/1.1", host: "www.seasidecrab.com"

相当于要把 *.html 给 301 重定向到 blog 下,www 本身静态站点,保留其他的资源类访问。

正常的 nginx 配置反向代理和 301 重定向:

location ~ \.html$ {
    proxy_pass https://blog.seasidecrab.com$request_uri;

    proxy_set_header Host b.com;
    proxy_set_header X-Real-IP $remote_addr;
}

location ~ \.html$ {
    return 301 https://blog.seasidecrab.com$request_uri;
}

https-portal 增加 nginx 站点自定义配置:

https-portal:
  # ...
  volumes:
    - ./ssl-certs:/var/lib/https-portal
    - ./vhosts:/var/www/vhosts
    - ./conf/www.seasidecrab.com.ssl.conf.erb:/var/lib/nginx-conf/www.seasidecrab.com.ssl.conf.erb:ro

ssl 对应的 https 访问,然后按照文档提示修改 erb 模版,添加 html 301 跳转(修复一下报错)。

最终版本 www.seasidecrab.com.ssl.conf.erb

# This file overrides default nginx HTTPS settings for my.example.com
# Mount this file as "/var/lib/nginx-conf/my.example.com.ssl.conf.erb"

server {
    listen 443 ssl;
    #listen 443 ssl;
    #http2 on;

    # domain.name will be "my.example.com", you can also hard-code it.
    server_name <%= domain.name %>;

    # ssl on;
    ssl_certificate <%= domain.chained_cert_path %>;
    ssl_certificate_key <%= domain.key_path %>;

    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_session_cache shared:SSL:50m;
    ssl_ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA;
    ssl_prefer_server_ciphers on;

    ssl_dhparam <%= dhparam_path %>;

    location ~* \.html$ {
        return 301 https://blog.seasidecrab.com$request_uri;
    }

    location / {
        root /var/www/vhosts/<%= domain.name %>;
        index index.html;
        try_files $uri $uri/ /index.html;
    }
}

测试没有报错。

还有很多跟 nginx 相关的配置,比如环境变量设置,或者限制访问 ip 之类的,没怎么用到,这里不做载录。

文章目录