Рабочий стенд · OpenCart · хостинг ukraine.com.ua

Сайт «задыхается»: 150 из 100 CPU-минут, 300 заявок за пару минут и письма, которые хостинг режет

Это три отдельные поломки, а не одна. Каждая лечится точечно, без переделки сайта: сначала снимаем нагрузку, потом закрываем форму, потом чиним заголовок From. Ниже порядок работ, код и чек-лист приёмки. Данные в примерах условные (вымышленный магазин), структура и правки реальные.

Сайт 1

Нагрузка 150/100 мин

боты, 404 и редиректы, тяжёлые запросы к БД
Сайт 1

Письма с чужим From

хостинг блокирует: в From адрес на rambler.ru, отправка с домена хостинга
Сайт 2

Спам и SQL-проба

строка вида 1' AND EXTRACTVALUE(...) в поле «Клиент»

1. Нагрузка: ищем, кто съедает CPU, а не гадаем

«Включить кэш» без диагностики часто не помогает: у OpenCart нагрузку дают разные источники. Порядок такой.

ШагЧто смотримЧто делаем
1Access-лог за сутки: топ IP и User-Agent, доля 404 и 301/302Отдельных ботов (не Google/Bing) режем в .htaccess; «I'm Under Attack» убираем, когда нагрузка спадёт: он отпугивает и покупателей, и поисковик
2Какие URL бьют чаще всего: поиск, фильтры, sort=, limit=, page= до бесконечностиЗакрываем перебор в robots.txt и отдаём 410/404 дёшево, без запуска всего OpenCart
3Цепочки редиректов (http→https, www, слэш, SEO-URL): 2-3 прыжка на каждый запрос ботаОдин прямой редирект на каноничный адрес, правило в начале .htaccess
4Slow query log / EXPLAIN по самым частым запросам: SEO-URL, категории, фильтры, поискИндексы на поля поиска (seo_url.keyword, url_alias.keyword/query), отключаем лишние модули и OCMod/vQmod-правки с тяжёлыми запросами
5Cron и внешние API: курсы валют, синхронизация, чужие виджетыРеже или по cron вместо запуска на каждый визит
# robots.txt: не пускаем ботов в бесконечные сочетания фильтров и сортировок
User-agent: *
Disallow: /*?*sort=
Disallow: /*?*order=
Disallow: /*?*limit=
Disallow: /*?*filter=
Disallow: /index.php?route=product/search
Disallow: /index.php?route=checkout/
Disallow: /index.php?route=account/

# .htaccess: прожорливые краулеры и пустой User-Agent (список сверяем с вашим логом, не вслепую)
RewriteCond %{HTTP_USER_AGENT} (AhrefsBot|SemrushBot|MJ12bot|DotBot|PetalBot|Bytespider|DataForSeoBot|^-?$) [NC]
RewriteRule .* - [F,L]

Что можно проверить: график CPU-минут в панели ukraine.com.ua за 3 суток после правок и число запросов в access-логе до и после.

2. Письма: почему хостинг блокирует отправку

На скрине хостинга: письмо ушло с ящика на web798.default-host.net, а в заголовке From: стоял чужой адрес на rambler.ru. Хостинг такое режет: отправитель не принадлежит домену, это признак подмены. В OpenCart так бывает, когда в форме обратной связи в From подставляют адрес посетителя.

Неправильно
$mail->setFrom($this->request->post['email']);
$mail->setSender($this->request->post['name']);
Правильно
$mail->setFrom($this->config->get('config_email')); // ящик на вашем домене
$mail->setReplyTo($this->request->post['email']); // ответ уйдёт посетителю
$mail->setSender($this->config->get('config_name'));
  1. Найти все места, где setFrom() берёт значение из $this->request (контакты, обратный звонок, «купить в 1 клик», сторонние модули).
  2. В настройках «Почта»: либо mail с корректным From, либо SMTP с ящиком домена.
  3. DNS домена: SPF с включением хостинга, DKIM, DMARC p=none на старте. Проверка тестовым письмом на Gmail: «Показать оригинал» → SPF и DKIM = PASS.

3. Форма заявок: спам и SQL-проба

На скрине заявка: «Клиент: 1' AND EXTRACTVALUE(1,CONCAT(0x7e,1)) AND '1'='1», телефон «1», комментарий «1». Это автоматический сканер: он проверяет, сломает ли кавычка запрос. Два следствия: нужно закрыть поток и нужно убедиться, что поле не попадает в SQL без экранирования (в модулях «быстрый заказ» это встречается).

СлойМераЭффект
СерверВалидация: имя без кавычек и SQL-символов, телефон по маске, длина комментарияМусор отбивается до письма и до БД
БДВсе значения только через $this->db->escape(); проверка кода модуля на склейку SQLЗакрывает саму инъекцию
БотСкрытое поле-ловушка + Cloudflare Turnstile или reCAPTCHAРежет основную массу скриптов без мучений для покупателя
ТемпЛимит: не больше 3 заявок с IP за 10 минут (таблица или сессия)«300 за несколько минут» становятся невозможны
ПочтаПисьмо уходит только после прохождения всех проверокЯщик больше не «обрушивается»
// костяк проверки в контроллере формы / «быстрого заказа»
if (!empty($this->request->post['website'])) { exit; }            // honeypot: человек это поле не видит
$name  = trim($this->request->post['name']  ?? '');
$phone = preg_replace('/\D+/', '', $this->request->post['phone'] ?? '');
if (!preg_match('/^[\p{L}\s\-\.]{2,60}$/u', $name))  $error['name']  = 'Укажите имя';
if (strlen($phone) < 10 || strlen($phone) > 13)     $error['phone'] = 'Проверьте номер';
if (too_many_from_ip($this->request->server['REMOTE_ADDR'], 3, 600)) { http_response_code(429); exit; }
// только после этого: запись в БД через escape() и письмо

Чек-лист приёмки (кликабельный)

Работа считается сданной, когда каждая строка подтверждена фактом, а не словами.

0 из 9

Что нужно от владельца для старта