آموزش راه اندازی سرویس کوتاه کننده لینک با Shlink
با استفاده از Shlink و Docker Compose یک سرویس کوتاه کننده لینک شخصی روی VPS بسازید. این راهنما شامل تنظیمات DNS، پایگاه داده Postgres، کلید API و تحلیل آمار کلیکها است.
آنچه میسازید
یک سرویس کوتاهکننده لینک (URL shortener) شخصی، سرور کوچکی است که لینکهای طولانی را به لینکهای کوتاهی که متعلق به خودتان است تبدیل کرده و تمام کلیکها را میشمارد. Shlink بهترین گزینه است: متنباز است، به صورت Docker image عرضه میشود و کل کار را در یک کانتینر به همراه یک دیتابیس انجام میدهد. این راهنما آن را روی یک VPS و پشت یک دامنه کوتاه واقعی، با HTTPS، کلید API، کدهای QR و آمار کلیکها راهاندازی میکند.
دو بخش باعث میشود که این سرویس مانند یک کوتاهکننده لینک تجاری عمل کند. سرور API به تغییر مسیرها (redirects) پاسخ میدهد و دادهها را نگهداری میکند. کلاینت وب یک برنامه استاتیک مجزا است که از طریق مرورگر شما با آن API ارتباط برقرار میکند. میتوانید هر دو را اجرا کنید، یا فقط API را اجرا کرده و آن را از طریق خط فرمان مدیریت کنید.
شماره نسخههای ذکر شده در اینجا، نسخههای جاری در اکتبر 2026 هستند: Shlink 5.1 و shlink-web-client 4.8.
ابتدا یک دامنه کوتاه را به سرور متصل کنید
دامنه همان محصول شماست. s.example.com/abc123 لینکی است که کاربران مشاهده میکنند، بنابراین یک نام کوتاه انتخاب کنید و پیش از نصب هر چیزی، آن را ثبت نمایید. Shlink دامنه را با هر URL کوتاه ذخیره میکند و تغییر آن در آینده باعث میشود تمام لینکهایی که قبلاً توزیع کردهاید از کار بیفتند.
یک رکورد DNS A برای دامنه کوتاه خود ایجاد کنید که به آدرس IPv4 عمومی VPS شما اشاره کند. اگر سرور شما از IPv6 پشتیبانی میکند، یک رکورد AAAA نیز اضافه کنید. سپس پیش از ادامه، اطمینان حاصل کنید که دامنه به درستی resolve میشود.
dig +short s.example.com Aخروجی باید آدرس سرور شما باشد. اگر خروجی خالی است، یعنی رکورد هنوز منتشر نشده است. در این صورت، تمام مراحل بعدی با خطاهای مبهم مواجه خواهند شد، زیرا صدور گواهی TLS (امنیت لایه انتقال) برای نامی که resolve نمیشود، امکانپذیر نیست.
فایل compose
Shlink به یک دیتابیس نیاز دارد. SQLite برای تست مناسب است، اما Postgres انتخاب درست برای هر چیزی است که قصد نگهداری آن را دارید، زیرا ردیفهای بازدید انباشته میشوند و Postgres ایندکسها و نوشتنهای همزمان را بهتر مدیریت میکند. این را در /opt/shlink/compose.yaml قرار دهید.
services:
shlink:
image: shlinkio/shlink:stable
restart: unless-stopped
ports:
- "127.0.0.1:8080:8080"
environment:
DEFAULT_DOMAIN: s.example.com
IS_HTTPS_ENABLED: "true"
DB_DRIVER: postgres
DB_HOST: database
DB_NAME: shlink
DB_USER: shlink
DB_PASSWORD: ${DB_PASSWORD}
depends_on:
- database
database:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: shlink
POSTGRES_USER: shlink
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- shlink_db:/var/lib/postgresql/data
web-client:
image: shlinkio/shlink-web-client:stable
restart: unless-stopped
ports:
- "127.0.0.1:8081:8080"
volumes:
shlink_db:هر دو پورت منتشر شده به 127.0.0.1 متصل میشوند، بنابراین تا زمانی که reverse proxy در بخش بعدی قرار نگیرد، هیچچیز از طریق اینترنت قابل دسترسی نیست. Docker قوانین هدایت (forwarding) خود را پیش از فایروال میزبان مینویسد، که به این معنی است که یک خط 8080:8080 ساده، برنامه را حتی روی سیستمی که فایروال آن بسته به نظر میرسد، در معرض دید قرار میدهد. اتصال به آدرس loopback از این امر جلوگیری میکند. همین الگو برای هر برنامهای که به این روش اجرا میکنید صدق میکند و جزئیات بیشتر آن در راهنمای Docker Compose روی VPS پوشش داده شده است.
رمز عبور دیتابیس از یک فایل .env در کنار فایل compose خوانده میشود، بنابراین هرگز در فایل YAML قرار نمیگیرد.
sudo mkdir -p /opt/shlink
printf 'DB_PASSWORD=%s\n' "$(openssl rand -base64 24)" | sudo tee /opt/shlink/.env
sudo chmod 600 /opt/shlink/.envآن را اجرا کنید و بالا آمدن API را مشاهده کنید.
cd /opt/shlink
sudo docker compose up -d
sudo docker compose logs -f shlinkاولین اجرا، مهاجرتهای دیتابیس (migrations) را انجام میدهد، بنابراین طولانیتر از دفعات بعدی است. وقتی وضعیت پایدار شد، بررسی کنید که سرویس به صورت محلی پاسخ میدهد یا خیر.
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/rest/healthیک 200 به این معنی است که API زنده است و اتصال دیتابیس کار میکند. یک 503 به این معنی است که Shlink در حال اجراست اما بررسی دیتابیس آن با شکست مواجه شده است. endpoint سلامت برای هر خطای دیتابیس 503 پاسخ میدهد و هرگز 500 برنمیگرداند. اگر curl عبارت 000 را چاپ کرد، یعنی هیچچیز روی پورت پاسخ نمیدهد: کانتینر در حین بررسیهای راهاندازی متوقف شده و Docker در حال راهاندازی مجدد آن است، پس لاگهای بالا را بخوانید. در هر صورت، علت معمول این است که DB_PASSWORD در .env با آنچه Postgres با آن ایجاد شده مطابقت ندارد، زیرا ایمیج Postgres فقط زمانی که یک دایرکتوری داده خالی را مقداردهی اولیه میکند، POSTGRES_PASSWORD را میخواند. ویرایش رمز عبور در مراحل بعدی تا زمانی که volume را حذف نکنید و دوباره شروع نکنید، هیچ اثری نخواهد داشت.
پایاندهی HTTPS در مقابل آن
سرویس Shlink از پروتکل HTTP ساده روی پورت 8080 استفاده میکند. مدیریت TLS بر عهده reverse proxy است و تنها تنظیم مهم در اینجا، انتقال نام دامنه اصلی (original host name) است. سرویس Shlink بر اساس خواندن هدر Host تصمیم میگیرد که هر کد کوتاه متعلق به کدام دامنه است؛ بنابراین، پروکسی که این هدر را بازنویسی کند، باعث میشود لینکهای موجود با خطای 404 مواجه شوند و آمار بازدیدها نیز به دامنه اشتباه اختصاص یابد.
server {
server_name s.example.com;
listen 80;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}سپس گواهی را صادر کنید. راهنمای کامل، شامل زمانبندی تمدید خودکار، در راهنمای Certbot برای nginx روی Ubuntu 24.04 موجود است.
sudo certbot --nginx -d s.example.comتنظیم IS_HTTPS_ENABLED: "true" در فایل compose همان چیزی است که باعث میشود Shlink در آدرسهای کوتاهی که بازمیگرداند، از https:// استفاده کند. این تنظیم بهتنهایی TLS را فعال نمیکند. اگر آن را false پشت یک پروکسی HTTPS رها کنید، هر لینکی که API برمیگرداند یک لینک http:// خواهد بود که سپس تغییر مسیر (redirect) میدهد؛ این کار باعث ایجاد یک رفتوبرگشت اضافی (round trip) شده و در رابط کاربری وب نیز نادرست به نظر میرسد.
ایجاد یک API key
هیچ ارتباطی با API بدون داشتن یک کلید ممکن نیست. یک کلید را از طریق CLI داخل کانتینر تولید کنید.
sudo docker compose exec shlink shlink api-key:generate --name "web client"این دستور کلید را فقط یکبار نمایش میدهد. آن را همین حالا کپی کنید، زیرا کلید بهصورت هششده ذخیره میشود و دیگر قابل مشاهده نخواهد بود. shlink api-key:list نام کلیدها و وضعیت فعال یا غیرفعال بودن هرکدام را نشان میدهد، اما هرگز خود کلید را نمایش نمیدهد. برای ابطال یک کلید از shlink api-key:disable به همراه نام آن استفاده کنید.
هر فراخوانی REST باید کلید را در هدر X-Api-Key حمل کند.
curl -H "X-Api-Key: YOUR_KEY" https://s.example.com/rest/v3/short-urlsیک شیء JSON با کلید shortUrls به معنای صحت عملکرد کلید است. یک 401 که type آن https://shlink.io/api/error/invalid-api-key باشد و عنوان Invalid API key را داشته باشد، نشاندهنده اشتباه بودن کلید، غیرفعال بودن یا منقضی شدن تاریخ اعتبار آن است.
ایجاد لینکهای کوتاه از طریق خط فرمان
رابط خط فرمان (CLI) سریعترین روش برای ساخت لینک است و بهخوبی در اسکریپتها عمل میکند.
sudo docker compose exec shlink shlink short-url:create https://example.com/a/very/long/path
sudo docker compose exec shlink shlink short-url:create https://example.com/docs --custom-slug docs --tag referenceاستفاده از --custom-slug به شما یک لینک خوانا بهجای کدهای تصادفی میدهد. اسلاگها (slugs) برای هر دامنه منحصربهفرد هستند؛ بنابراین اگر تلاش کنید از اسلاگی استفاده کنید که قبلاً ثبت شده است، عملیات با خطا مواجه میشود و لینک قبلی بهطور ناخواسته بازنویسی نخواهد شد. دستور --tag را میتوان تکرار کرد و تگها روشی برای دستهبندی لینکهایی هستند که میخواهید بعداً آمار ترکیبی آنها را مشاهده کنید.
ابتدا لیست لینکهای موجود را مشاهده کنید و سپس ترافیک یک لینک خاص را بررسی نمایید.
sudo docker compose exec shlink shlink short-url:list
sudo docker compose exec shlink shlink short-url:visits docsدستور short-url:visits برای هر کلیک یک ردیف شامل تاریخ، ارجاعدهنده (referrer) و عامل کاربر (user agent) چاپ میکند. ستونهای کشور و شهر خالی میمانند مگر اینکه متغیر محیطی GEOLITE_LICENSE_KEY را تنظیم کنید؛ این متغیر یک کلید رایگان MaxMind است که Shlink از آن برای دانلود پایگاه داده GeoLite2 استفاده میکند. بدون این کلید، بازدیدها همچنان ثبت میشوند، اما موقعیت جغرافیایی آنها مشخص نخواهد شد.
کلاینت وب و کدهای QR
کلاینت وب اکنون روی 127.0.0.1:8081 در دسترس است و به یک ورودی proxy اختصاصی نیاز دارد، یا اگر ترجیح میدهید آن را منتشر نکنید، میتوانید از یک SSH tunnel استفاده کنید. در اولین بارگذاری، کلاینت از شما یک URL سرور و یک API key میخواهد. https://s.example.com و کلیدی که تولید کردهاید را وارد کنید. کلاینت هر دو مورد را در حافظه مرورگر نگه میدارد و مستقیماً با API شما تماس میگیرد، بنابراین هیچ دادهای از طریق شخص ثالث منتقل نمیشود. جدا کردن رابط کاربری از API الگویی است که ارزش توجه دارد، زیرا همان الگویی است که به Halcyon اجازه میدهد کتابخانه Jellyfin را به شکل یک فروشگاه کرایه فیلم دهه 1990 درآورد بدون اینکه تغییری در مدیا سرور پشت آن ایجاد کند.
کدهای QR از کلاینت وب تولید میشوند، نه از API. در Shlink 5 ترفند قدیمیِ اضافه کردن /qr-code به انتهای یک URL کوتاه حذف شده است، بنابراین آن آدرس دیگر تصویری برنمیگرداند. در عوض، کلاینت وب کد را در مرورگر شما ترسیم میکند، بدون اینکه درخواستی به Shlink ارسال شود. گزینه QR code را برای هر لینکی در لیست URLهای کوتاه باز کنید، اندازه، فرمت و حاشیه را تنظیم کرده و نتیجه را دانلود کنید. برای کدی که قرار است در ابعاد کوچک چاپ شود یا بخشی از آن پوشانده شود، سطح تصحیح خطا (error correction level) را افزایش دهید. همان پنجره تنظیمات، امکان تغییر رنگها و افزودن لوگو را نیز فراهم میکند.
پایدار نگه داشتن سرویس
یک سرویس کوتاه کننده لینک در سکوت از کار میافتد. لینکها دیگر تغییر مسیر (redirect) نمیدهند و کسی به شما اطلاع نمیدهد، زیرا کاربری که روی لینک کلیک کرده تصور میکند لینک خراب است. یک بررسی uptime را به جای صفحه اصلی، روی یک URL کوتاه واقعی تنظیم کنید و برای هر چیزی که redirect نباشد، هشدار دریافت کنید. یک نمونه Uptime Kuma که خودتان میزبانی میکنید این کار را به خوبی انجام میدهد و میتواند وضعیت یک کد خاص را پایش کند.
از دیتابیس بکآپ بگیرید، نه از کانتینر. یک دستور آن را dump میکند.
sudo docker compose exec -T database pg_dump -U shlink shlink | gzip > shlink-$(date +%F).sql.gzآن فایل به همراه فایل compose، کل سرویس را روی یک سرور جدید بازسازی میکند. هر برنامهای روی سرور به نسخه مخصوص خود از این جفت فایل نیاز دارد؛ کتابخانههای عکس مورد خاصی هستند، زیرا PhotoPrism و Immich هر دو فایلهای اصلی را روی دیسک نگه میدارند و همزمان ردیفهایی در دیتابیس دارند، بنابراین یک dump به تنهایی حسابها و تنظیمات را برمیگرداند اما حتی یک عکس را هم بازیابی نمیکند. ارتقاها با sudo docker compose pull و به دنبال آن sudo docker compose up -d انجام میشوند و Shlink هر migration جدیدی را هنگام شروع اجرا میکند. پیش از pull کردن، dump بگیرید، زیرا migrationها قابل بازگشت (rollback) نیستند.
FAQ
چرا لینکهای کوتاه من پس از افزودن reverse proxy خطای 404 میدهند؟
نرمافزار Shlink کد کوتاه را با دامنه موجود در هدر Host تطبیق میدهد. اگر پروکسی نام خود یا یک آدرس داخلی را ارسال کند، Shlink به دنبال آن کد در دامنهای میگردد که هیچ لینکی ندارد و در نتیجه خطای 404 برمیگرداند. مقدار proxy_set_header Host $host; را در بلاک location مربوط به nginx تنظیم کرده و پروکسی را reload کنید. لینکها بلافاصله و بدون نیاز به restart کردن container شروع به کار میکنند.
آیا به Postgres نیاز دارم یا SQLite کافی است؟
SQLite برای تست کردن Shlink مناسب است و نیازی به container دوم ندارد. پیش از انتشار لینکهای مهم، به Postgres مهاجرت کنید؛ زیرا با هر کلیک، ردیفهای بازدید افزایش مییابند و SQLite عملیات نوشتن را بهصورت سریالی انجام میدهد. تغییر دیتابیس در آینده مستلزم export و import کردن لینکهاست، بنابراین انتخاب Postgres از همان ابتدا شما را از این مهاجرت بینیاز میکند.
آیا میتوانم API key که فراموش کردهام کپی کنم را بازیابی کنم؟
خیر. Shlink هشِ کلید را ذخیره میکند، بنابراین api-key:list نامها و وضعیتها را نشان میدهد اما هرگز مقدار کلید را نمایش نمیدهد. یک کلید جایگزین با shlink api-key:generate ایجاد کنید، آن را در کلاینت وب وارد کنید و سپس کلید قدیمی را با shlink api-key:disable غیرفعال کنید تا از کار بیفتد.
چرا ستونهای کشور در آمار بازدید من خالی هستند؟
موقعیتیابی جغرافیایی به دیتابیس GeoLite2 نیاز دارد که Shlink تنها در صورت ارائه GEOLITE_LICENSE_KEY آن را دانلود میکند. این کلید از طریق MaxMind بهصورت رایگان قابل دریافت است. آن را به بخش environment اضافه کنید، container را recreate کنید تا بازدیدهای جدید موقعیتیابی شوند. بازدیدهایی که پیش از این ثبت شدهاند تا زمانی که shlink visit:locate را اجرا نکنید، خالی باقی میمانند.
چگونه Shlink را به سرور دیگری منتقل کنم؟
دامنه را حفظ کرده و دادهها را منتقل کنید. دیتابیس را با pg_dump dump بگیرید، فایل dump و فایل compose را به سرور جدید کپی کنید، stack را اجرا کنید و سپس پیش از رسیدن ترافیک واقعی، dump را در دیتابیس خالی restore کنید. رکورد DNS را در آخرین مرحله تغییر دهید. کدهای کوتاه و تاریخچه بازدید آنها حفظ میشوند، زیرا همه چیز در دیتابیس قرار دارد.