最安全的方式自托管短链:Shlink + Docker + Cloudflare Tunnel
在一台 2C2G 云服务器上用 Docker 跑 Shlink,通过 Cloudflare Tunnel 只暴露短链跳转,管理走官方 app.shlink.io,实现最小攻击面。
On this page本页目录

我以前一直用 Bitly。免费的,够用,也没多想。直到有一天我发现,我所有的分享链接,每一次点击,每一台设备的位置,全在他们的数据库里躺着。我一分钱没花,但代价是我的数据归他们了。
更难受的是,这些链接不是我的。Bitly 哪天关门,或者改了政策,我几年积累的短链全废。
所以我开始找自托管方案。
为什么是 Shlink
市面上的开源短链工具不少,我主要看了两个:
Dub.co,GitHub 上 21k star,确实漂亮,功能也猛。但它太重了。PlanetScale MySQL + Upstash Redis + Tinybird ClickHouse + Cloudflare R2 + GitHub OAuth,五六个外部依赖。我就是想缩个 URL,不是想搭一个数据分析平台。
Shlink 不一样。PHP 单体应用,一个 Docker 容器加一个数据库就跑起来了。API-first 设计,自带点击统计、地理定位、自定义短码、多域名支持、标签管理。MIT 协议,代码在 GitHub 上。
关键是轻。2C2G 的小服务器跑起来绰绰有余。

安全思路:最小暴露
大部分 Shlink 教程的做法是这样的:把 Shlink 的 API 和管理面板一起暴露到公网,然后加个密码保护。
这样做的问题在于,你的管理面板和 API 端点都暴露在互联网上了。密码泄露、零日漏洞、暴力破解,任何一个环节出问题,你的短链服务就被别人控制了。
我的做法不一样。
核心原则:只暴露必须暴露的东西。
一个短链服务对公众只需要做一件事:当有人访问 go.wyt.icu/xxx 时,把它 301 跳转到对应的长链接。就这么简单。不需要让任何人访问你的管理界面。
所以我的架构是这样的:
- Cloudflare Tunnel 只代理短链跳转的 80 端口,其他什么都不开
- 管理面板不部署,不暴露,连容器都不起
- 管理操作走官方 app.shlink.io,通过 API key 连接服务器
- API 端点也只有 Tunnel 可以触达,加了 IP 白名单
这样即使有人扫到了我的服务器,他也只能看到一个跳转服务。没有登录页,没有管理面板,没有可以攻击的入口。
部署步骤
1. 准备工作
你需要:
- 一个短域名(我用的是
go.wyt.icu) - 一台云服务器(2C2G 足够)
- Docker 和 Docker Compose
- Cloudflare 账号(管理你的域名 DNS)
- 服务器上装好
cloudflared
2. Cloudflare Tunnel 配置
在 Cloudflare Zero Trust 面板创建一个 Tunnel,拿到 Token。然后在服务器的 cloudflared 配置里,只把一个域名指向 Shlink 容器:
# ~/.cloudflared/config.yml
tunnel: your-tunnel-id
credentials-file: /root/.cloudflared/your-tunnel-id.json
ingress:
- hostname: go.wyt.icu
service: http://localhost:8080
- service: http_status:404
注意这里只代理了 go.wyt.icu 到本地的 8080 端口。没有把 API 路径单独开出来,也没有暴露管理面板。
在 Cloudflare DNS 里把 go.wyt.icu 的 CNAME 指向你的 Tunnel 就行。
3. Docker Compose
# docker-compose.yml
version: "3.8"
services:
shlink:
image: shlinkio/shlink:stable
container_name: shlink
restart: unless-stopped
ports:
- "8080:8080"
environment:
- DEFAULT_DOMAIN=go.wyt.icu
- IS_HTTPS_ENABLED=true
- DB_DRIVER=postgres
- DB_HOST=shlink-db
- DB_PORT=5432
- DB_NAME=shlink
- DB_USER=shlink
- DB_PASSWORD=换成你的密码
- GEOLITE_LICENSE_KEY=你的key # 可选,地理定位用
depends_on:
- shlink-db
volumes:
- ./data:/etc/shlink/data
shlink-db:
image: postgres:16-alpine
container_name: shlink-db
restart: unless-stopped
environment:
- POSTGRES_USER=shlink
- POSTGRES_PASSWORD=换成你的密码
- POSTGRES_DB=shlink
volumes:
- ./db-data:/var/lib/postgresql/data
用 Postgres 是个人偏好,Shlink 也支持 MySQL/MariaDB 和 SQLite。如果更想省资源,SQLite 也行,2C2G 甚至可以降到 1C1G。
然后:
docker compose up -d
看一眼日志确认启动成功:
docker logs shlink
4. 生成 API Key
Shlink 启动后,用 CLI 生成一个 API key:
docker exec -it shlink shlink api-key:generate
记下这个 key,后面要用它连接管理面板。
管理走官方 PWA
这是我觉得最优雅的地方。
Shlink 官方提供了一个 Web 客户端,托管在 app.shlink.io。它是一个纯前端的 PWA,所有操作都通过 API 调用你的服务器。

你打开 app.shlink.io,填入你的服务器地址(https://go.wyt.icu)和 API key,就能管理所有短链了。
这样做的好处:
- 管理面板完全不部署在你的服务器上,零攻击面
- 官方维护,自动更新,你不用管前端版本
- 数据始终在你的服务器上,app.shlink.io 只是一个操作界面,不存储任何数据
- 可以连接多个 Shlink 实例,如果你以后跑了多个短链服务
当然,如果你特别介意 API key 经过第三方域名,你也可以自己部署 shlinkio/shlink-web-client 镜像,但那样就又多了一个需要保护的入口。对我来说,官方 PWA 够了。
日常使用
创建短链,直接在 app.shlink.io 的界面操作,或者走 API:
curl -X POST https://go.wyt.icu/rest/v3/short-urls \
-H "X-Api-Key: your-api-key" \
-H "Content-Type: application/json" \
-d '{"longUrl": "https://jask.dev"}'
返回:
{
"shortCode": "0wPLj",
"shortUrl": "https://go.wyt.icu/0wPLj",
"longUrl": "https://jask.dev"
}
点击统计、访问来源、设备类型、地理位置,全都有。数据在你自己的 Postgres 里,不经过任何第三方。
成本
- 服务器:2C2G VPS,一年几十块
- 域名:
wyt.icu已经有了,加个go子域不花钱 - Cloudflare Tunnel:免费
- Shlink:开源免费
总计:接近零。
最后
我的短链已经跑起来了:go.wyt.icu/0wPLj,指向我的个人主页 jask.dev。
整个架构的思路其实就一句话:最小暴露面。服务器上只开一个跳转端口,管理面板不部署,API 不公开。你扫到的只是一个安静的 301 服务,别的什么都看不到。
如果你也在找自托管短链方案,Shlink + Docker + Cloudflare Tunnel 这套组合是我目前能想到的最干净的方式。
如果你还不了解 Cloudflare Tunnel,可以先看我的另一篇:Cloudflare 从入门到入土,全教给你。