MAATRIX / Блог / Shlink на сервере: частые ошибки и решения

Shlink на сервере: частые ошибки и решения

MAATRIX

Shlink — самостоятельно размещаемый URL-shortener с REST API и подробной аналитикой переходов, и большинство проблем с ним сводится к десятку типовых ситуаций: не проходит подключение к базе, короткие ссылки редиректят по http вместо https, статистика по геолокации не работает, а Web Client отвечает 401 или молчит из-за CORS. Ниже — конкретные причины и решения для каждой, без лишней теории.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Ошибка подключения к базе данных

При старте контейнера Shlink пишет в лог что-то вроде SQLSTATE[HY000] [2002] Connection refused или Access denied for user, и приложение не поднимается. Это почти всегда рассинхронизация между переменными окружения Shlink и реальными параметрами базы. Проверьте набор переменных в docker-compose.yml:

environment:
  DB_DRIVER: mysql
  DB_NAME: shlink
  DB_USER: shlink
  DB_PASSWORD: пароль
  DB_HOST: shlink_db
  DB_PORT: 3306

Частые причины отказа:

  • DB_HOST указывает не туда. В Docker Compose это должно быть имя сервиса базы данных из того же compose-файла (shlink_db), а не localhost и не внешний IP — контейнер Shlink обращается к базе по внутренней сети Docker.
  • База ещё не готова, когда стартует Shlink. MySQL и PostgreSQL при первом запуске какое-то время инициализируют том с данными, и если Shlink стартует раньше, соединение будет отклонено. Добавьте depends_on с условием service_healthy и healthcheck на контейнер базы, а не просто depends_on: [shlink_db] — простой depends_on ждёт запуска процесса, а не готовности принимать соединения.
  • Пароль или имя пользователя не совпадают с тем, что реально создано в базе при её собственной инициализации (переменные MYSQL_PASSWORD/POSTGRES_PASSWORD контейнера базы).

Если Shlink стартовал, но при первом обращении к API или Web Client сыплются ошибки про отсутствующие таблицы — миграции не применились. В официальном образе shlinkio/shlink миграции накатываются автоматически при старте контейнера, но при обновлении с очень старой версии или ручной установке без Docker их нужно прогнать вручную:

php vendor/bin/doctrine-migrations migrations:migrate --no-interaction

Перед миграцией на проде всегда снимайте бэкап базы — откатить неудачную миграцию вручную заметно дороже, чем восстановить дамп.

Короткие ссылки открываются по http, а не https

Классическая ситуация: сайт работает по HTTPS через nginx как реверс-прокси, а Shlink генерирует и редиректит короткие ссылки на http://. Причина в том, что Shlink сам не видит протокол, по которому пришёл запрос — он общается с nginx по обычному http внутри Docker-сети, и по умолчанию считает, что вся цепочка не защищена.

Решить это можно двумя способами. Первый — явно сказать Shlink, что снаружи всегда https, через переменную окружения:

environment:
  IS_HTTPS_ENABLED: "true"

Второй, более правильный при работе за прокси — убедиться, что nginx передаёт заголовок X-Forwarded-Proto, а Shlink его учитывает:

