前言
本文是在对应域名上使用linux服务器部署Logto后端与使用其预制前端的实现文章,本文将会详细解释在linux服务器上部署Logto的流程与注意事项。
本文将会使用被提供的域名与被提供的无管理员权限的linux服务器演示部署流程,同时本文中所有 密钥,和私密信息将会被隐藏,读者复现可以使用自己的密钥与信息复现流程。本文使用域名 www.testweb.com(https) 与 auth.testweb.com(https) 作为演示域名,该域名初始没有业务页面,因此需要自行开发主页,对接 Logto 身份回调。
1. 注意事项与流程简要
由于主要部署的是无管理员权限的 linux 服务,所以部署采用 docker 容器运行在后端服务器上,同时需要注意的是,部署的目标软件是 Logto,所以大部分的Docker容器中运行的软件功能为:辅助Logto运行。
实现流程中使用的docker镜像内的软件为: 1. logto (登录系统后端) 2. postgres (Logto需要的数据库) 3. caddy (请求转发) 4. nginx (Nginx TLS 预嗅探四层分流) 5. web(自制express后端请求)
1. 注意事项
Logto 默认监听两个端口: 3001面向用户、OIDC登录页、账号中心。3002是管理控制台。建议不要直接把 3002 暴露公网,本文通过 SSH 本地端口转发访问控制台。
由于Logto本身为后端程序,它的登录预制网页需要由前端网页调用Logto提供的对应网页框架的函数包,同时还需要提供Logto登录完成后的回调处理程序,对应的详细可以参考:
因为Logto预制登录页面需要前端触发,所以需要在域名上提供前端网页访问和Logto:3001接口访问。由于使用URL请求分割 /api/* 等前缀来实现请求分离太过麻烦,所以这里演示使用了子域名(auth)作为Logto:3001请求的入口,以及主域名(www)作为用户访问前端网页的入口
2. 部署流程简要
本演示的部署流程可以简要为以下流程图
1.www.testweb.com 与auth.testweb.com 长期开放443转发到调试linux:3000机作为请求入口,短期开放 80 端口到调试linux:3000方便接收ACME证书(ACME HTTP-01 挑战依赖 80 端口,证书签发成功后也建议保留 80,用于自动续期。)
ssh建立调试linux服务器到电脑之间的端口转发,本演示使用 SSH 本地端口转发,将服务器宿主机的 8080 映射到本机,用来访问 Logto 控制台;该端口不向公网开放。
Nginx 容器绑定宿主机3000,作为整个系统流量入口。Nginx 在 stream 层使用ssl_preread:如果是 TLS 流量,转发到 Caddy 的 443;普通 HTTP 流量转发到 Caddy 的 80。
Caddy 收取 80 端口,自动请求和续费ACME证书,对 443 端口区分 auth 与 www, 分别转发至Logto监听的 3001 端口与使用node自制的web容器暴露的3003端口
www请求由web返回对应前端页面,auth请求由logto处理,经由Caddy中转返回网页,完整流程
在 auth 域名完整链路调通前,仅靠 SSH 转发可以进入控制台,但登录、OIDC、账号中心等 auth 域名下的功能无法正常工作,可以作为排查链路的判断依据。
Let's Encrypt 存在速率限制,频繁重启、反复触发ACME容易被限流,冷却约一小时;调试优先走明文 HTTP,确认路由无误后再开启自动 HTTPS。
2. 具体部署流程
以下演示部署的docker-compose.yml为分片服务,文章最后会附上完整docker-compose.yml。
本部署中所有的软件在docker的同一个 logto-net 网络中,方便互相访问
1. 部署 logto 与对应的 postgres
首先使用docker容器部署logto与postgres,本演示的部署使用规则: logto依赖postgres,同时postgres使用持久化存储。docker容器监听宿主机 8080 并直接转发至容器内的3002端口(logto控制台端口),方便本机可视化调试控制台
需要注意的是,第一次启动 postgres 需要附加命令,启动完成后删除
entrypoint: ["sh", "-c", "npm run cli db seed && npm start"]
如果希望容器每次启动安全初始化,把 seed 改为
npm run cli db seed --swe && npm start
--swe (seed-when-empty)只会在数据库为空时写入种子数据,已经存在表时直接跳过,避免唯一主键冲突报错。
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: logto
POSTGRES_PASSWORD: logto
POSTGRES_DB: logto
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U logto -d logto"]
interval: 5s
timeout: 5s
retries: 5
networks:
- logto-net
logto:
image: ghcr.io/logto-io/logto:latest
depends_on:
postgres:
condition: service_healthy
entrypoint: ["sh", "-c", "npm start"]
ports:
- "8080:3002"
environment:
- HOST=0.0.0.0
- TRUST_PROXY_HEADER=1
- DB_URL=postgres://logto:logto@postgres:5432/logto
- ENDPOINT=https://auth.testweb.com
- ADMIN_ENDPOINT=http://127.0.0.1:8080
networks:
- logto-net
完成第一步docker-compose.yml后,使用ssh连接控制台页面(用你自己的密钥)
ssh -p 00000 -i ./key -L 8080:127.0.0.1:8080 user@127.0.0.2
这里如果是第一步转发成功了,页面返回应该是Logto的控制台注册页面,进去后是不断报错的控制台页面,这里我放一张能用的控制台大致参考一下页面

2. 部署 Caddy
完成上述步骤后,部署Caddy用于转发 auth 与 www 的域名。由于有Nginx在前面分流 80 与 443 的功能在,Caddy不需要docker映射端口,但是需要对Caddy的数据进行持久化存储,这样可以在Caddy已经成功申请证书后,防止docker容器重启后Caddy反复申请证书。对应的docker-compose.yml的分片如下
caddy:
image: caddy:latest
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
depends_on:
- logto
networks:
- logto-net
同时caddy也需要自己的Caddyfile,当前演示只用处理 auth 与 www 即可,演示Caddyfile如下
auth.testweb.com {
reverse_proxy logto:3001 {
header_up Host {host}
header_up X-Forwarded-Server {server_name}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-For {remote}
}
}
www.testweb.com {
reverse_proxy web:3003
}
上文Caddyfile中Caddy会自动处理https请求,但是从刚刚开始的调试来讲,应该先测试http请求能否处理,然后再测试https,这样做可以缩减可能发生的报错的范围,尽量让调试报错可处理,所以原始测试的Caddyfile为
官方文档: 全局关闭自动https(防止可能的http请求跳转https)
{
auto_https off
}
http://auth.testweb.com {
reverse_proxy logto:3001 {
header_up Host {host}
header_up X-Forwarded-Server {server_name}
header_up X-Forwarded-Proto {scheme}
header_up X-Forwarded-For {remote}
}
}
http://www.testweb.com {
reverse_proxy web:3003
}
启动容器,可以使用以下命令测试转发是否成功(需要注意这时候docker容器要打开caddy对宿主机的3000端口的监听)
curl -v -H "Host:auth.testweb.com" http://127.0.0.1:3000
如果测试成功的话应该会返回 logto 对应的未知页面的跳转
3. 部署 Nginx
完成以上流程后,由于演示网页的80 请求和 443 请求均转发到调试linux上的3000端口 ,而ACME证书返回则是使用 http(80) 返回的,所以需要使用 nginx 对 80 请求和 443 请求进行分流。不过这里并不用单独调整 Caddyfile 特殊处理80请求,因为Caddyfile对ACME回应的路由有特殊豁免,所以只需要转发即可。
Nginx 容器内部进程监听 3000, ports: "3000:3000"则将宿主机 3000 映射进容器。作为外部请求的入口,以下是docker-compose.yml的分片
nginx-sniff:
image: nginx:1.27-alpine
container_name: nginx-sniff
restart: always
ports:
- "3000:3000"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- caddy
networks:
- logto-net
Nginx也有对应的nginx.conf,本演示根据发送的请求是否是TLS请求来分流80请求和443请求到Caddy,如下
worker_processes auto;
events {
worker_connections 1024;
}
stream {
ssl_preread on;
upstream caddy_tls {
server caddy:443;
}
upstream caddy_http {
server caddy:80;
}
map $ssl_preread_protocol $backend {
~^TLS caddy_tls;
"" caddy_http;
default caddy_http;
}
server {
listen 3000;
proxy_connect_timeout 5s;
proxy_timeout 30s;
proxy_pass $backend;
}
}
对于Nginx的分流效果的测试,可以将Caddy中的转发先调整为 respond "字符串",然后尝试向 3000端口发送带头的请求,观察返回的字符串的长度。
在完成 Nginx的部署之后,可以将Caddy改变为https(上文给出的配置)状态,尝试请求ACME证书(一定要确定域名的80端口是开放的),然后去观察调试日志。如果证书请求成功,那么完成了上述部署流程的 auth 就可以使用 3001请求了,那么控制台的效果就会如下

可以尝试随机点击一个选项查看控制台是否能查询和更改
4. 部署自制网页,提供前端访问
在控制台成功使用后,需要为 www 提供用户访问页面,提供访问页面前,先使用控制台创建网页回调,具体流程可以参考
本演示使用的不是实例文的React框架,使用的是后端express框架。注册完回调后,需要使用node在服务器内构建docker容器,本示例构建的Dockerfile为
FROM node:22-alpine
WORKDIR /app
COPY package.json ./
RUN npm install
COPY testLogin.js ./
COPY testweb ./testweb
EXPOSE 3003
CMD ["node", "testweb.js"]
这里express的主代码的调用路径要和上述Dockerfile内复制进去的项目路径一致,否则会GET失败,网页框架的依赖依靠 package.json + npm install 自动构建,需要将 package.json 提前传入。
这里我使用的是 FileZilla 将文件传入服务器
构建web对应的docker-compose.yml为
web:
build: ./web
restart: unless-stopped
expose:
- "3003"
volumes:
- ./web/testweb:/app/testweb
networks:
- logto-net
需要注意的是,expose仅为文档标记,不会把端口映射到宿主机;同 Docker 网络内,Caddy 通过web:3003 访问服务;服务需要监听 0.0.0.0:3003,否则别的容器无法连接。
5. docker-compose.yml
完整的docker-compose.yml为
version: '3.8'
services:
postgres:
image: postgres:16-alpine
environment:
POSTGRES_USER: logto
POSTGRES_PASSWORD: logto
POSTGRES_DB: logto
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U logto -d logto"]
interval: 5s
timeout: 5s
retries: 5
networks:
- logto-net
logto:
image: ghcr.io/logto-io/logto:latest
depends_on:
postgres:
condition: service_healthy
entrypoint: ["sh", "-c", "npm start"]
ports:
- "8080:3002"
environment:
- HOST=0.0.0.0
- TRUST_PROXY_HEADER=1
- DB_URL=postgres://logto:logto@postgres:5432/logto
- ENDPOINT=https://www.testweb.com
- ADMIN_ENDPOINT=http://127.0.0.1:8080
networks:
- logto-net
caddy:
image: caddy:latest
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
depends_on:
- logto
networks:
- logto-net
nginx-sniff:
image: nginx:1.27-alpine
container_name: nginx-sniff
restart: always
ports:
- "3000:3000"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- caddy
networks:
- logto-net
web:
build: ./web
restart: unless-stopped
expose:
- "3003"
volumes:
- ./web/testweb:/app/testweb
networks:
- logto-net
volumes:
postgres_data:
caddy_data:
caddy_config:
networks:
logto-net:
driver: bridge
3. 后续以及补充
完成上述部署流程后,打开域名的主页应该会根据你写好的web网页跳转到你需要的网页上,本演示的效果为
点击中间的登录后,会调用Logto的登录页面,可以发现域名头为 auth
完成登录流程后,登录回调由 Logto SDK 的handleAuthRoutes捕获,完成 code 交换、会话写入后,默认重定向至首页 "/" ;可以通过 postSignInRedirectPath 修改登录成功跳转目标。
在部署完成后,可以发现主域名 www 其实可以被添加进 React 等前端框架,并部署完整的网站,服务器同时为网站服务器和后端服务器,读者如果想要分离后端与前端服务器,可以自行研究。
部署小结
用户 → 公网 80/443 → 服务器:3000 → Nginx(stream ssl_preread)
→ HTTP → Caddy:80
→ TLS → Caddy:443
Caddy:
auth.testweb.com → logto:3001
www.testweb.com → web:3003(Express)
SSH 转发:本机 ↔ 服务器 8080 ↔ logto:3002(控制台)