官方地址: https://gzctf.gzti.me/zh
开始之前
当前资源: 一台公网服务器 , 一个域名
请先在 DNS 服务商将域名映射到服务器IP。若没有域名也可忽略,后续步骤差不多,类比参考即可。
需要注意的是:
Let’s Encrypt(免费 CA)
从 2025 年 7 月起,Let’s Encrypt 已在 Staging(测试)环境开始签发 IP 地址证书,并计划在晚些时候推向生产环境。
这类证书时效极短(约 6 天),只能通过 HTTP-01 或 TLS-ALPN-01 挑战(无法用 DNS-01)来证明对该 IP 的控制权
参考文档: https://letsencrypt.org/2025/07/01/issuing-our-first-ip-address-certificate/
在正式开始部署之前我们需要先阅读相关的文档。
我们只有一台服务器,那么直接采用docker单机部署。
参考: https://gzctf.gzti.me/zh/guide/deployment
缺点: 安全性较低,资源限制相关功能不够完善
然后我们可以先部署 1panel 面板,方便后面的 SSL 证书申请及部署,以及配置反向代理等
还可以直接跳过单独配置 docker (1panel自动化部署)
1panel参考文档: https://github.com/1Panel-dev/1Panel
然后登录面板 -> 应用商店安装 OpenResty (默认监听 80 443端口)
开始部署
初始化配置
现在我们创建一个 GZCTF
工作目录,新建文件 appsettings.json
compose.yml
注意: 这里我直接在 root
环境部署,不推荐实际生产环境这么做,一般推荐创建一个专门的用户,授予满足正常运行条件的权限即可。
编辑 appsettings.json
注意 json
中不允许有注释,此处注释作为解释,实际运行请删除。
此处为参考配置,详细的 appsettings.json
配置字段介绍地址: https://gzctf.gzti.me/zh/config/appsettings
当前需要修改的初始化配置:
<Your POSTGRES_PASSWORD>
<Your XOR_KEY>
<Your PUBLIC_ENTRY>
{
"AllowedHosts": "*",
"ConnectionStrings": {
"Database": "Host=db:5432;Database=gzctf;Username=postgres;Password=<Your POSTGRES_PASSWORD>" // 数据库密码
},
"EmailConfig": {
"SenderAddress": "",
"SenderName": "",
"UserName": "",
"Password": "",
"Smtp": {
"Host": "localhost",
"Port": 587
}
},
"XorKey": "<Your XOR_KEY>", // 此处配置加密密钥,用于加密数据库中比赛的私钥信息,可为任意长度的任意字符串。
"ContainerProvider": {
"Type": "Docker", // or "Kubernetes" // 单机部署不修改
"PortMappingType": "Default", // or "PlatformProxy"
"EnableTrafficCapture": false,
"PublicEntry": "<Your PUBLIC_ENTRY>", // or "xxx.xxx.xxx.xxx" 容器后端的公网地址,用于生成比赛的访问地址,展示给参赛队伍。
// optional
"DockerConfig": {
"SwarmMode": false,
"Uri": "unix:///var/run/docker.sock"
}
},
"CaptchaConfig": {
"Provider": "None", // or "CloudflareTurnstile" or "HashPow"
"SiteKey": "<Your SITE_KEY>",
"SecretKey": "<Your SECRET_KEY>"
},
"ForwardedOptions": {
"ForwardedHeaders": 7,
"ForwardLimit": 1,
"TrustedNetworks": ["192.168.12.0/8"] // 可信网络,后续使用反向代理时,我们需要配置成docker网关IP
}
}
编辑 compose.yml
当前需要修改的初始化配置:
<Your GZCTF_ADMIN_PASSWORD>
<Your POSTGRES_PASSWORD>
ports
services:
gzctf:
image: registry.cn-shanghai.aliyuncs.com/gztime/gzctf:develop
restart: always
environment:
- "GZCTF_ADMIN_PASSWORD=<Your GZCTF_ADMIN_PASSWORD>" // 平台初始管理员密码 // 用户名默认 admin
# choose your backend language `en_US` / `zh_CN` / `ja_JP` ...
- "LC_ALL=zh_CN.UTF-8"
ports:
- "80:8080" // 外部80端口在前面1panel中被openresty占用了,我们修改为8080等端口都行 -> 8080:8080
volumes:
- "./data/files:/app/files"
- "./appsettings.json:/app/appsettings.json:ro"
# - "./kube-config.yaml:/app/kube-config.yaml:ro" # this is required for k8s deployment
- "/var/run/docker.sock:/var/run/docker.sock" # this is required for docker deployment
depends_on:
- db
db:
image: postgres:alpine
restart: always
environment:
- "POSTGRES_PASSWORD=<Your POSTGRES_PASSWORD>" // 数据库密码.与 appsettings.json 中一致
volumes:
- "./data/db:/var/lib/postgresql/data"
运行 docker compose up -d
启动
现在访问 <Your PUBLIC_ENTRY>:8080
就能正常进入平台啦。
// compose.yml
中最初配置端口映射为 8080:8080 会接收 0.0.0.0 的流量,仅仅是测试正常访问使用,后续方向代理时会修改重载
常见Q&A
如果在 Chrome
访问时出现这种情况,是网站主域或其他端口域配置了SSL证书,但是此8080端口没被证书覆盖到,造成的 HSTS安全性问题(协议自动升级)。具体细节不赘述
短期解决办法为 在 Chrome
搜索框输入: chrome://net-internals/#hsts
进入 HSTS
设置 在如图位置填入域名进行删除HSTS缓存就好了
要想永久解决,在后续会一并处理
然后再次访问的时候指定HTTP协议访问,就能正常访问啦。
反向代理
现在我们来为网站配置反向代理,将80 , 443 端口流量均代理到 8080 端口
访问登录 1panel 面板 -> 网站 -> 创建 -> 反向代理
主域名: example.com (你的域名)
其他域名: 如果映射有子域名也可以填上如: www.example.com
代理地址: 127.0.0.1:8080 (同一台主机内网,直接选HTTP减小开销)
其余按照要求填写即可。
确认后,点击配置:
反向代理 -> 源文
我们需要确保存在下面配置: (为了OpenResty 反向代理的 Websocket兼容) (GZCTF 使用了Websocket消息通信)
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 $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
这里贴一个示例源文:
location ^~ / {
proxy_pass http://127.0.0.1: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 REMOTE-HOST $remote_addr;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $http_connection;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Port $server_port;
proxy_http_version 1.1;
add_header X-Cache $upstream_cache_status;
add_header Cache-Control no-cache;
proxy_ssl_server_name off;
proxy_ssl_name $proxy_host;
add_header Strict-Transport-Security "max-age=31536000";
}
设置完成之后,我们保存。回到 网站 -> 重载 OpenResty
现在访问 http://example.com
反代成功
现在 ban 掉前面用于测试对0.0.0.0开启的8080端口监听,将 compose.yml
改为监听 127.0.0.1
// 安装的OpenResty
容器默认是以 --network=host
模式运行的, 容器内的 127.0.0.1
就是宿主机回环, 所以在 GZ 的 compose.yml
中监听 127.0.0.1 即可,如果不在此模式运行,那么我们需要 把两个容器放在同一个自定义网络,用服务名直连。
然后在项目目录下执行重新挂载 docker 并重启。
docker compose down && docker compose up -d
现在尝试访问 example.com:8080 已经访问不到了。80端口依然正常反代
配置SSL证书
证书 -> Acme账户 -> 创建
邮箱随便填
回到 证书 -> 申请证书 -> 从网站中获取 -> 选择前面创建的网站 -> Acme账户 -> 选择刚刚创建的账户
主域名会自动填入,也可以自己调整好,当你设置有子域名 DNS 解析时,请将其他域名也一并填入,或者申请 *.example.com 的通配符域名。 验证方式选为 HTTP -> 确认
回到网站 -> 点击创建的网站的配置 -> HTTPS -> 启用HTTPS
HTTP选项根据需要自己修改,这里直接强制协议升级
选择刚刚申请的证书保存 然后重载
然后顺便为面板也使用 HTTPS , 避免因为同域下的协议升级导致前面的HSTS安全缓存问题。
面板设置 -> 安全 -> 启用面板SSL -> 直接使用前面申请的证书 -> 保存重载。(会需要重新登录)
现在我们访问 GZCTF 平台可以看到,全线HTTPS启用。
301跳转
注意,如果配置了 www 等子域名映射到同一个网站,我们最好将子域301到主域
网站 -> 配置 -> 重定向 -> 301 -> 目标主域
配置可信网络域
有时候我们需要在平台内部看到登录/操作IP,但是现在经过反向代理后我们的 IP 均会变为网关地址。
这时候就需要配置可信网络域,将反代传递的 XFF IP记录下来。
(图为已经调整好后的图)
docker网络可以从 面板的 容器 -> 网络看到。
也可以在项目目录下使用下面命令获取。
docker network inspect gzctf_default | grep -E '"Subnet"|"Gateway"'
现在对 appsettings.json
文件再次编辑:
如果没有则添加: ForwardedOptions
"ForwardedOptions": {
"ForwardedHeaders": 7,
"ForwardLimit": 1,
"ForwardedForHeaderName": "X-Forwarded-For",
"TrustedNetworks": ["172.19.0.0/16", "127.0.0.0/8"],
"TrustedProxies": ["172.19.0.1", "127.0.0.1"]
}
然后在 1panel 重启或重新挂载均可以
docker compose down && docker compose up -d
然后IP记录将回归正常。
邮件配置
邮件发送有多种选择: 自建邮服 , 事务型发信服务 , 企业邮箱(163/腾讯企业邮等) , 个人邮箱(163/QQ 免费邮箱)等
综合因素我们选择 事务型发信服务
, 当然,如果你的 DNS 服务商直接免费提供邮件服务那更好。
这里我采用阿里云 DirectMail (总2000条邮件免费额度, 每日限额200条免费,按量也很便宜)
下面正式开始!
进入阿里云控制台,搜索 邮件推送 ,开通
来到 -> 邮件设置->发信域名 -> 新增域名 -> 填入你的域名。
点击配置
接下来我们将根据给出的要求配置,在DNS服务商处,进行配置
先来填写 域名记录处记录:
需要增添 spf 验证记录: 记录类型 txt , IP地址/目的地填写前面获取到的 spf 验证的记录值
然后增添 MX 验证: 记录类型 txt , IP地址/目的地填写前面获取到的 MX 验证的记录值, 优先级设置为 10
现在填写 子域名记录:
添加所有权验证记录: 子域名填写对应的主机记录,目的地填写记录值,类型TXT
添加DKIM验证记录,添加DMARC验证记录,方法和前面一样。注意公钥需要复制完全。
全部填写完后,等待DNS解析生效即可(一般5-30分钟)。然后进行验证,等待验证通过。
然后点击发信地址 -> 新建发信地址 -> 发信类型选择触发邮件 -> 其余按要求填写即可。
按要求设置 SMTP 密码
SMTP服务地址: smtpdm.aliyun.com ,SMTP服务端口号:25或80或465(SSL加密)。
现在回到 appsetting.json
进行编辑,如果没有就添加 EmailConfig
"EmailConfig": {
"SenderAddress": "你前面创建的发信地址",
"SenderName": "发信名称,可自定义",
"UserName": "发信名称,与发信地址相同即可",
"Password": "SMTP密码",
"Smtp": {
"Host": "smtpdm.aliyun.com",
"Port": 465
},
"BypassCertVerify": false
},
重启或重新挂载。
docker compose down && docker compose up -d
进入平台,启用邮件验证
注册账号,进行验证,发送成功
端口范围设置
参考地址: https://gzctf.gzti.me/zh/guide/issues/port-range
在进行端口设置后,配合前面 HTTPS 的设置,会有一个新问题产生。即 分配的题目端口不再 SSL 证书信任范围之内,将会抛出如之前 HSTS 不信任的问题。
我们可以通过在最顶层的 OpenResty 容器 进行再次端口代理即可。