Что делать, если не отправляется почта из PHP

Тривиальные ситуации, когда имеется непорядок со значением sendmail_path в конфигурации php, решаются легко. Проверьте через phpinfo();, чтобы у вас было задано значение типа /usr/sbin/sendmail -t -i в конфиге, и чтобы за /usr/sbin/sendmail действительно скрывался установленный и работающий MTA. Но сегодня пришлось побеждать менее типовую проблему: из php-скрипта почта как бы отправлялась, но где-то дальше сообщения терялись.

Убеждаемся, что PHP точно пытается отправить почту

Для начала напомню, что начиная с php 5.3 можно включить лог для всей исходящей почты, отсылаемой через функцию mail() из php. Достаточно в конфигурацию php.ini добавить такую настройку:

mail.log = /var/log/phpmail.log

При этом важно, чтобы пользователь, от которого работает php, имел доступ к файлу /var/log/phpmail.log на запись. Рекомендую для комфортной отладки создать файл и позволить всем в него записывать, примерно так:

touch /var/log/phpmail.log
chmod 666 /var/log/phpmail.log

Если у вас php работает как модуль Apache, то перезапустите последний.

Записи в логе будут примерно такие:

mail() on [/var/www/modules/system/system.mail.inc:83]: To: admin@example.tld --
Headers: From: user@example.tld

Если версия php у вас поновее чем 5.3, то в лог добавятся дата и время (что, конечно, очень полезно). Например, PHP версии 5.5.32 точно добавляет дату и время (но с какой в точности версии это началось — я не знаю). Всё-таки главное в этом логе — абсолютный адрес скрипта и номер строки, с которой вызывалась функция.

В общем, следующий тривиальный скрипт должен обязательно оставить след в логе:

<?php
  $to = 'yourmail@example.tld';
  $subject = 'Test email subject';
  $message = 'Test from PHP at ' . date('Y-m-d H:i:s');
  mail($to, $subject, $message);
  echo 'Mail sent';
?>

У меня была как раз такая ситуация: php исправно делал свою работу и записи в логе почты оставлял (ничего при этом не оставляя в логе ошибок).

Проверяем, что письмо может отправить администратор из консоли (без всяких там PHP)

Одновременно с этим почта у меня прекрасно отправлялась из серверной консоли. Проверить можно так:

echo "Test message" | mail -s "Test emal subject" yourmail@example.tld

Если вдруг на вашем сервере нет утилиты mail, то установите пакет mailx вот так:

yum -y install mailx

Анализируем лог postfix

Пришлось заглядывать в лог почтовика. Он пишется в файл /var/log/maillog (актуально для CentOS).

И вот там-то нашлись неприятные записи такого характера:

18:34:31 postfix/sendmail[26381]: fatal: chdir /var/spool/postfix: Permission denied
18:51:16 postfix/sendmail[4603]: fatal: chdir /var/spool/postfix: Permission denied

Даже минимальный опыт общения с линуксом подсказывает, что тут дело либо в несоответствии прав/владельцев, либо в какой-то дефолтной защите, типа фаерволла или… SELinux. В данном случае «виноват» последний.

Отучаем SELinux блокировать почту, отправляемую из PHP

Проверить можно такой командой:

getsebool httpd_can_sendmail

Если вывод будет httpd_can_sendmail --> off, то значит вашу почту блокирует SELinux. Чтобы его от этого отучить на постоянной основе выполните команду:

setsebool -P httpd_can_sendmail 1

И после этого ждите несколько секунд, пока снова не увидите приглашение командной строки. Без ключа -P блокировка снимется только до перезагрузки.

Кстати, пусть вас не смущает, что в параметрах SELinux упомянут httpd. Даже если у вас Apache отсутствует в принципе, а используется, например, связка nginx и php-fpm, то почта всё равно может блокироваться с аналогичными симптомами. Решается проблема точно также и для php-fpm (меняем ту же переменную).

Запрещаем виртуальному хосту отправлять почту

Иногда бывает такая ситуация, что надо наоборот запретить отправлять почту какому-то сайту. Например, если ясно, что на нём вирус, но на лечение вируса нужно время.

Тогда действуем от противного — в настройки виртуального хоста проблемного сайта добавляем параметр:

php_admin_value sendmail_path "/dev/null"

И перезапускаем Apache, чтобы изменения вступили в силу. Либо вы можете включить упомянутый выше параметр SELinux, однако, учтите, что это не поможет, если в вашей системе SELinux отсутствует или перманентно отключен.



2 комментария на запись «Что делать, если не отправляется почта из PHP»:

  1. > Если версия php у вас поновее чем 5.3, то в лог добавятся дата и время (что, конечно, очень полезно).
    У меня php 5.4, но дата и время в лог не добавляется! Есть решение?

  2. Leonid, а у меня PHP 5.5.32 пишет в лог даты. Запись выглядит примерно так:
    [05-Apr-2016 15:22:28 Europe/Moscow] mail() on [/var/www/xxx/public/sites/... ...MailSystem.class.php:47]: To: xxx@xxx -- Headers: MIME-Version: 1.0 Content-Type: text/html; charset=UTF-8; Content-Transfer-Encoding: 8Bit X-Mailer: Drupal Return-Path: xxx@xxx Sender: xxx@xxx From: xxx@xxx Bcc: xxx@xxx

    C какой точно версии даты начали писаться — не скажу. Пока только известно, что PHP 5.3.3 их не пишет. И ваш 5.4 — тоже. А 5.5.32 уже точно пишет.

    Если вы не хотите обновлять php, то, пожалуй, единственное решение в том, чтоб написать свой скрипт-прокладку, который потом указать в sendmail_path. То есть php будет на этот скрипт передавать письма, скрипт будет их желаемым вами образом логировать, а потом уже отдавать в настоящий sendmail для реальной отправки.

    Самый простой вариант скрипта-прокладки выглядит примерно так:
    sendmail_path = "tee -a /var/log/phpmail.log | /usr/sbin/sendmail -t -i" 🙂

    Ремарка для новичков: это надо прописать в /etc/php.ini, предварительно создав файл /var/log/phpmail.log с правами на запись.

Прокомментируйте: