1Panel 面板部署
★ 前置条件
- 已安装 1Panel 面板
- 已在 1Panel 中安装 OpenResty 应用
- 已在 1Panel 中通过“网站”功能创建 PHP 运行环境网站
- 已完成域名解析和 SSL 证书配置
★ 1Panel 容器内路径
1Panel 的 OpenResty Docker 容器会将宿主机路径映射到容器内路径:
| 宿主机路径 | 容器内路径 |
|---|---|
/{1panel安装路径}/www/sites/域名/ | /www/sites/域名/ |
/{1panel安装路径}/www/conf.d/ | /etc/nginx/conf.d/ |
所有 Nginx 配置文件中引用的路径,必须使用容器内路径(如 /www/sites/...),绝不能写宿主机路径(如 /{1panel安装路径}/www/sites/...)。因为 Nginx 运行在容器内部,它只能“看”到容器内的目录结构,看不到宿主机的真实路径。
验证你的容器挂载映射:
docker ps --format "table {{.Names}}\t{{.Image}}" | grep -i openrestydocker inspect {openresty容器名} --format '{{json .Mounts}}' | python3 -m json.tool提示:此处的 {openresty容器名} 是第一条指令中的输出。
一、上传源码
将项目所有文件上传到网站目录的 index/ 子目录中。
1Panel 网站目录结构:
二、编辑配置
1. internal_host 跨容器通信
1Panel 的 OpenResty 和 PHP 容器可能使用不同的网络模式:
| 容器 | 典型网络模式 | 说明 |
|---|---|---|
| OpenResty | host | 共享宿主机网络,127.0.0.1 可访问 |
| PHP-FPM | 1panel-network(bridge) | 独立网络,127.0.0.1 指向自己 |
PHP 容器内的 127.0.0.1 指向的是容器自身,访问不到 OpenResty 的 18500 端口。
因此,请先排查是否存在此问题:
docker inspect {OpenResty容器名} --format '{{.HostConfig.NetworkMode}}'docker inspect {PHP容器名} --format '{{.HostConfig.NetworkMode}}'如果你的输出和我一样(OpenResty 为 Host,PHP 为 1panel-network),请修改 config.php 中的 internal_host 配置,将 http://127.0.0.1:18500 改为 Docker 网桥上宿主机的 IP 地址(例如 http://172.18.0.1:18500)。
查找网关 IP 方法:
docker inspect {PHP容器名} --format '{{range .NetworkSettings.Networks}}{{.Gateway}}{{end}}'2. config.php
打开 ./api/config.php 文件进行修改:
建议直接编辑当前版本源码自带的 api/config.php,只修改部署相关值。下面列出 v1.11.0 的默认配额和内部通信配置,升级时应以新版本文件为准。
警告:注意修改高亮行内容。将 http://127.0.0.1:18500 改为 Docker 网桥上宿主机的 IP 地址。
<?php
return [
// 短链域名
'domain' => 'https://{你的域名}', // 改为你的短链域名,用于拼接完整短链
// 前端面板域名(用于 CORS,与短链跳转域名独立)
// 如果前端面板与短链服务部署在不同域名,请改为面板实际域名(如 https://panel.example.com)
// 未配置时回退使用 domain,保持向后兼容
'panel_origin' => '',
// 前端面板总开关
// false 时 PHP 层直接返回 403,前端不再可访问(短链跳转不受影响)
// 同时建议在 nginx 的 /api/ 块启用 return 403(双层独立生效)
'panel_enabled' => true,
// 时区:可使用固定偏移,也可使用会自动处理夏令时的 IANA 时区名称
'tz_offset' => '+08:00',
// 冷存储 JSON 文件路径
'perm_path' => __DIR__ . '/../backend/data/perm.json', // 永久短链数据文件
'temp_path' => __DIR__ . '/../backend/data/temp.json', // 临时短链数据文件
// API Key 存储
'keys_path' => __DIR__ . '/../backend/data/keys.json', // Key 存储文件
'key_ttl_days' => 7, // 常驻 Key 有效期(天)
'onetime_pool_size' => 20, // 一次性 Key 池大小
// 数量限制(四类 limit 总和不得超过 30000,受 10MB su_url 共享字典限制)
'perm_limit' => 10000, // 用户永久短链
'temp_limit' => 500, // 用户临时短链
// 服务密钥专用配置(仅无头链路生效)
'svc_code_length' => 4, // 服务密钥生成的短码长度:4 或 5
'svc_perm_limit' => 18000, // 服务密钥永久短链上限
'svc_temp_limit' => 1500, // 服务密钥临时短链上限
// TTL 上限,临时短链最长存活时间(秒),默认 1 年
'ttl_max' => 365 * 24 * 3600,
// 保留短码(与 nginx 路由前缀对齐,禁止用户注册为自定义短码)
// 新增路由时需同步更新此列表
'reserved_codes' => ['api', 'stat', 'admin', 'data', 'lua', 'headless', 'internal'],
// 内部 OpenResty API 地址(仅本地 18500 端口,不对外暴露)
// 注意!! 如果 PHP 容器使用 bridge 网络,127.0.0.1 指向的是容器自身,访问不到 OpenResty,此时需改为 Docker 网桥的宿主机 IP(例如172.19.0.1)
//如果你的 PHP 是直接部署的,则无需改网桥IP,保持 127.0.0.1 即可
'internal_host' => 'http://127.0.0.1:18500', //具体内网地址请自行查阅!
'internal_timeout' => 2.0, //内部接口请求超时时间(秒)
'internal_token_path' => __DIR__ . '/../backend/data/internal_token',
// 不要在 config.php 中缓存 internal_token;PHP 每次内部调用都会从上面的路径读取。
];三、修改 OpenResty 主配置
使用 1Panel 面板的文件管理打开文件:
/{1Panel安装目录}/apps/openresty/{openresty容器名}/conf/nginx.conf在 http 块开头(include mime.types; 之前)添加以下内容:
警告:注意修改高亮行内容。
# ═══════════════════════════════════════════════════
# 声明共享字典和限流区域
# ═══════════════════════════════════════════════════
# ———— 共享字典 ——————————————————————————————————————
# su_url 10m:与 config.php 的容量假设一致(四类 limit 总和 30000 条按 10MB 内存推导)
lua_shared_dict su_url 10m;
lua_shared_dict su_exp 2m;
lua_shared_dict su_meta 1m;
lua_shared_dict su_blocklist 1m;
# ———— API 限流:每 IP 每分钟 30 次请求 ————————————————
limit_req_zone $binary_remote_addr zone=api:10m rate=30r/m;
# ———— 跳转限流:每个 IP 每秒 2 次,突发 10 次 ——————————
limit_req_zone $binary_remote_addr zone=redirect_burst:10m rate=2r/s;
# ———— Worker 初始化:注册定时器(每次 worker 启动/reload 均执行)————
# 注意:init_worker_by_lua_block 中 ngx.var 不可用,配置通过 config.lua 读取
# expire_sweep 定时器在此注册,确保 nginx reload 后定时器不丢失
init_worker_by_lua_block {
package.path = "/www/sites/{你的域名}/index/backend/lua/?.lua;;;" .. package.path
local init = require "shorturl.init"
init.init_worker()
}
# ═══════════════════════════════════════════════════
# 内部 Lua API(仅本地访问)
# ═══════════════════════════════════════════════════
server {
listen 18500;
server_name _;
# 必须与 api/config.php 的 internal_token_path 指向同一文件
set $su_internal_token_path "/www/sites/{你的域名}/index/backend/data/internal_token";
location /internal/ {
# 访问控制:允许本地回环 + Docker bridge 网段
# 请将 172.18.0.1/16 替换为你实际的 Docker 网桥子网
allow 127.0.0.1;
allow 172.18.0.1/16;
deny all;
content_by_lua_block {
package.path = "/www/sites/{你的域名}/index/backend/lua/?.lua;;;" .. package.path
local internal = require "shorturl.internal"
internal.dispatch()
}
}
}
# ═══════════════════════[END]═════════════════════════四、修改站点 Nginx 配置
这是最关键的一步。1Panel 创建网站时会自动生成一份 Nginx 配置,我们需要在保留它的基础上添加 Short_NURL 所需配置。请优先复制同版本源码 backend/nginx/nginx.md 中的模板,再替换为 1Panel 容器内路径。
LPA-Key 的 PHP 与 Lua 两端都会在每次内部请求时从文件读取。令牌缺失或不可读时内部 API 会 fail-close,不会绕过认证;使用 php nurl -itk 轮换后无需 reload Nginx 或重启 PHP-FPM。
请从 1Panel 面板的“网站”界面进入你的网站,编辑 Nginx 配置。
警告:注意修改高亮行内容。
# ═══════════════════════════════════════════════════
# ★ 新增配置
# ═══════════════════════════════════════════════════
# ★ 业务参数
set $su_php_fpm "127.0.0.1:9000"; # PHP-FPM 监听地址,按实际环境修改
set $su_domain "https://{你的域名}";
set $su_tz_offset "+08:00";
set $su_perm_path "/www/sites/{你的域名}/index/backend/data/perm.json";
set $su_temp_path "/www/sites/{你的域名}/index/backend/data/temp.json";
set $su_expire_interval 3600;
set $su_redirect_code 302; # 短链跳转状态码,可选 301(永久)或 302(临时)
# ★ 安全响应头(补充 ShortURL 需要的)
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "no-referrer" always;
# ★ 屏蔽敏感目录
location ^~ /data/ {
deny all;
return 404;
}
location ^~ /lua/ {
deny all;
return 404;
}
location ^~ /backend/ {
deny all;
return 404;
}
location ^~ /api/common/ {
deny all;
return 404;
}
location ^~ /api/key/ {
deny all;
return 404;
}
location ^~ /api/lua/ {
deny all;
return 404;
}
location ^~ /api/storage/ {
deny all;
return 404;
}
location ^~ /headless/ {
deny all;
return 404;
}
# ^~ 前缀匹配,拦截所有 /internal/* 路径(不仅是精确的 /internal/)
location ^~ /internal/ {
deny all;
return 404;
}
#———— ★ API 路由 → PHP-FPM ————————————————————————————————————————————————————
# 覆盖原有的 PHP location,API 请求不走默认 PHP 处理
location ^~ /api/ {
limit_req zone=api burst=5 nodelay;
location = /api/create {
limit_except POST { deny all; }
fastcgi_pass $su_php_fpm;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /www/sites/{你的域名}/index/api/routes/create.php;
include fastcgi_params;
}
location = /api/delete {
limit_except POST { deny all; }
fastcgi_pass $su_php_fpm;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /www/sites/{你的域名}/index/api/routes/delete.php;
include fastcgi_params;
}
location = /api/list {
limit_except GET { deny all; }
fastcgi_pass $su_php_fpm;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /www/sites/{你的域名}/index/api/routes/list.php;
include fastcgi_params;
}
location = /api/stat {
limit_except GET { deny all; }
fastcgi_pass $su_php_fpm;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /www/sites/{你的域名}/index/api/routes/stat.php;
include fastcgi_params;
}
location = /api/ping {
limit_except GET { deny all; }
include fastcgi_params;
fastcgi_pass $su_php_fpm;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /www/sites/{你的域名}/index/api/ping.php;
}
}
#———— ★ 无头链路路由 → PHP-FPM(nurl-key 远程管理工具使用)——————————————
location ^~ /headless/api/ {
limit_req zone=api burst=5 nodelay;
location = /headless/api/create {
limit_except POST { deny all; }
fastcgi_pass $su_php_fpm;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /www/sites/{你的域名}/index/headless/create.php;
include fastcgi_params;
}
location = /headless/api/delete {
limit_except POST { deny all; }
fastcgi_pass $su_php_fpm;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /www/sites/{你的域名}/index/headless/delete.php;
include fastcgi_params;
}
location = /headless/api/list {
limit_except GET { deny all; }
fastcgi_pass $su_php_fpm;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /www/sites/{你的域名}/index/headless/list.php;
include fastcgi_params;
}
location = /headless/api/stat {
limit_except GET { deny all; }
fastcgi_pass $su_php_fpm;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME /www/sites/{你的域名}/index/headless/stat.php;
include fastcgi_params;
}
# ~* 大小写不敏感,与其他接口对齐;PHP 侧已做 strtolower
location ~* "^/headless/api/get/([0-9a-zA-Z-]{1,5})$" {
limit_except GET { deny all; }
fastcgi_pass $su_php_fpm;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME {prefix}/headless/get.php;
fastcgi_param PATH_INFO /$1;
include fastcgi_params;
}
}
# ————★ 一次性数据加载(惰性触发)————————————————————————————————
# 站点首次请求时由一个 worker 加锁执行冷启动,后续请求通过共享 inited 标记跳过
# 注意:定时器注册已移至 init_worker_by_lua_block(见主配置),此处仅负责数据加载
access_by_lua_block {
package.path = "/www/sites/{你的域名}/index/backend/lua/?.lua;;;" .. package.path
local su_meta = ngx.shared.su_meta
if not su_meta:get("inited") then
local init = require "shorturl.init"
init.init()
end
}
# ————★ 短链跳转(最低优先级,匹配 1-5 位字母数字)————————————————————
# 正则中的花括号必须用双引号包裹,否则 Nginx 会报错
location ~* "^/([0-9a-z-]{1,5})$" {
limit_req zone=redirect_burst burst=10 nodelay;
limit_except GET { deny all; }
content_by_lua_block {
package.path = "/www/sites/{你的域名}/index/backend/lua/?.lua;;;" .. package.path
local redirect = require "shorturl.redirect"
redirect.go()
}
}
# ══════════════════════[END]═════════════════════════════五、生成 API Key
docker exec {PHP容器名} php /www/sites/{你的域名}/index/nurl -new预期输出如下:
正在生成 API 密钥...
━━━ 常驻密钥 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
su_# //Key 内容已隐去,下同
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
有效期:7 天。过期时间:2026-05-27T12:36:48+08:00
━━━ 一次性密钥(新增 20 个)━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
1. [su_前缀] su_#1
2. [su_前缀] su_#2
3. [su_前缀] su_#3
4. [su_前缀] su_#4
5. [su_前缀] su_#5
……
20. su_#20
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
请立即保存所有密钥,之后将不再显示。
每个密钥仅可使用一次,使用后立即失效。
系统不会自动补充,请使用 `docker exec {PHP容器名} php /www/sites/{你的域名}/index/nurl -full` 补充密钥池。
密钥永不过期,直到被使用。
密钥已存储至:/www/sites/{你的域名}/index/backend/data/keys.json
━━━ 内部通信令牌(LPA-Key)━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
5b7b8a45d3e0db1685...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
已写入:/www/sites/{你的域名}/index/backend/data/internal_token
此令牌用于 PHP↔Lua 内部通信认证,无需手动复制。内部通信令牌(LPA-Key)由 nurl -new 首次执行时自动生成,用于 PHP↔Lua 内部 API 认证。需要轮换时执行 docker exec {PHP容器名} php /www/sites/{你的域名}/index/nurl -itk,新令牌无需 reload 即可生效。
Key 明文仅在此时显示一次,立即保存。
六、设置文件权限
chown -R {PHP容器UID}:{共享GID} /{1panel安装路径}/www/sites/{你的域名}/index/backend/data
find /{1panel安装路径}/www/sites/{你的域名}/index/backend/data -type d -exec chmod 2770 {} \;
find /{1panel安装路径}/www/sites/{你的域名}/index/backend/data -type f -exec chmod 660 {} \;PHP-FPM 必须能写入该目录,OpenResty worker 必须能读取数据文件和 internal_token。不要使用 777/666。容器中的 UID/GID 可能与宿主机名称不同,请在 1Panel 容器配置中为两端加入同一个 {共享GID},然后重建或重启容器。
七、验证
curl -I https://{你的域名}curl https://{你的域名}/api/stat \
-H "X-Token: {su_你的Key}"curl -X POST https://{你的域名}/api/create \
-H "Content-Type: application/json" \
-H "X-Token: {su_你的Key}" \
-d '{"url":"https://{测试长链接}"}'curl -I https://{你的域名}/{短码}