Предупреждение PHP Startup: Unable to load dynamic library значит одно: в одном из ini-файлов PHP есть строка extension=..., а файл расширения не найден или не загружается. PHP при этом обычно работает дальше, но без этого расширения. Сайт может падать с ошибкой вида Call to undefined function, а в cron и в консоли каждый запуск php начинается с этого предупреждения.
Самые частые причины сегодня: расширение не установлено для нужной версии PHP (на сервере их несколько), после обновления системы осталась строка от старой версии, или расширение удалено из PHP, как mcrypt в PHP 7.2. Решение почти всегда одно из двух: поставить пакет php8.x-имя для нужной версии или убрать лишнюю строку extension=.
Ниже — как за пять минут найти, какой файл подключает расширение, где PHP его ищет и что с ним делать. Команды проверены для Debian 12 и 13, Ubuntu 24.04, AlmaLinux / Rocky Linux 9 и 10 и PHP 8.2–8.4.
Как выглядит ошибка
В PHP 8 сообщение содержит список путей, по которым PHP искал файл. Примерно так:
PHP Warning: PHP Startup: Unable to load dynamic library 'mcrypt.so' (tried: /usr/lib/php/20230831/mcrypt.so (/usr/lib/php/20230831/mcrypt.so: cannot open shared object file: No such file or directory), /usr/lib/php/20230831/mcrypt.so.so (/usr/lib/php/20230831/mcrypt.so.so: cannot open shared object file: No such file or directory)) in Unknown on line 0
Разбираем по частям:
'mcrypt.so'— имя из строкиextension=./usr/lib/php/20230831/— каталог расширений (extension_dir). Число — номер API модулей конкретной ветки PHP. На Debian и Ubuntu 20220829 — это PHP 8.2, 20230831 — 8.3, 20240924 — 8.4. На AlmaLinux каталог один:/usr/lib64/php/modules/.- Текст в скобках после пути — причина.
No such file or directory— файла нет.undefined symbol— файл есть, но собран под другую версию PHP или библиотеки. Сообщение про другую библиотеку.so— не хватает зависимости. in Unknown on line 0— ошибка случилась при запуске PHP, до выполнения скриптов. Номер строки ini-файла PHP здесь не сообщает.
Шаг 1. Какой PHP выдаёт ошибку
У консоли (CLI) и у PHP-FPM разные наборы ini-файлов. Ошибка может быть только в одном из них. Если на сервере несколько версий PHP, у каждой версии свои файлы. Поэтому сначала выясняем, кто именно ругается.
- Ошибка при запуске
phpв консоли или в письмах от cron — это CLI. - Ошибка в журнале FPM — это FPM:
journalctl -u php8.3-fpmна Debian и Ubuntu,journalctl -u php-fpmна AlmaLinux.
Какие версии стоят:
# Debian, Ubuntu
ls /etc/php/
update-alternatives --display php
# AlmaLinux, Rocky
php -v
rpm -qa 'php*-fpm'
Как устроены несколько версий PHP на одном сервере, разобрано в статье Несколько версий PHP и PHP-FPM на одном сервере.
Шаг 2. Найти ini-файл со строкой extension
Команда php --ini показывает главный php.ini и каталог дополнительных ini-файлов:
php8.3 --ini
Вывод примерно такой:
Configuration File (php.ini) Path: /etc/php/8.3/cli
Loaded Configuration File: /etc/php/8.3/cli/php.ini
Scan for additional .ini files in: /etc/php/8.3/cli/conf.d
Additional .ini files parsed: /etc/php/8.3/cli/conf.d/10-opcache.ini,
/etc/php/8.3/cli/conf.d/20-mcrypt.ini,
...
Для FPM каталог другой: /etc/php/8.3/fpm/conf.d. Можно спросить сам бинарник FPM: php-fpm8.3 -i | grep -E 'Loaded Configuration|Scan this dir'.
Ищем, где упоминается проблемное расширение:
# Debian, Ubuntu
grep -rn --include='*.ini' -E '^\s*(zend_)?extension\s*=.*mcrypt' /etc/php/
# AlmaLinux, Rocky (системный PHP)
grep -rn -E '^\s*(zend_)?extension\s*=.*mcrypt' /etc/php.ini /etc/php.d/
# AlmaLinux, параллельные версии Remi
grep -rn -E '^\s*(zend_)?extension\s*=.*mcrypt' /etc/opt/remi/
Где обычно находится строка:
| Система | Файлы | Как устроено |
|---|---|---|
| Debian, Ubuntu | /etc/php/8.x/mods-available/имя.ini, ссылки в /etc/php/8.x/{cli,fpm,apache2}/conf.d/20-имя.ini | Пакет кладёт ini в mods-available, ссылки создаёт phpenmod |
| AlmaLinux, Rocky | /etc/php.d/20-имя.ini | Ini ставится вместе с пакетом расширения |
| Remi, параллельные версии | /etc/opt/remi/php83/php.d/20-имя.ini | Так же, но своя копия на каждую версию |
| Любая | Главный php.ini | Строки, добавленные вручную. Частый источник проблем после обновлений |
Шаг 3. Где PHP ищет расширения
Каталог расширений показывает любая из команд:
php8.3 -i | grep '^extension_dir'
php8.3 -r 'echo ini_get("extension_dir"), PHP_EOL;'
Посмотрите, какие файлы там есть, и сравните с тем, что ищет PHP:
ls /usr/lib/php/20230831/ # Debian, Ubuntu, PHP 8.3
ls /usr/lib64/php/modules/ # AlmaLinux, системный PHP
Если в каком-то ini-файле переопределён extension_dir, PHP ищет все расширения не там, где они лежат. Тогда ошибок будет много, по одной на каждое расширение. Проверьте: grep -rn extension_dir /etc/php/. Штатно эта строка в php.ini закомментирована.
Пишите в ini только имя расширения: extension=redis. С PHP 7.2 суффикс .so не обязателен. Абсолютный путь в строке extension= привязывает настройку к одной версии PHP и ломается при обновлении.
Шаг 4. Исправить причину
Расширение не установлено для этой версии
Самый частый случай на серверах с несколькими версиями PHP. Пакет без номера версии, например php-redis, — это метапакет: он ставит расширение только для версии по умолчанию. Для других версий его нет, а ini-файл остался.
# Debian, Ubuntu: какой пакет нужен и есть ли он
apt-cache search '^php8.3-' | grep -i redis
apt install php8.3-redis
# AlmaLinux, Rocky
dnf provides '*/redis.so'
dnf install php-pecl-redis6 # имя зависит от версии и репозитория
dnf install php83-php-pecl-redis6 # для параллельной версии Remi
После установки перезапустите FPM нужной версии: systemctl restart php8.3-fpm.
Расширение больше не нужно
Если расширение не используется, выключите его штатным способом. На Debian и Ubuntu:
phpdismod -v 8.3 -s ALL mcrypt
systemctl restart php8.3-fpm
Если строка была добавлена в php.ini вручную, закомментируйте её точкой с запятой. На AlmaLinux удалите пакет расширения, ini-файл уйдёт вместе с ним. Оставшиеся от удалённых пакетов файлы ищите по расширению .rpmsave в /etc/php.d/.
Расширение удалено из PHP
Некоторые расширения были частью PHP, а потом их убрали. После обновления старые строки в php.ini продолжают на них ссылаться.
| Расширение | Удалено в | Чем заменить |
|---|---|---|
mysql | PHP 7.0 | mysqli или PDO (pdo_mysql) |
mcrypt | PHP 7.2 (перенесено в PECL) | openssl (openssl_encrypt) или sodium |
json | С PHP 8.0 встроено и не отключается | Просто удалить строку extension=json |
xmlrpc | PHP 8.0 (перенесено в PECL) | Библиотеки на PHP |
imap, pspell, oci8 | PHP 8.4 (перенесены в PECL) | PECL-версия или пакет из стороннего репозитория |
С mcrypt чаще всего и встречается ошибка. Библиотека libmcrypt не развивается с 2007 года, поэтому правильный путь — переписать код на openssl или sodium. Тонкость при миграции: MCRYPT_RIJNDAEL_128 с 256-битным ключом совместим с AES-256 из openssl, если учесть дополнение нулями. А MCRYPT_RIJNDAEL_256 — это Rijndael с блоком 256 бит, не AES, и openssl его не расшифрует. Старые данные в этом формате перешифровывают через библиотеку phpseclib. Если старый код нужно запустить прямо сейчас, в репозитории Sury есть пакеты php8.x-mcrypt. Это временная мера.
Расширение sodium входит в PHP с версии 7.2. На Debian и Ubuntu оно уже в пакете php8.x-common, на AlmaLinux ставится пакетом php-sodium. Подробности — в руководстве PHP по mcrypt и по sodium.
Несоответствие версий после обновления
Типичная картина после перехода Debian 11 → 12 или после смены потока модуля PHP на AlmaLinux. PHP обновился, а в конфиге остались следы старой версии:
- в php.ini есть строка с абсолютным путём в старый каталог, например
/usr/lib/php/20200930/(PHP 7.4); - подключено расширение, собранное вручную через PECL под старую версию;
- подключён ionCube Loader или другой коммерческий модуль для старой ветки PHP.
В последних двух случаях сообщение бывает другим:
PHP Warning: PHP Startup: redis: Unable to initialize module
Module compiled with module API=20220829
PHP compiled with module API=20230831
These options need to match
Это значит: файл расширения нашёлся, но собран под другую ветку PHP. Номера API подсказывают, под какую. Расширение нужно поставить пакетом для текущей версии или пересобрать.
Старые конфиги удалённых версий на Debian остаются на диске. Посмотреть пакеты в состоянии «удалён, конфиги остались» и вычистить их:
dpkg -l 'php*' | grep '^rc'
apt purge php7.4-common
Расширение из PECL
Если расширения нет в пакетах, его собирают через PECL. Нужны заголовки той версии PHP, под которую собираете:
apt install php8.3-dev php-pear build-essential
update-alternatives --set php /usr/bin/php8.3
update-alternatives --set phpize /usr/bin/phpize8.3
update-alternatives --set php-config /usr/bin/php-config8.3
pecl install имя_расширения
PECL собирает модуль под тот PHP, на который указывают phpize и php-config. На сервере с несколькими версиями это главный источник ошибок: собрали под 8.4, а подключили в 8.3. Файл ляжет в каталог расширений текущей версии. Ini-файл создайте сами и включите его:
echo "extension=имя_расширения" > /etc/php/8.3/mods-available/имя_расширения.ini
phpenmod -v 8.3 имя_расширения
systemctl restart php8.3-fpm
Расширение, собранное через PECL, не обновляется вместе с системой. После перехода на новую ветку PHP его нужно собрать заново. Поэтому сначала всегда ищите готовый пакет: в Debian, Sury, EPEL или Remi их много. На AlmaLinux пакеты расширений из Remi подробно описаны в статье Репозитории EPEL и Remi для AlmaLinux и Rocky Linux. На смену PECL сейчас приходит инструмент PIE от PHP Foundation, но на серверах пакеты дистрибутива по-прежнему надёжнее.
Не хватает системной библиотеки
Если в скобках после пути указана другая библиотека, например libmemcached.so.11: cannot open shared object file, файл расширения есть, но ему не хватает зависимости. Проверьте командой ldd:
ldd /usr/lib/php/20230831/memcached.so | grep 'not found'
С пакетами из репозитория такого не бывает: зависимости ставятся сами. Обычно это модуль, скопированный с другого сервера или собранный вручную под другую версию системы.
Шаг 5. Проверить результат
php8.3 -m | grep -i redis # расширение в CLI
php-fpm8.3 -m | grep -i redis # расширение в FPM
php8.3 -v # не должно быть предупреждений
systemctl restart php8.3-fpm
journalctl -u php8.3-fpm -n 20 --no-pager
Если php -v выводит только версию, без строк PHP Warning, а в журнале FPM после перезапуска нет предупреждений, ошибка исправлена. На AlmaLinux те же проверки: php -m, php-fpm -m, journalctl -u php-fpm.
Сводная таблица
| Что в сообщении | Причина | Решение |
|---|---|---|
No such file or directory | Пакет расширения не установлен для этой версии PHP | Поставить php8.x-имя или php-имя на AlmaLinux |
No such file or directory для mcrypt, mysql, json, imap | Расширение удалено из этой версии PHP | Убрать строку extension=, переписать код на замену |
| Ошибки для всех расширений сразу | Переопределён extension_dir | Закомментировать extension_dir в php.ini |
| Путь в старый каталог API | Абсолютный путь остался после обновления PHP | Писать только имя расширения |
Module compiled with module API=... | Модуль собран под другую ветку PHP | Поставить пакет для текущей версии или пересобрать |
undefined symbol | Модуль собран под другую версию PHP или библиотеки | То же, что выше |
Нет другой библиотеки .so | Не хватает системной зависимости | ldd, поставить библиотеку или пакет расширения |
| Ошибка только в CLI или только в FPM | Разные conf.d у CLI и FPM | Искать в /etc/php/8.x/cli или /etc/php/8.x/fpm |
Исторический пример: CentOS 6 и module.so
Первая версия этой статьи была написана в 2012–2013 годах для CentOS 5 и 6 с PHP 5.4 из сторонних репозиториев. Ошибка выглядела так: Unable to load dynamic library '/usr/lib64/php/modules/module.so'. Файла module.so не существует. В пакете php-mcrypt был битый /etc/php.d/mcrypt.ini, где вместо extension=mcrypt.so стояло extension=module.so. Лечилось правкой этой строки и проверкой, что версия php-mcrypt совпадает с версией PHP.
Логика поиска с тех пор не изменилась: посмотреть, какое имя PHP пытается загрузить, найти ini-файл с этой строкой и сверить с тем, что реально лежит в каталоге расширений. Но сам рецепт устарел: mcrypt удалён из PHP, а CentOS 6 давно не поддерживается.
Частые вопросы
Можно ли просто не обращать внимания на это предупреждение?
Можно, если расширение действительно не нужно. Но тогда лучше выключить его, а не терпеть шум. Предупреждение попадает в письма cron, в журналы и иногда в вывод консольных скриптов, ломая их разбор. А если расширение нужно, сайт будет падать на первом вызове его функций.
Почему в консоли ошибки нет, а в журнале PHP-FPM есть?
У CLI и FPM разные каталоги ini-файлов: /etc/php/8.x/cli/conf.d и /etc/php/8.x/fpm/conf.d на Debian и Ubuntu. Расширение могли включить только для одного из них, или строку дописали в один из двух php.ini. Проверяйте каждую сторону отдельно: php -m и php-fpm8.x -m.
Нужно ли писать .so в строке extension?
Нет. С PHP 7.2 достаточно имени: extension=redis. PHP сам добавит расширение файла и будет искать его в extension_dir. Так конфиг не зависит от системы и не ломается при переходе на новую версию. Абсолютный путь нужен только для модулей, которые лежат вне каталога расширений, например некоторых коммерческих загрузчиков.
Чем отличается extension от zend_extension?
Обычные расширения подключаются через extension=. Модули, которые встраиваются в движок Zend, — OPcache, Xdebug, ionCube Loader — подключаются через zend_extension=. Если перепутать, модуль не заработает, а PHP выведет предупреждение о неправильном способе загрузки.
Как вернуть mcrypt для старого сайта на PHP 8?
На Debian и Ubuntu с репозиторием Sury есть пакеты php8.x-mcrypt, собранные из PECL. На AlmaLinux похожие пакеты есть в Remi. Это временное решение, чтобы сайт работал, пока код переписывают на openssl или sodium. Библиотека libmcrypt не поддерживается почти двадцать лет, строить на ней новые решения нельзя.
После обновления Debian сломались все расширения сразу. Что делать?
Скорее всего, обновилась ветка PHP, а в php.ini остались абсолютные пути или переопределённый extension_dir от старой версии. Сравните php --ini и extension_dir с реальным каталогом в /usr/lib/php/. Затем поставьте пакеты расширений для новой версии: список старых можно взять из dpkg -l 'php7.4-*', если они ещё значатся в системе.