Общий хостинг DreamHost: как он устроен
Эта страница описывает аккаунт общего хостинга DreamHost так, как его видит владелец: логин dh_user, сервер <server>.dreamhost.com, корень документов ~/example.com/ и сайт example.com. Отличие от Beget не в бренде, а в веб-слое, и следующие главы показывают, где это задевает. Инструменты, которые понадобятся: curl, SSH, grep.
Только Apache, TLS в Apache
Спереди нет nginx. Apache принимает соединение и для адреса https сам держит сертификат. Заголовок ответа говорит только об Apache:
curl -sS -D - -o /dev/null https://example.com/
HTTP/2 200 content-type: text/html; charset=UTF-8 server: Apache
Зонд из проверки сервера даёт подробности. Запишите его в корень документов под именем, которое никто не угадает:
vi ~/example.com/probe-7f3a.php
<?php
echo "HTTPS=", var_export($_SERVER['HTTPS'] ?? null, true),
" XFP=", var_export($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? null, true),
" SERVER_SOFTWARE=", $_SERVER['SERVER_SOFTWARE'] ?? '',
" sapi=", PHP_SAPI, "\n";
curl -sS https://example.com/probe-7f3a.php
HTTPS='on' XFP=NULL SERVER_SOFTWARE=Apache sapi=cgi-fcgi
HTTPS='on' означает, что TLS заканчивается в этом Apache. Никакой прокси не присылает X-Forwarded-Proto, поэтому XFP=NULL. Редирект на https, который проверяет только этот заголовок, срабатывает и на https-запросах и отправляет каждый адрес на самого себя. Правило, которое здесь работает, проверяет %{HTTPS}:
RewriteCond %{HTTPS} off RewriteCond %{HTTP:X-Forwarded-Proto} !https RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
Цикл, который исправило это правило, вместе с командами, которые его показывают, — в статье Переезд на DreamHost: цикл. Удалите зонд, когда закончите:
rm ~/example.com/probe-7f3a.php
Статические файлы проходят через .htaccess
Переднего сервера нет, поэтому картинки, CSS и JavaScript тоже отдаёт Apache, и каждое правило в .htaccess срабатывает и для них. На Beget наоборот: существующий на диске статический файл отдаёт nginx, и Apache не читает для него правила (nginx перед Apache). На DreamHost из этого следуют две вещи:
- Редирект для перенесённой картинки работает, даже если старый файл ещё лежит на сервере.
- Правило для страниц, которое подходит и к пути файла, теперь срабатывает и для этого файла. На Beget такое правило скрыто и проявляется только после переезда.
Реальный случай. Сайт хранит свои PHP-классы, а с ними картинки и скрипты, которые они используют, в папке с именем .php. Его .htaccess отправляет PHP-страницу с PATH_INFO (page.php/extra) на чистый адрес:
RewriteCond %{REQUEST_FILENAME} -f RewriteCond %{REQUEST_URI} ^(.+\.php)/.+$ RewriteRule ^ https://%{HTTP_HOST}%1 [R=301,L]
Шаблон подходит и к /.php/img/...: имя папки кончается на .php, и за ним идёт слеш. На DreamHost картинка из этой папки отвечает 301, и редирект ведёт на папку:
curl -sS -o /dev/null -D - https://example.com/.php/img/cards/airport.jpg | grep -i -E '^(HTTP|location|server)'
HTTP/2 301 location: https://example.com/.php server: Apache
На странице видна сломанная картинка, а журнал ошибок ничего не объясняет. Какие файлы попали под правило, показывает журнал доступа, скрипты тоже:
awk '$9 == 301 && $7 ~ /\/\.php\// {print $7}' ~/logs/example.com/https/access.log | sort | uniq -c
3 /ru/.php/img/cards/actress-800x600.jpg 1 /.php/scripts/top10_script3.js 1 /.php/scripts/top10_script2.js 1 /ao/.php/scripts/top10_script2.js
Исправление: требовать перед .php имя файла:
RewriteCond %{REQUEST_FILENAME} -f RewriteCond %{REQUEST_URI} ^(.*[^/]\.php)/.+$ RewriteRule ^ https://%{HTTP_HOST}%1 [R=301,L]
[^/] означает любой символ, кроме слеша. Поэтому page.php/extra по-прежнему подходит, а /.php/img/cards/airport.jpg уже нет. Тот же запрос к серверу, похожему на Beget, и к серверу, похожему на DreamHost, до исправления и после, разобран в проверке 6 плана проверок Docker.
PHP как FastCGI
DreamHost запускает PHP как приложение FastCGI на всех тарифах хостинга (PHP overview). На общем сервере это значит, что mod_fcgid запускает обработчик на каждый сайт; файл обработчика назван по версии PHP, например php85.cgi для PHP 8.5. Зонд выше ответил sapi=cgi-fcgi, и две команды показывают то же самое из оболочки:
pgrep -l -u user php
2745746 php85.cgi
ls /usr/sbin/apache2*
/usr/sbin/apache2 /usr/sbin/apache2ctl
Apache установлен, но в списке его модулей нет php_module: PHP не внутри него. Полная проверка, включая apache2ctl -M на сервере, которым вы управляете, — в статье Какой у вас сервер.
Почему php_flag отвечает 500
php_flag и php_value — директивы mod_php. На DreamHost mod_php не загружен, поэтому Apache не знает этих слов. Одна такая строка в .htaccess останавливает каждый запрос в этой папке и ниже. Сайт отвечает 500 на каждой странице, включая главную:
curl -sS -D - -o /dev/null https://example.com/
HTTP/2 500 content-length: 533 content-type: text/html; charset=iso-8859-1 server: Apache
Журнал ошибок называет файл и первую неизвестную директиву:
tail -n 1 ~/logs/example.com/https/error.log
[Fri Nov 06 23:29:54 2026] [example.com] [alert] [client 203.0.113.7:37118] [pid 3064457] config.c(2045): /home/user/example.com/.htaccess: Invalid command 'php_flag', perhaps misspelled or defined by a module not included in the server configuration
Строки, которые это вызвали, с того же аккаунта:
grep -n -E '^\s*php_(flag|value)' ~/example.com/.htaccess
78:php_flag display_startup_errors off 79:php_flag display_errors off 80:php_flag html_errors off 81:php_flag log_errors on 82:php_flag ignore_repeated_errors off 83:php_flag ignore_repeated_source off 84:php_flag report_memleaks on 85:php_flag track_errors on 86:php_value docref_root 0 87:php_value docref_ext 0 91:php_value error_reporting -1 92:php_value log_errors_max_len 0
Закройте блок так, чтобы хостинг без mod_php его пропускал, а не падал:
vi ~/example.com/.htaccess
<IfModule mod_php.c> php_flag display_errors off php_flag log_errors on # the other ten lines stay inside this block </IfModule>
Полный блок из двенадцати строк и копия для отката — в статье 500 после переезда: исправление. Общий вид защитного блока — в статье IfModule.
Настройки PHP в phprc
Когда блок пропущен, PHP работает с настройками хостинга по умолчанию. DreamHost читает дополнительные настройки для каждой версии PHP из файла в домашней папке с именем phprc, который работает как php.ini (php.ini overview). Для PHP 8.5 путь — ~/.php/8.5/phprc:
vi ~/.php/8.5/phprc
display_startup_errors = Off display_errors = Off html_errors = Off log_errors = On ignore_repeated_errors = Off ignore_repeated_source = Off report_memleaks = On error_reporting = -1
track_errors убрали в PHP 8.0, а log_errors_max_len в PHP 8.1, поэтому их здесь нет. PHP читает файл при старте своего обработчика. Завершите работающий обработчик, чтобы следующий запрос запустил новый:
pkill -x php85.cgi
Провайдер даёт ту же инструкцию с killall -9 php85.cgi -u dh_user и объясняет, почему файл может выглядеть неработающим (My phprc file isn't updating). Зонд после перезагрузки показывает новые значения; полный вывод до и после — в статье случай с php_flag.
Журналы ошибок и доступа
Журналы лежат в домашней папке, по папке на домен и по папке на протокол. Для https-сайта из этого примера:
~/logs/example.com/https/error.log
~/logs/example.com/https/access.log
У http-сайта та же пара в ~/logs/example.com/http/. Apache пишет туда свои ошибки, а сообщения PHP следуют за ними, когда error_log пуст, с пометкой mod_fcgid: stderr:
[Sat Nov 07 01:01:52 2026] [example.com] [warn] [client 203.0.113.9:61816] [pid 1836802] fcgid_bucket.c(153): mod_fcgid: stderr: PHP Deprecated: Creation of dynamic property Related::$urls is deprecated in /home/user/example.com/.php/Related.php on line 10
Провайдер документирует те же файлы и просмотр в панели (Viewing and examining your error log via SSH). 500 из-за сломанной директивы — это строка config.c, как выше; 403 из-за прав — строка Permission denied.
SSH и права файлов
SSH включён, но пользователь сайта должен быть пользователем оболочки (SSH overview). Войдите с ключом, который загрузили, затем перейдите в корень документов:
ssh -i ~/.ssh/<key> dh_user@<server>.dreamhost.com
cd ~/example.com
Аккаунт работает с обычными правами UNIX: папки 755, файлы 644. Apache работает под другим пользователем, поэтому папка 700 или файл 600 для него невидимы и страница отвечает 403, или 404, когда закрыта вся папка. Провайдер описывает биты в статье Unix permissions overview. Если запустить команду из корня документов после загрузки, владелец — dh_user, а права такие:
stat -c '%a %U:%G %n' dev/python dev/python/files
755 dh_user:pg000000 dev/python 700 dh_user:pg000000 dev/python/files
rsync и zip сохраняют эти права, и новый сервер копирует их точно так же. Сохраните старые права в файл, затем исправьте папки и файлы на сервере и в локальной копии:
find ~/example.com -printf '%m %p\n' > ~/backups/example.com.modes.$(date +%Y%m%d-%H%M%S)
find ~/example.com -type d -exec chmod 755 {} +
find ~/example.com -type f -exec chmod 644 {} +
Следующая загрузка не должна вернуть права обратно. Копируйте с rsync --chmod=D755,F644; эта опция объяснена в статье Копирование файлов. Переезд, на котором возникла эта проблема, описан в примере, а Docker-образ воспроизводит её с корнем документов только для чтения, принадлежащим другому пользователю: проверка 5.
Python и Node.js
DreamHost документирует Python как скрипты CGI и FastCGI на общем хостинге. Файл Python должен заканчиваться на .py, .cgi или .fcgi, начинаться с пути интерпретатора, иметь права 755 и использовать переводы строк UNIX (Guidelines for setting up a Python file). Python 3 установлен; своя версия и пакеты ставятся в виртуальное окружение в домашней папке (Python overview).
Passenger на DreamHost не среда запуска: страница провайдера Supported and unsupported technologies перечисляет Passenger как NO, не поддерживается на серверах DreamHost. Поэтому у приложения на WSGI-фреймворке здесь нет точки входа Passenger, в отличие от Beget.
Node.js доступен на Managed VPS и выделенных серверах, но не на общем хостинге, как описывают та же страница поддерживаемых технологий и обзор Node.js overview.
Проверка на сервере, похожем на DreamHost
В репозитории есть Docker-образ, который копирует этот веб-слой: только Apache, PHP как FastCGI через mod_fcgid, без mod_php, папка phprc на каждый сайт, журналы в /logs/example.com/ и самоподписанный сертификат. Запустите на нём свой сайт до переезда: Образ, похожий на DreamHost.
Автор статьи: Arthur Isaev