SSD Nodes Learn Hosting plans →
Руководства Matt ConnorАвтор: Matt Connor

Как развернуть свой сервис сокращения ссылок Shlink

Установите Shlink на VPS через Docker Compose для управления короткими ссылками. В руководстве описана настройка DNS, базы данных Postgres, API ключей и веб-интерфейса.

Что вы создаёте

Собственный сервис сокращения ссылок — это небольшой сервер, который превращает длинную ссылку в короткую, принадлежащую вам, и подсчитывает количество переходов по ней. Shlink — оптимальный выбор: это open source решение, которое поставляется в виде Docker image и выполняет всю работу в одном контейнере вместе с базой данных. В этом руководстве мы развернём его на VPS с использованием собственного короткого домена, HTTPS, API key, QR-кодов и статистики переходов.

Система состоит из двух компонентов, что делает её похожей на коммерческие сервисы. API server отвечает за редиректы и хранение данных. Web client — это отдельное статическое приложение, которое взаимодействует с API через ваш браузер. Вы можете запустить оба компонента или использовать только API, управляя им через командную строку.

Версии, указанные здесь, были актуальны на октябрь 2026 года: Shlink 5.1 и shlink-web-client 4.8.

Сначала укажите короткий домен для сервера

Домен — это и есть продукт. s.example.com/abc123 — это ссылка, которую видят пользователи, поэтому выберите короткое имя до начала установки. Shlink сохраняет домен вместе с каждой короткой ссылкой, и изменение домена в будущем приведет к тому, что все ранее распространенные ссылки перестанут работать.

Создайте одну DNS-запись типа A для короткого домена, указав публичный IPv4-адрес вашего VPS. Добавьте также запись AAAA, если у сервера есть IPv6. Затем убедитесь, что домен разрешается, прежде чем продолжать.

dig +short s.example.com A

Выводом должен быть IP-адрес вашего сервера. Если вывод пуст, значит, запись еще не распространилась, и каждый последующий шаг завершится ошибкой, которую будет сложно диагностировать, так как TLS-сертификат (transport layer security) невозможно выпустить для имени, которое не разрешается.

Файл 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 открыла бы доступ к приложению даже на сервере с закрытым firewall. Привязка к адресу 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/health

Код 200 означает, что API работает и соединение с базой данных установлено. Код 503 означает, что Shlink запущен, но проверка базы данных не прошла. Health endpoint возвращает 503 при любой ошибке базы данных, но никогда не 500. Если curl выводит 000, значит, порт не отвечает: контейнер остановился во время проверок при запуске, и Docker пытается его перезапустить — изучите логи, указанные выше. В любом случае, частая причина заключается в том, что DB_PASSWORD в .env не совпадает с тем, с которым был создан Postgres, так как образ Postgres считывает POSTGRES_PASSWORD только при инициализации пустой директории данных. Изменение пароля позже не даст эффекта, пока вы не удалите volume и не запустите всё заново.

Терминация HTTPS перед приложением

Shlink работает по протоколу HTTP на порту 8080. TLS должен быть настроен на стороне reverse proxy, при этом критически важно передавать исходное имя хоста. Shlink определяет домен для короткой ссылки, считывая заголовок Host. Если прокси перезаписывает этот заголовок, Shlink возвращает ошибку 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://. Это приведет к лишнему перенаправлению, что увеличивает время отклика и некорректно отображается в веб-клиенте.

Создание 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, в котором 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 позволяет получить читаемую ссылку вместо сгенерированного кода. Слаги уникальны для каждого домена, поэтому повторная попытка создать ссылку с уже занятым слагом завершится ошибкой, а не перезапишет существующую ссылку. Команду --tag можно использовать повторно, а теги позволяют группировать ссылки, для которых вы захотите получить общую статистику в будущем.

Выведите список существующих ссылок, а затем просмотрите статистику трафика для одной из них.

sudo docker compose exec shlink shlink short-url:list
sudo docker compose exec shlink shlink short-url:visits docs

short-url:visits выводит по одной строке на каждый клик с указанием даты, реферера и user agent. Столбцы с данными о стране и городе останутся пустыми, если вы не зададите переменную окружения GEOLITE_LICENSE_KEY. Это бесплатный ключ MaxMind, который Shlink использует для загрузки базы данных GeoLite2. Без него посещения всё равно будут фиксироваться, но без определения географического положения.

Веб-клиент и QR-коды

Веб-клиент теперь доступен по адресу 127.0.0.1:8081. Для него требуется отдельная запись в прокси-сервере или SSH-туннель, если вы не хотите публиковать его в сети. При первом запуске клиент запрашивает URL сервера и API-ключ. Введите https://s.example.com и сгенерированный ранее ключ. Клиент сохраняет оба значения в хранилище браузера и обращается к API напрямую, поэтому данные не проходят через сторонние узлы. Разделение интерфейса и API — важный архитектурный паттерн; именно он позволяет Halcyon стилизовать библиотеку Jellyfin под видеопрокат 1990-х годов, не внося изменений в сам медиасервер.

QR-коды генерируются веб-клиентом, а не API. В Shlink 5 был удален старый метод добавления /qr-code к короткой ссылке, поэтому этот адрес больше не возвращает изображение. Теперь веб-клиент отрисовывает код непосредственно в браузере без запроса к Shlink. Откройте опцию QR-кода для любой ссылки в списке, настройте размер, формат и отступы, а затем скачайте результат. Увеличьте уровень коррекции ошибок, если код должен считываться при печати в малом размере или при частичном повреждении. В этом же диалоговом окне можно настроить цвета и добавить логотип.

Поддержание работоспособности

Сервис сокращения ссылок может выйти из строя незаметно. Ссылки перестают перенаправлять трафик, а вы об этом не узнаете, так как пользователь решит, что ссылка просто нерабочая. Настройте проверку работоспособности (uptime check) на реальную короткую ссылку, а не на домашнюю страницу, и настройте оповещения на любой ответ, отличный от редиректа. Экземпляр Uptime Kuma отлично справляется с этой задачей и позволяет отслеживать конкретные коды состояния.

Выполняйте резервное копирование базы данных, а не контейнера. Дамп создается одной командой.

sudo docker compose exec -T database pg_dump -U shlink shlink | gzip > shlink-$(date +%F).sql.gz

Этот файл вместе с вашим compose-файлом позволит развернуть сервис заново на новом сервере. Каждое приложение на сервере требует аналогичной пары файлов. Фотоархивы — особый случай, так как PhotoPrism и Immich хранят оригиналы файлов на диске, а не только записи в базе данных. Поэтому один лишь дамп восстановит учетные записи и настройки, но не сами фотографии. Обновление выполняется командами sudo docker compose pull и sudo docker compose up -d, после чего Shlink автоматически применяет новые миграции при запуске. Создавайте дамп перед обновлением образа, так как миграции невозможно откатить.

FAQ

Почему мои короткие ссылки возвращают 404 после добавления reverse proxy?

Shlink сопоставляет короткий код с доменом в заголовке Host. Если прокси передает свое имя или внутренний адрес, Shlink ищет код в домене, где нет ссылок, и возвращает 404. Установите proxy_set_header Host $host; в блоке location конфигурации nginx и перезагрузите прокси. Ссылки заработают сразу, перезапуск контейнера не требуется.

Нужен ли мне Postgres или достаточно SQLite?

SQLite подходит для тестирования Shlink и не требует отдельного контейнера. Переходите на Postgres до публикации важных ссылок, так как записи о посещениях растут с каждым кликом, а SQLite сериализует операции записи. Переход на другую БД позже потребует экспорта и импорта ссылок, поэтому выбор Postgres на старте избавит вас от этой миграции.

Можно ли восстановить забытый API key?

Нет. Shlink хранит только хеш ключа, поэтому api-key:list показывает имена и статусы, но не само значение. Создайте новый ключ с помощью shlink api-key:generate, вставьте его в веб-клиент, а затем отключите старый через shlink api-key:disable, чтобы он перестал работать.

Почему столбцы со странами пусты в статистике посещений?

Для геолокации требуется база данных GeoLite2, которую Shlink загружает только при наличии GEOLITE_LICENSE_KEY. Ключ можно бесплатно получить на сайте MaxMind. Добавьте его в секцию environment, пересоздайте контейнер, и новые посещения будут определяться. Посещения, записанные ранее, останутся без данных, пока вы не запустите shlink visit:locate.

Сохраните домен и перенесите данные. Сделайте дамп базы данных с помощью pg_dump, скопируйте дамп и файл compose на новый сервер, запустите стек, а затем восстановите дамп в пустую базу данных до того, как пойдет реальный трафик. Запись DNS меняйте в последнюю очередь. Короткие коды и история посещений сохранятся, так как все данные находятся в базе.

Была ли эта статья полезной?

#shlink#url-shortener#self-hosting#docker#postgres