Из практики админа 05. Перенос сайта с недоступного хостинга на свой сервер
Note
Материал сверен с официальной документацией (nginx, PHP, Grav, Let's Encrypt и Certbot, curl, OpenSSL, Git, Zabbix; ссылки — в конце статьи). Статья основана на реальном случае, описанном обезличенно: названия, адреса и значения изменены. Примеры вывода — иллюстрации. Перед работой на боевых системах проверьте шаги на тестовой машине.
Небольшой интернет-магазин работал на виртуальном хостинге. Сайт построен на Grav — CMS (системе управления содержимым) на PHP, которая хранит страницы и данные в обычных файлах и обходится без базы данных. К магазину подключён плагин приёма платежей. Однажды хостинг стал недоступен целиком: и сайт, и SFTP (передача файлов поверх SSH) отвечали только тайм-аутами. Доступа к файлам не было. Ждать хостинг-провайдера мы не стали и решили восстановить сайт на собственном сервере с Ubuntu, nginx и PHP-FPM из того, что хранилось вне хостинга.
Ниже — из чего мы собирали сайт, как устроили сервер, на чём споткнулись и как проверяли результат до переключения DNS. Конфигурации — типовые примеры: у вас будут другие пути, версия PHP и набор плагинов.
Что нужно знать заранее
- Linux для админа Windows 09. Веб-сервер nginx: сайт, обратный прокси, логи — блоки
serverиlocation,nginx -tиreload. - HTTPS и сертификаты: как работает Let's Encrypt и как получить сертификат — проверка домена, Certbot, срок действия.
- Сети с нуля 07. DNS: как имя превращается в адрес — A-записи и TTL (время, на которое запись кэшируется).
- Linux для админа Windows 04. Файловая система, диски и права доступа — владелец, группа, режимы
640и750, бит setgid. - Git для администратора: храним конфигурации и скрипты — клонирование и
.gitignore.
PHP-FPM (FastCGI Process Manager) — менеджер процессов PHP: nginx сам PHP не выполняет, а передаёт запрос процессам FPM через сокет. Процессы объединены в пулы; у каждого пула свой пользователь и свой сокет. Flat-file CMS — система, у которой данные лежат в файлах, а не в базе: восстановить такой сайт — значит восстановить набор файлов и правильно выставить права.
Симптомы: хостинг молчит
Сайт и SFTP не отвечали вовсе: клиент ждал и получал тайм-аут. «Отказ в соединении» означает, что сервер ответил и закрыл порт; тайм-аут — что ответа нет совсем. Прежде чем решать, что хостинг недоступен, проверьте его из другой сети, например через мобильный интернет. У нас недоступно было всё, что находилось на хостинге, а файлов сайта на руках не было. Оставалось собирать сайт из резервных источников.
Что у нас было: инвентаризация
Сначала мы выписали, что нужно сайту и откуда это взять.
| Что нужно сайту | Откуда взяли | Чего не хватало |
|---|---|---|
| Ядро CMS | Локальная сборка ядра | Ничего |
| Страницы, тема, плагины | Репозиторий Git | Одного файла плагина (см. ниже) |
| Данные магазина, конфигурация, учётные записи, секреты | Архив резервной копии недельной давности | Всего, что изменилось после даты архива |
| Заказы и правки в админке после даты архива | Нигде | Потеряны |
Из архива учётных записей мы не переносили отладочную учётную запись разработчика. Кэш и журналы переносить не нужно: Grav создаёт их сам, нужны лишь пустые каталоги с правильными правами.
Потеряли заказы и правки в админке, сделанные после даты последнего архива: он был недельной давности, и бэкап оказался неполным по времени. Для магазина, где данные меняются каждый день, это много. Вывод: ежедневный бэкап данных магазина (о нём ниже).
Как строили сервер
Свой пользователь и свой пул PHP-FPM
Сайту мы дали отдельного системного пользователя без возможности входа и отдельный пул PHP-FPM со своим сокетом. Рассуждение такое (практика автора): если уязвимым окажется плагин или сама CMS, злоумышленник получит права пользователя этого сайта, а не всего веб-сервера. Оговорка из документации PHP: пулы — не механизм безопасности и полного разделения не дают (например, у всех пулов общий OPcache — кэш скомпилированного PHP-кода). Это ограничение ущерба, не стена.
sudo adduser --system --group --home /srv/shop shop
sudo mkdir -p /srv/shop/www
sudo install -d -o shop -g shop -m 700 /srv/shop/private \
/srv/shop/private/tmp /srv/shop/private/sessions
sudo -u shop test -w /srv/shop/private/sessions && echo "запись разрешена"
Опция --system создаёт системного пользователя без пароля и с оболочкой /usr/sbin/nologin, --group — одноимённую группу, --home создаёт каталог, если его нет. Каталог private лежит внутри каталога сайта, но вне веб-корня (www), принадлежит пользователю пула и закрыт от остальных (700): в нём будут временные файлы и сессии PHP. Создавать его нужно командой install: каталоги, созданные через sudo mkdir, достались бы root, и пул не смог бы писать в них. Старые сессии в своём каталоге придётся чистить самим (практика автора).
Пул — файл /etc/php/<версия>/fpm/pool.d/shop.conf (так устроены пакеты Ubuntu; путь зависит от версии PHP):
[shop]
user = shop
group = shop
listen = /run/php/shop.sock
listen.owner = shop
listen.group = www-data
listen.mode = 0660
pm = ondemand
pm.max_children = 8
pm.process_idle_timeout = 30s
security.limit_extensions = .php
env[TMPDIR] = /srv/shop/private/tmp
php_admin_value[upload_tmp_dir] = /srv/shop/private/tmp
php_admin_value[session.save_path] = /srv/shop/private/sessions
php_admin_value[sendmail_path] = /usr/sbin/sendmail -t -i
По умолчанию владелец и группа сокета — пользователь пула, а режим 0660, поэтому nginx (он работает от www-data) не смог бы подключиться: группу сокета мы отдали веб-серверу через listen.group. security.limit_extensions разрешает пулу выполнять только файлы .php, чтобы ошибка в настройке веб-сервера не позволила запустить другие файлы как PHP. Числа в pm.* — заглушки: подбирайте по объёму памяти. Строка с sendmail_path задаёт, как PHP отправляет письма; к почте мы вернёмся.
Права на файлы
Схема из нашего случая: файлы 640, каталоги 750, закрытые каталоги недоступны группе веб-сервера, файлы с секретами — 600.
cd /srv/shop/www
sudo mkdir -p cache logs tmp backup images assets
sudo chown -R shop:www-data /srv/shop/www
sudo find . -type d -exec chmod 750 {} +
sudo find . -type f -exec chmod 640 {} +
sudo chmod 2750 images assets
closed="cache logs backup tmp user/data user/accounts user/config"
sudo chown -R shop:shop $closed
sudo chmod 700 $closed
sudo chmod 600 user/config/<файл-с-секретами>
Перед chown -R проверяйте текущий каталог (pwd): рекурсивная команда в неверном месте ломает систему, и откатить её нечем. Каталоги images и assets помечены битом setgid (2750; практика автора): CMS пишет туда файлы, которые отдаёт nginx, и при строгой umask они могли бы оказаться недоступны веб-серверу, а setgid заставляет их наследовать группу www-data. Документация Grav предлагает другое решение: images.cache_perms: '0775' в system.yaml или umask(0002) в setup.php. Если магазин отдаёт посетителям файлы из user/data, понадобятся исключения из правил поставки (см. ниже).
Документация Grav описывает три сценария прав. По смыслу наша схема ближе к первому, названному предпочтительным (один пользователь у PHP и у файлов). Там же оговорено, что для выделенного веб-сервера этот вариант недостаточно безопасен: речь о веб-сервере, запущенном от личной учётной записи. У нас отдельный пользователь без входа и свой пул, так что ущерб ограничен (практика автора). Вариант с общей группой и правами 775 и 664 даёт группе веб-сервера запись во все файлы, и мы его не брали.
nginx: переписываем правила, которые жили в .htaccess
Файл .htaccess читает Apache, а nginx его не читает, поэтому правила, закрывающие служебные каталоги, нужно перенести в конфигурацию самим. Если этого не сделать, посетители смогут скачать файлы учётных записей, конфигурации, сохранённые данные форм и даже резервные копии сайта: именно об этом предупреждает документация Grav. Она же предлагает взять за основу файл webserver-configs/nginx.conf из поставки. Ниже его сокращённая иллюстрация: набор не повторяет поставку полностью (нет, например, исключений для аватаров и медиафайлов), поэтому для рабочего сервера переносите правила из поставки целиком и сверяйте их с версией своей CMS.
# порт 80: проверка домена и редирект
server {
listen 80;
listen [::]:80;
server_name shop.lab.example www.shop.lab.example;
location /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
}
location / {
return 301 https://www.shop.lab.example$request_uri;
}
}
# порт 443, голый домен -> www
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name shop.lab.example;
ssl_certificate /etc/letsencrypt/live/shop.lab.example/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/shop.lab.example/privkey.pem;
return 301 https://www.shop.lab.example$request_uri;
}
# порт 443, сам сайт
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name www.shop.lab.example;
ssl_certificate /etc/letsencrypt/live/shop.lab.example/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/shop.lab.example/privkey.pem;
root /srv/shop/www;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# скрытые файлы, кроме каталога .well-known
location ~ (^|/)\.(?!well-known(?:/|$)) { return 403; }
# служебные каталоги
location ~* ^/(\.git|cache|bin|logs|backup|tmp|webserver-configs|tests)/ {
return 403;
}
location ~* ^/user/(env/[^/]+/)?(config|accounts|data)/ {
return 403;
}
# исходники и скрипты внутри user, system, vendor
location ~* ^/(system|vendor)/.*\.(txt|xml|md|json|yaml|yml|php|twig)$ {
return 403;
}
location ~* ^/user/.*\.(txt|md|json|yaml|yml|php|twig)$ {
return 403;
}
# служебные файлы в корне
location ~* ^/(LICENSE\.txt|composer\.(json|lock)) { return 403; }
location ~* ^/(nginx\.conf|web\.config|htaccess\.txt) { return 403; }
# PHP выполняется только через index.php
location = /index.php {
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_pass unix:/run/php/shop.sock;
}
location ~ \.php$ { return 404; }
}
Что здесь важно. Порядок регулярных выражений имеет значение: nginx проверяет их по очереди и останавливается на первом совпадении, поэтому запреты стоят выше правила для PHP. Точное совпадение (= /index.php) завершает поиск сразу, так что /index.php не попадает под ~ \.php$, а любой другой .php получает 404. Исключение для .well-known взято из поставки: без него запрет скрытых файлов закрыл бы и проверочные файлы Let's Encrypt, если бы проверка дошла до этого сервера. Проверка Let's Encrypt умеет следовать редиректам, но только на порты 80 и 443; проверочный каталог мы всё равно отдаём напрямую, без перенаправления. Как всегда: сначала nginx -t, потом reload.
Сертификат и автопродление
Сертификат был выпущен в режиме webroot: программа Certbot (клиент Let's Encrypt) кладёт проверочные файлы в каталог /.well-known/acme-challenge/ внутри указанного веб-корня. В каком порядке выпуск идёт относительно переключения DNS, зависит от вашей схемы; ниже два пути.
sudo mkdir -p /var/www/letsencrypt
sudo certbot certonly --webroot -w /var/www/letsencrypt \
-d shop.lab.example -d www.shop.lab.example
sudo certbot renew --dry-run
Есть тонкость порядка. Проверка домена по HTTP (HTTP-01) приходит на адрес сайта по имени, а имя пока указывает на недоступный хостинг, поэтому выпуск не пройдёт. Допустим, сертификата для нового сервера ещё нет. Первый путь — проверка домена через DNS (DNS-01, если у DNS-провайдера есть API): он описан в статье про Let's Encrypt. Второй путь (практика автора): сначала переключить A-записи и выпустить сертификат, а предпроверки по HTTPS до этого делать с временным самоподписанным сертификатом. Пока выпуск не закончен, у посетителей возможны предупреждения о сертификате, поэтому по возможности выбирайте DNS-01. Блоки порта 443 в nginx появляются только после выпуска (при втором пути — после создания временного самоподписанного сертификата и указания его в ssl_certificate): пока файлов сертификата нет, nginx -t сообщит об ошибке.
Автопродление обычно уже настроено пакетом: задание живёт либо в таймере systemd, либо в cron (systemctl list-timers). Чтобы nginx подхватывал новый сертификат, нужен хук развёртывания: исполняемый файл в /etc/letsencrypt/renewal-hooks/deploy/ запускается после успешного продления.
#!/bin/sh
systemctl reload nginx
Сохраните его как /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh и дайте права 755. Проверить хук можно командой sudo certbot renew --dry-run --run-deploy-hooks: без последней опции при пробном запуске хуки развёртывания не выполняются.
Что не помогло и где споткнулись
Первое: правила из .htaccess. Старые правила защиты лежали в .htaccess, а nginx его не читает: без переноса закрытые каталоги отдавали бы содержимое. Проверку закрытых путей мы сделали обязательной (см. ниже).
Второе: ошибка 500 из-за .gitignore. После запуска на страницах магазина появилась ошибка 500. Причину нашли по журналу: не хватало файла класса плагина, и в репозитории его не было. В .gitignore стояло правило по имени файла с секретами, а на Windows регистр в именах файлов не различается, и под правило попал класс плагина с похожим именем. Как именно регистр влияет на сопоставление шаблонов, в документации Git не описано, так что это наше объяснение, а не цитата. Проверка от объяснения не зависит: git check-ignore -v путь/к/файлу показывает, какая строка какого .gitignore сработала. Правило мы уточнили, файл добавили в репозиторий. Шаблон лучше привязать к каталогу: с косой чертой в начале или середине он считается от каталога самого .gitignore.
Третье: цепочка сертификатов платёжного сервиса. Запрос к API платёжного сервиса падал с ошибкой self-signed certificate in certificate chain. По документации OpenSSL это значит, что цепочку удалось построить, но подходящего корневого сертификата в хранилище доверенных центров не нашлось. Сервис использует цепочку удостоверяющего центра, корня которого нет в системном хранилище Ubuntu. Менять системное хранилище мы не стали. В настройках плагина указали путь к файлу с корневым и промежуточным сертификатами. У cURL это собственный набор доверенных центров (в командной строке — опция --cacert): он действует только для этих запросов, а файл может содержать несколько сертификатов в формате PEM. Проверка из командной строки:
curl --cacert /srv/shop/private/pay-ca.pem -sS -o /dev/null \
-w '%{http_code}\n' https://api.pay.example/
Файлы сертификатов берите из документации платёжного сервиса и сверяйте отпечатки, если сервис их публикует. Заведите напоминание: у промежуточного сертификата есть срок, его нужно заменить до окончания (команды для просмотра срока — в статье про Let's Encrypt). Не решайте эту ошибку через -k: документация curl советует никогда не пропускать проверку в рабочей среде.
Проверка до переключения DNS
Адрес сайта в DNS ещё указывал на недоступный хостинг, поэтому нового сервера «снаружи» не было видно. Проверить его можно, не трогая DNS: curl --resolve подставляет свой адрес для пары «имя:порт», как это сделал бы файл hosts. В URL остаётся имя сайта, поэтому заголовок Host и проверка сертификата идут по настоящему имени. Записи нужны отдельно для каждого порта: для 80 и для 443.
SITE=www.shop.lab.example
IP=203.0.113.10
curl -sS -o /dev/null -w '%{http_code}\n' \
--resolve "$SITE:443:$IP" "https://$SITE/"
curl -sS -o /dev/null -w '%{http_code} %{redirect_url}\n' \
--resolve "shop.lab.example:80:$IP" "http://shop.lab.example/"
Если сертификат ещё не выпущен и в блоке 443 стоит временный самоподписанный, добавьте к запросам -k. Это отключает проверку и допустимо только для такой предпроверки. Мы проверили четыре вещи.
- Все адреса из карты сайта (sitemap) отвечают
200. Адреса достаём изsitemap.xmlи проверяем в цикле, на экран выводим только ответы, отличные от200. - Приватные пути отвечают
403, в том числе служебные файлы в корне. Файлов по этим путям может и не быть: запрет срабатывает по пути. Произвольный/x.phpдолжен отвечать404. - Редиректы. С порта 80 —
301наhttps, с голого домена —301наwww. - Оформление заказа до платёжной ссылки. Тестовый заказ через форму создаёт платёжную ссылку без списания. Это безопасная проверка до оплаты; реальный платёж в тестах мы не делали.
curl -s --resolve "$SITE:443:$IP" "https://$SITE/sitemap.xml" \
| grep -o '<loc>[^<]*</loc>' | sed -e 's/<[^>]*>//g' > urls.txt
while read -r u; do
c=$(curl -s -o /dev/null -w '%{http_code}' \
--resolve "$SITE:443:$IP" "$u")
[ "$c" = 200 ] || echo "$c $u"
done < urls.txt
for p in /user/config/system.yaml /user/accounts/ /backup/ /logs/ \
/.git/config /composer.json /LICENSE.txt /x.php; do
c=$(curl -s -o /dev/null -w '%{http_code}' \
--resolve "$SITE:443:$IP" "https://$SITE$p")
echo "$c $p"
done
Только после этого мы сменили A-записи. DNS-провайдер работал без проксирования трафика: запись указывала прямо на адрес сервера. Если у домена есть AAAA-записи (IPv6), их тоже нужно изменить или убрать, иначе часть посетителей продолжит ходить на старый адрес (практика автора). Заранее (не позже чем за время, равное прежнему TTL, до переключения) уменьшите TTL записей. Откат здесь ограничен: старый хостинг недоступен, возвращать записи некуда. Сохраните прежние значения записей на случай, если хостинг оживёт, и не удаляйте старые настройки платёжного сервиса (включая адрес webhook), пока новый сервер не отработал нормально (практика автора).
Почта и webhook платёжного сервиса
Письма магазина идут по цепочке: PHP вызывает sendmail (интерфейс отправки почты, его даёт почтовый сервер postfix), дальше postfix передаёт их внешнему почтовому сервису. При разборе мы обнаружили риск попадания писем в спам. Адрес отправителя в конверте (envelope-from) не покрыт SPF домена сайта (списком серверов, которым разрешено отправлять почту от имени домена), DMARC (политика проверки отправителя) работает в режиме «карантин», а DKIM-подписи (цифровой подписи письма) нет. Как это устроено, разобрано в статье про SPF, DKIM и DMARC. Решений было три: добавить новый источник в SPF, подписывать письма DKIM на postfix либо отправлять через аутентифицированный SMTP внешнего провайдера. Проверка после любого варианта — отправить письмо на тестовый ящик и посмотреть заголовок Authentication-Results.
Отдельный пункт — webhook (уведомления от платёжного сервиса). Адрес для уведомлений строится из публичного адреса сайта, поэтому после переноса его нужно проверить в настройках плагина и в кабинете платёжного сервиса.
После переезда: бэкап и мониторинг
После переезда мы настроили ежедневный бэкап. Архив содержит сайт, конфигурацию сервера и сертификаты; хранится ряд ежедневных копий за месяц на отдельном хранилище. Раз в архиве закрытые ключи и секреты, защитите его: права 600 и ограниченный доступ к хранилищу. Копия, которую не пробовали разворачивать, ничего не гарантирует: мы проверяем её разворотом во временный каталог и сверкой контрольных сумм. Подробнее о приёмах — в статьях про правило 3-2-1 и rsync и restic.
Мониторинг следит за страницами, закрытыми каталогами, сертификатом и свежестью бэкапа. Для страниц и каталогов подходят веб-сценарии Zabbix: шаг может требовать конкретный код ответа, и для закрытого каталога мы ожидаем 403. Срок сертификата и свежесть бэкапа разобраны в статьях про Let's Encrypt и про мониторинг свежести бэкапов.
Выводы и чек-лист
Всё, что нужно для восстановления, должно жить вне хостинга:
- код и контент в Git; после клонирования проверьте, что в каталогах плагинов есть все файлы;
- данные, конфигурация и секреты в бэкапе, который делается ежедневно, а не в архиве, возраст которого измеряется днями; сам бэкап защищён;
- записанный заранее список «что где лежит» и порядок восстановления.
И перед запуском на новом сервере:
- правила закрытия каталогов переписаны под nginx и проверены запросами (
403); - PHP выполняется только через
index.php, пул работает под отдельным пользователем, каталоги пула принадлежат ему; - сервер проверен через
curl --resolveдо смены DNS (адреса из sitemap, редиректы, закрытые пути, тестовый заказ без списания); - цепочка сертификатов внешних сервисов проверена, срок промежуточного сертификата записан;
- почта проходит SPF и DKIM, webhook платёжного сервиса проверен;
- бэкап настроен и разворачивался, мониторинг следит за его свежестью.
Состояние на ноябрь 2026 года (проверьте актуальность). В документации Grav стабильной названа ветка 2.x, ветка 1.7 помечена как Legacy. Правила закрытия каталогов зависят от версии: по документации, правило против запуска скриптов в
imagesиassetsвходит в поставку с версии 2.1.10, а правило дляuser/envуточнено в версии 2.2.4; для nginx такие правила нужно добавлять самим. Сроки жизни сертификатов Let's Encrypt сокращаются: актуальные даты — в статье про Let's Encrypt.
Типичные ошибки и недоразумения
- «Защита каталогов уже есть, она в
.htaccess». Под nginx этот файл не действует: каталоги нужно закрыть в конфигурации и проверить запросом. - Создать каталоги пула через
sudo mkdir. Они достанутся root; нуженinstall -d -o … -g …. - Отдать группе веб-сервера запись во всё (
775и664): ошибка в любом скрипте тогда получает запись во все файлы сайта. - Выпускать сертификат по HTTP-01 до смены DNS. Проверка требует, чтобы имя уже указывало на этот сервер.
- Лечить ошибку цепочки через
-k. Правильнее подключить недостающие сертификаты как отдельный набор доверенных центров. - Считать репозиторий полным, не проверив. Правило
.gitignoreможет спрятать нужный файл; помогаетgit check-ignore -v. - Проверять новый сервер «по DNS». Пока записи не переключены, трафик идёт на старый адрес; используйте
curl --resolve.
Проверьте себя
- Почему правила из
.htaccessне защищают каталоги на nginx и что делать? nginx не читает.htaccess. Правила нужно перенести в конфигурацию (блокиlocationсreturn 403), взяв за основу файл из поставки CMS, и проверить запросами. - Как проверить новый сервер до смены DNS?
curl --resolve имя:порт:адрес URL: запрос идёт на указанный адрес, а имя в URL остаётся. Записи нужны отдельно для портов 80 и 443. - Что означает ошибка
self-signed certificate in certificate chainпри запросе к внешнему API и как исправить её, не меняя системное хранилище? Цепочка построена, но подходящего корневого сертификата в хранилище нет. Нужно указать файл с корневым и промежуточным сертификатами (у cURL —--cacert) и следить за сроком промежуточного. - Почему
location ~ \.php$ { return 404; }не блокирует/index.php, если выше естьlocation = /index.php? Точное совпадение завершает поиск раньше, чем проверяются регулярные выражения. - Чем создание
private/tmpчерезinstall -dлучше, чемmkdir?install -dсразу задаёт владельца и режим. Послеmkdirкаталог принадлежит root, и пул PHP-FPM не может писать в него.
Что дальше
- HTTPS и сертификаты: как работает Let's Encrypt и как получить сертификат — DNS-01, сроки, мониторинг сертификата.
- SPF, DKIM и DMARC на примере: как защитить домен от подделки писем — как письма магазина проходят проверки.
- Из практики админа 03. Бэкап жив? Мониторинг свежести резервных копий — как проверить, что копия свежая.
- Безопасность для начинающих 09. Защита сайта: TLS, заголовки, права, OWASP Top 10 — TLS, заголовки, права, обновления CMS.
Источники
- nginx: ngx_http_core_module — выбор
location, порядок регулярных выражений, точное совпадение=,try_files,server_name - nginx: ngx_http_rewrite_module — директива
return, коды 301 и 403 - PHP: FastCGI Process Manager, настройка — директивы пула, сокет и его права,
security.limit_extensions,php_admin_value, оговорка о пулах - Ubuntu Manpages: adduser(8), 24.04 — опции
--system,--group,--home - Grav: User Folder Exposure — какие каталоги закрывать, правила для nginx, версии поставки правил
- Grav: Permissions — три сценария прав, потребность CMS в записи, права на кэш изображений
- Grav: webserver-configs/nginx.conf (GitHub) — конфигурация nginx из поставки CMS
- Certbot: User Guide — режим webroot, автопродление, хуки развёртывания,
--dry-run - Let's Encrypt: Challenge Types — HTTP-01, порт 80, следование редиректам
- OpenSSL: X509_STORE_CTX_get_error — смысл ошибки «self-signed certificate in certificate chain»
- curl: TLS Certificate Verification — собственное хранилище доверенных центров для отдельных запросов, предупреждение о
--insecure - curl: man page —
--resolve,--cacert,-w - Git: git-check-ignore —
-v, какая строка.gitignoreсработала - Git: gitignore — привязка шаблона к каталогу, формат шаблонов
- Zabbix: Web monitoring — сценарии, обязательные коды ответа, следование редиректам