آموزش راه اندازی سرویس کوتاه کننده لینک با 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 قوانین فورواردینگ خود را پیش از فایروال میزبان اعمال میکند، به این معنی که یک خط 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 فعال است و اتصال به پایگاه داده بهدرستی کار میکند. دریافت 500 در اینجا تقریباً همیشه به پایگاه داده مربوط میشود: مقدار 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 داخل container تولید کنید.
sudo docker compose exec shlink shlink api-key:generate --name "web client"docker exec -it <container_name> ./manage.py generate-key --name <key_name>
این دستور کلید را فقط یک بار نمایش میدهد. آن را همین حالا کپی کنید، زیرا کلید بهصورت هششده ذخیره میشود و دیگر قابل مشاهده نخواهد بود. دستور 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-urlscurl -H "Authorization: Bearer <your_key>" https://api.example.com/status
یک شیء JSON با کلید shortUrls به این معنی است که کلید بهدرستی کار میکند. دریافت کد 401 به همراه 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 به هیچ پیکربندی نیاز ندارند. کافی است /qr-code را به هر URL کوتاهی اضافه کنید تا API تصویر مربوطه را بازگرداند.
https://s.example.com/docs/qr-code?size=500&format=svg&margin=20size عرض تصویر به پیکسل است و مقداری بین 50 تا 1000 را میپذیرد که مقدار پیشفرض آن 300 است. format میتواند png یا svg باشد. margin فضای خالی (quiet space) اطراف کد به پیکسل است و اندازه نهایی تصویر برابر با اندازه کد به اضافه دو برابر حاشیه خواهد بود. برای کدی که هنگام چاپ در ابعاد کوچک یا در صورت پوشانده شدن بخشی از آن همچنان قابل اسکن باشد، از errorCorrection=Q استفاده کنید.
حفظ پایداری سرویس
یک سرویس کوتاه کننده لینک ممکن است بدون هیچ هشداری از کار بیفتد. در این حالت، لینکها دیگر تغییر مسیر نمیدهند و کسی به شما اطلاع نمیدهد، زیرا کاربری که روی لینک کلیک کرده تصور میکند لینک خراب است. یک بررسی uptime را روی یک لینک کوتاه واقعی تنظیم کنید، نه صفحه اصلی؛ و برای هر پاسخی که یک redirect نباشد، هشدار دریافت کنید. یک نمونه Uptime Kuma که خودتان میزبانی میکنید این کار را بهخوبی انجام میدهد و میتواند وضعیت یک کد خاص را پایش کند.
از دیتابیس نسخه پشتیبان تهیه کنید، نه از container. یک دستور ساده برای 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 را در آخرین مرحله تغییر دهید. کدهای کوتاه و تاریخچه بازدید آنها حفظ میشوند، زیرا همه چیز در دیتابیس قرار دارد.