location / {
    proxy_pass http://shlink:8080;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Если после этого схема всё равно определяется неверно, проверьте DEFAULT_DOMAIN — он должен совпадать с тем доменом, по которому реально открываются короткие ссылки, включая поддомен, если он используется отдельно от основного сайта.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

GeoLite2 не скачивается, геолокация переходов пустая

Shlink считает статистику по странам и городам через базу MaxMind GeoLite2. С 2020 года MaxMind закрыл анонимную загрузку этой базы — нужен бесплатный аккаунт и лицензионный ключ. Без него в логах контейнера будет ошибка вида Error downloading GeoLite2 database или 403 Forbidden, а в статистике посещений страна и город всегда будут пустыми либо «Unknown».

Решение — зарегистрироваться на сайте MaxMind, получить лицензионный ключ в личном кабинете и передать его Shlink:

environment:
  GEOLITE_LICENSE_KEY: ваш_ключ_maxmind

После добавления ключа перезапустите контейнер — Shlink скачает базу при следующем старте или по внутреннему расписанию обновления. Если ключ указан верно, а ошибка не уходит, проверьте исходящий доступ в интернет из контейнера: некоторые сетевые конфигурации Docker или фаервол на сервере блокируют исходящие HTTPS-соединения контейнеров, и загрузка базы GeoLite2 падает по таймауту, а не по ошибке авторизации — это видно по тексту ошибки в логе.

Web Client отвечает 401 Invalid API key

Web Client Shlink подключается к серверу API по ключу, и 401 Invalid API key означает, что ключ, введённый в интерфейсе, не совпадает с тем, что знает сервер (или уже удалён). Список действующих ключей и создание нового делаются через CLI внутри контейнера:

docker exec -it shlink shlink api-key:list
docker exec -it shlink shlink api-key:generate

Если ключей нет вовсе — например, после переустановки без сохранённого тома — сгенерируйте новый и введите его заново в Web Client при добавлении сервера. Обратите внимание: при первом запуске контейнера можно сразу задать ключ через переменную INITIAL_API_KEY, тогда не придётся заходить в контейнер руками:

environment:
  INITIAL_API_KEY: заранее_заданный_ключ

Это удобно, если Web Client настраивается декларативно через переменные окружения (SHLINK_SERVER_API_KEY) без ручного добавления сервера через UI — ключ сразу известен на обеих сторонах.

CORS блокирует запросы из Web Client

Если Web Client и API Shlink развёрнуты на разных доменах или поддоменах, браузер может блокировать запросы с ошибкой в консоли вида has been blocked by CORS policy, а сам интерфейс просто не показывает данные без явной ошибки на экране. По умолчанию Shlink разрешает запросы с любого источника, но если политика была сужена явно, нужно перечислить разрешённые домены:

environment:
  VALID_ORIGINS: "https://shlink-client.example.com,https://example.com"

Значение задаётся списком через запятую, без пробелов внутри значений и без завершающего слэша в адресах. После правки переменной обязательно пересоздайте контейнер (docker compose up -d --force-recreate shlink), а не просто перезапустите — при обычном restart переменные окружения контейнер не перечитывает заново с хоста в некоторых конфигурациях, и надёжнее пересоздание.

Отдельно проверьте, что сам домен в адресной строке совпадает буквально — www.example.com и example.com для CORS считаются разными источниками, как и разные порты.

Nginx отдаёт 404 или 502 на коротких ссылках

Если сам интерфейс Shlink открывается, а переход по короткой ссылке example.com/AbC123 выдаёт 404 от nginx, а не редирект от Shlink — значит nginx вообще не проксирует этот путь на контейнер приложения, а пытается отдать его как статический файл или обрабатывает отдельным location, который перехватывает запрос раньше нужного блока. Проверьте порядок location в конфиге: слишком широкий location / с try_files для статики, стоящий раньше проксирования на Shlink, съедает все пути, включая короткие коды.

502 Bad Gateway на коротких ссылках при том, что сам Web Client работает нормально, обычно означает, что nginx проксирует API-эндпоинты на правильный порт, а редиректы — на другой или вовсе не туда. У Shlink один порт слушает и API, и сами редиректы (по умолчанию 8080 в официальном образе) — если в конфиге исторически завели два разных upstream для разных путей, стоит свести их к одному и тому же адресу контейнера. Подробнее про типовые грабли самого nginx как реверс-прокси — в статье nginx как реверс-прокси: частые ошибки и решения.

Если после проверки конфига проблема не уходит, посмотрите логи самого Shlink в момент перехода по ссылке (docker logs -f shlink) — если запрос вообще не долетает до контейнера, дело точно в nginx, а не в приложении.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Shlink не стартует после docker compose up, что смотреть первым делом?

Логи контейнера: docker compose logs shlink. В подавляющем большинстве случаев там сразу видна причина — ошибка подключения к базе, неприменённые миграции или неверный формат переменной окружения.

Нужен ли Redis или RabbitMQ для базовой работы Shlink?

Нет, это опциональные компоненты для realtime-обновлений статистики в Web Client через WebSocket/pub-sub. Базовое сокращение ссылок и редиректы работают без них, на одной базе данных.

Можно ли использовать SQLite вместо MySQL или PostgreSQL?

Да, для небольшой нагрузки это рабочий вариант — достаточно DB_DRIVER: sqlite. Для проекта с заметным трафиком и параллельными обращениями к API лучше MySQL или PostgreSQL — они переносят конкурентную запись заметно надёжнее.

Как перенести Shlink на другой сервер без потери ссылок?

Перенести том с базой данных (или сделать дамп через mysqldump/pg_dump) и поднять на новом сервере тот же образ с теми же переменными окружения, указав на восстановленную базу. Короткие коды и статистика переедут вместе с данными.

Почему счётчик переходов не растёт, хотя ссылка открывается?

Проверьте, не блокирует ли клиент трекинг (некоторые боты и краулеры Shlink по умолчанию не считает), и не проксируется ли трафик мимо Shlink напрямую на конечный адрес через отдельное правило редиректа на уровне DNS или CDN — в этом случае Shlink просто не видит переход.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →