Власний скорочувач URL на VPS із Shlink і Docker
Налаштуйте Shlink 5.1 на VPS: короткий домен із HTTPS, Postgres, API-ключі, веб-клієнт 4.8, QR-коди та статистика натискань у Docker Compose.
Що ви створюєте
Власний сервіс скорочення URL — це невеликий сервер, який перетворює довге посилання на коротке посилання під вашим контролем і підраховує кожне натискання. Для цього підходить Shlink: це програмне забезпечення з відкритим кодом, яке постачається як образ Docker і виконує всю роботу в одному контейнері разом із базою даних. У цьому посібнику його налаштовано на VPS за справжнім коротким доменом із HTTPS, ключем API, QR-кодами та статистикою натискань.
Два компоненти роблять сервіс подібним до комерційного сервісу скорочення посилань. Сервер API обробляє перенаправлення та зберігає дані. Веб-клієнт — це окремий статичний застосунок, який звертається до цього API з браузера. Можна запускати обидва компоненти або лише API і керувати ним з командного рядка.
Наведені тут номери версій були актуальними станом на July 2026: Shlink 5.1 і shlink-web-client 4.8.
Спочатку вкажіть короткий домен для сервера
Домен — це основа сервісу. s.example.com/abc123 — це посилання, яке бачать користувачі, тому виберіть короткий варіант до встановлення будь-яких компонентів. Shlink зберігає домен у кожній короткій URL-адресі. Якщо змінити його пізніше, усі вже поширені посилання перестануть працювати.
Створіть один DNS-запис A для короткого домену та вкажіть у ньому публічну IPv4-адресу вашого VPS. Також додайте запис AAAA, якщо сервер має IPv6. Потім переконайтеся, що домен правильно розпізнається, перш ніж продовжувати.
dig +short s.example.com AУ виведенні має бути адреса вашого сервера. Якщо виведення порожнє, запис ще не поширився в DNS. У такому разі всі наступні кроки завершаться помилками, які складно пояснити, оскільки сертифікат TLS (безпека транспортного рівня) неможливо видати для імені, яке не розпізнається.
Файл 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, тому з інтернету до них неможливо підключитися, доки в наступному розділі не буде налаштовано зворотний проксі. 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Під час першого запуску виконуються міграції бази даних, тому він триває довше, ніж наступні запуски. Після завершення перевірте, що служба відповідає локально.
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8080/rest/health200 означає, що API працює, а підключення до бази даних справне. 500 тут майже завжди вказує на проблему з базою даних: DB_PASSWORD у .env не відповідає значенню, з яким було створено Postgres, оскільки образ Postgres читає POSTGRES_PASSWORD лише під час ініціалізації порожнього каталогу даних. Зміна пароля пізніше не допоможе, доки ви не видалите том і не запустите контейнер знову.
Завершення HTTPS перед Shlink
Shlink обслуговує звичайний HTTP на порту 8080. TLS потрібно налаштувати у зворотному проксі. Важливо передавати початкове ім’я хоста. 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.comIS_HTTPS_ENABLED: "true" у файлі compose визначає, що Shlink додає https:// до коротких URL, які повертає. Сам по собі цей параметр не вмикає TLS. Залиште його значенням false за HTTPS-проксі. Тоді кожне посилання, яке повертає API, буде посиланням http://, після чого воно перенаправлятиметься. Це створює додатковий мережевий обмін і має неправильний вигляд у вебклієнті.
Створення ключа API
Без ключа жоден компонент не може звертатися до 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 зі значенням 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 створює зрозуміле посилання замість згенерованого коду. Slug є унікальним для кожного домену, тому повторна спроба використати зайнятий slug завершується помилкою, а не непомітно перезаписує перше посилання. --tag можна повторювати. Теги дають змогу групувати посилання, для яких пізніше потрібно отримувати спільну статистику.
Спочатку перегляньте наявні посилання, а потім перевірте трафік одного з них.
sudo docker compose exec shlink shlink short-url:list
sudo docker compose exec shlink shlink short-url:visits docsshort-url:visits виводить один рядок для кожного переходу із зазначенням дати, джерела переходу та user agent. Стовпці країни й міста залишаються порожніми, якщо не задано змінну середовища GEOLITE_LICENSE_KEY. Це безкоштовний ключ MaxMind, який Shlink використовує для завантаження бази даних GeoLite2. Без цього ключа відвідування все одно записуються, але їхнє географічне розташування не визначається.
Вебклієнт і QR-коди
Вебклієнт доступний за адресою 127.0.0.1:8081. Для нього потрібен окремий запис проксі або тунель SSH, якщо ви не хочете публікувати його. Під час першого завантаження він запитує URL-адресу сервера та ключ API. Укажіть https://s.example.com і згенерований ключ. Клієнт зберігає обидва значення у сховищі браузера та безпосередньо звертається до вашого API, тому дані не проходять через сторонні сервіси.
Для 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 — це вільний простір навколо коду в пікселях. Розмір готового зображення дорівнює розміру коду плюс подвоєне значення поля. Додайте errorCorrection=Q, щоб код можна було розпізнати навіть у разі малого розміру під час друку або часткового перекриття.
Підтримуйте роботу сервісу
Сервіс скорочення URL може непомітно припинити працювати. Посилання перестають перенаправляти, і ніхто не повідомляє про проблему, оскільки користувач, який натиснув посилання, вважає його непрацюючим. Налаштуйте перевірку доступності для справжньої скороченої URL-адреси, а не для головної сторінки, і налаштуйте сповіщення про будь-яку відповідь, яка не є перенаправленням. Самостійно розгорнутий екземпляр Uptime Kuma добре підходить для цього та може перевіряти конкретний код стану.
Створюйте резервну копію бази даних, а не контейнера. Для цього достатньо однієї команди.
sudo docker compose exec -T database pg_dump -U shlink shlink | gzip > shlink-$(date +%F).sql.gzЦей файл разом із файлом compose дає змогу відновити весь сервіс на новому сервері. Оновлення виконуються за допомогою sudo docker compose pull, а потім sudo docker compose up -d, і під час запуску Shlink виконує всі нові міграції. Створюйте дамп перед виконанням pull, оскільки міграцію неможливо відкочувати.
FAQ
Чому мої короткі посилання повертають 404 після додавання reverse proxy?
Shlink зіставляє короткий код із доменом у заголовку Host. Якщо proxy передає власне ім’я або внутрішню адресу, Shlink шукає цей код у домені, для якого немає посилань, і повертає 404. Установіть proxy_set_header Host $host; у блоці location для nginx і перезавантажте proxy. Посилання одразу запрацюють, без перезапуску контейнера.
Чи потрібен Postgres, чи достатньо SQLite?
SQLite підходить для тестування Shlink і не потребує другого контейнера. Перейдіть на Postgres до публікації важливих посилань, оскільки кількість записів про відвідування зростає з кожним переходом, а SQLite серіалізує операції запису. Подальший перехід потребує експорту та повторного імпорту посилань, тому вибір Postgres на початку заощадить цю міграцію.
Чи можна відновити API key, який я забув скопіювати?
Ні. Shlink зберігає хеш ключа, тому api-key:list показує назви та стан, але не саме значення. Створіть заміну за допомогою shlink api-key:generate, вставте її у web client, а потім вимкніть старий ключ за допомогою shlink api-key:disable, щоб він більше не працював.
Чому стовпці з даними про країни порожні у статистиці відвідувань?
Для геолокації потрібна база даних GeoLite2. Shlink завантажує її лише після отримання GEOLITE_LICENSE_KEY. Ключ можна безкоштовно отримати в MaxMind. Додайте його до секції змінних середовища, пересоздайте контейнер, і нові відвідування отримуватимуть дані про місцезнаходження. Для відвідувань, записаних раніше, ці поля залишаться порожніми, доки ви не запустите shlink visit:locate.
Як перенести Shlink на інший сервер?
Збережіть домен і перенесіть дані. Створіть дамп бази даних за допомогою pg_dump, скопіюйте дамп і compose file на новий сервер, запустіть stack, а потім відновіть дамп у порожню базу даних до надходження реального трафіку. Запис DNS змініть в останню чергу. Короткі коди та історія їхніх відвідувань збережуться, оскільки всі дані містяться в базі даних.